The script command threw away the result of each export, so a PDF with
a drop shadow came out with nothing on stderr, while run --export
printed "warning: 1 object(s) with soft effects ... exported as images".
Each warning an export returns now goes to stderr as "warning: ...".
The JSON report on stdout is unchanged.
`validate_pdf` accepts `--standard x4|x1a`, and both its own usage string and the
module docs say so, but the line in the global `USAGE` left it off -- and that
line is exactly what `validate-pdf --help` slices out. So the option was
documented everywhere except the one place a user would look (noted in review).
`subcommand_help_names_the_options_that_subcommand_takes` asserts it, checks the
parser and the help agree by refusing an unknown standard, and does the same for
`run --cmd` and `mcp --automation-read-root` so this is about subcommand help
rather than one line.
`<sub> --help` answered about that subcommand without first asking whether the
subcommand was one, so `designcraft-cli typo --help` printed the whole usage and
succeeded. `typo` on its own was an error and `typo --help` was not, which is
backwards: a script checking the exit status carried straight past a misspelled
command (noted in review).
`is_subcommand` reads the names out of `USAGE` rather than listing them again,
so one added to the usage text cannot be missing here, and `usage_for` now uses
the same line parser instead of its own copy. An unknown name falls through to
the dispatch and fails there, as it already did without `--help`.
Measured over every name the usage lists -- run, commands, describe, script,
app, mcp, perf, bench, validate-pdf, links -- each still answers on stdout and
exits 0, while `typo` and `nonsense` exit 1. The test asserts both halves, so
the check cannot quietly become a block on subcommand help.
designcraft-cli thumbnail a.designcraft a.designcraft replaced the
document with PNG bytes. The output is now refused before anything is
read or written when it is the input by the same path, through a
symlink, or on Unix as a hard link (same device and inode).
Review caught a consequence of the previous commit. Standing the order check
aside so a missing value could be reported meant the value was reported
later -- after the exports before it had already been written. `run --export
first.png --scale --export second.png` wrote first.png and only then failed,
so an invocation that fails could overwrite a file that was already there.
`values_present` checks every option that takes a value before the order
check and before any export, so the line is refused as a whole. The test
writes a file at the export path first and asserts its bytes are unchanged.
- An option took the next token whatever it was, so `--export --scale` wrote a
file called `--scale` in the working directory. A token that cannot be a
value is refused, and where the value is a path the message offers the
shell's own way out (`./-name`). A negative number can still be a value, and
`-inf`/`-nan` still reach the check that reports them.
- An argument that is not UTF-8 panicked inside `std::env::args`. It is a
usage error now, named, since such a path cannot reach the engine anyway.
- `<sub> --help` was read as an unknown option by every parser but `script`'s.
It answers from the usage block, on stdout, and nothing else happens.
Rebased onto the post-split main, where the fix had to grow one part. Upstream
has since added a check that an export option sits before its `--export`, and
it consumed the following token whatever it was, so `--scale --export out.png`
was reported as the *order* of the options -- advice to move an option that is
already where it belongs -- when what is wrong is that `--scale` has no value.
That check now skips a value only where the next token can be one, and stands
aside entirely when a value is missing so the value error speaks. Its own test,
`run_export_options_come_before_their_export`, still passes.
The CLI's tests moved to tests/it/cli.rs, so the test additions went there.
The PDF exporter called Cache::get twice for every text frame it drew.
Each call rebuilds the cache signature of the whole story, which looks up
every frame of the story with Document::find (a linear walk over all
spreads), and the anchored-frame placements walk them again. For a story
threaded through many frames this is O(frames^2 x items) per export.
The document doesn't change during an export, so the exporter now keeps
the composition it got for each (story, page) and reuses it. The cache
still decides what to compose; the output is byte-identical.
Add examples/pdf_export.rs to designcraft-cli: File > Export PDF through
a headless session on the `designcraft-cli perf` stress document (200
pages, 1M characters in 40 threaded stories, 500 placed images). The
stress document generator moves unchanged from src/perf.rs to a small
library module (src/stress.rs) that the binary and the example share.
Ryzen 16 threads, Linux, 12 runs each, interleaved:
main median 3485 ms, min 3427 ms
patched median 2016 ms, min 1656 ms (-42 % median, -52 % min)
The exported PDF is byte-identical (12,075,744 bytes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #1374 with upstream main merged (egui 0.37, craft-libs at
storytold/craft-libs e3b9e239).
Conflicts:
- Cargo.toml: print-color's pins (storytold/craft-libs e3b9e239 for
craft-pdf, craft-prepress, krilla and the hayro crates; winit and egui
patches), with craft-prepress keeping the `flatten` feature.
- xtask/src/pdf_deps.rs: print-color's REPO/REV.
- Cargo.lock: print-color's, relocked with cargo metadata; adds only
craft-prepress and linesweeper.
- crates/pdf/src/export.rs: main's optional-content registration and
add_form_fields helper; the helper leaves form fields and media out of
PDF/X-1a with a warning.
- crates/engine/src/cmd/export.rs: the X-1a parameter docs plus main's
`created`; the X-1a flatten and shadow-mask path on top of main's
PDF/A-2b-only rasterize_layered.
Also: x1a_tests builds FlattenTotals through Default, since the struct is
non_exhaustive at e3b9e239.
PDF/X and CMYK groups keep this branch's approach: krilla's serialize
settings (pdfx::print_settings) write the CMYK page and transparency
groups, the GTS_PDFX output intent and the PDF/X Info and XMP entries.
make_pdfx4 adds only the XMP dates, so main's title argument and its
output-intent, Info and pdfxid XMP edits are dropped, and both
cmyk_group_spaces calls after writer.finish() (Export PDF, Print
Booklet) stay removed. Main's new parts are kept: file_info_metadata
(also for Print Booklet, on top of the print settings), optional content
registration and layer errors, add_form_fields, and pdfx's fonts module.
Main's metadata panic test calls the two-argument make_pdfx4.
craft-libs and the krilla patch are pinned to storytold/craft-libs
e3b9e239; Cargo.lock is main's, relocked without updates.
Pin the final egui, winit and shared UI revisions and reconcile current main.
Preserve document-epoch input and screenshot boundaries, host action ordering,
and Windows path regression coverage. Add click/End batch characterization.
Validation: all nine Linux CI stages pass (3230 tests, zero failures); Windows
CI and bounded native editing workflows pass on the same production source.
The test removed only the \\?\ prefix from the folder it resolved, so a
temporary folder on a network share became the relative path
UNC\server\share\... . It now removes the \\?\UNC\ prefix first and
keeps the two leading backslashes, as file_access::plain does, with a
test of both forms.
Four tests from #1678 fail on Windows. Each one builds its paths, or the
text it expects, from a folder resolved with `std::fs::canonicalize`,
which adds the `\\?\` prefix on Windows. `AutomationRoots::describe` and
the rejection messages show the roots without that prefix, so the
expected text does not match. Windows does not resolve `..` in a path
with the prefix, so the checks refuse every such path as one they cannot
resolve, including the ones the tests expect to pass from one root into
another. The code the tests check is unchanged.
The engine tests take the resolved folder through `file_access::plain`,
which the roots use for the text they show. The CLI test strips the
prefix the same way.
* chore: resolve SonarCloud maintainability issues
- Replace lambda closures with method references (S1612)
- Replace wildcard imports with explicit imports (S2208)
- Refactor complex functions with extracted helpers (S3776)
- Replace OR pattern with range pattern in math parser (S9047)
* style: apply rustfmt to warichu helper
* fix(compose): revert slice conversion in tests and remove unused var in warichu
* Group warichu row layout scalars to satisfy S107 parameter limit
* style: satisfy rustfmt in warichu.rs
---------
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
A path passes when it lies inside one of the roots of its kind. The roots'
common parent, a sibling named like a root, `..` or a link out of them and
the other kind's roots stay refused; a folder given twice counts once.
Track text state through graphics saves and drawn forms; exempt invisible text. Report font names and pages in export warnings and validation without embedding or outlining placed fonts.
Repin craft-pdf, craft-prepress, krilla and the hayro crates to d30598d.
flatten_document writes the X-1a file at PDF 1.4 with GTS_PDFXVersion
PDF/X-1:2003 in the Info dictionary and XMP, so DesignCraft's header
rewrite and incremental update are gone. check_pdfx1a refuses DeviceN
NChannel attributes and warns about an ICC v4 output intent profile in a
file of PDF 1.5 or lower; the export names the built-in Coated FOGRA39
(v4) when it is the intent. validate-pdf lists warnings apart from issues.
standard x1a writes the PDF/X-4 print file, flattens it with
craft_prepress::flatten::flatten_document at the flattener preset's
resolution (high 300, medium 150, low 72 ppi; default 300), then makes it
PDF/X-1a:2003: the PDF 1.4 header, GTS_PDFXVersion and GTS_PDFXConformance
in the Info dictionary and XMP. check_pdfx1a refuses transparency (soft
masks, transparency groups, CA/ca below 1, blend modes) and color outside
DeviceCMYK, DeviceGray, Separation and DeviceN; export fails when the
flattened file still has either.
The X-1a path skips DesignCraft's RGB transparency flattener. Soft drop
shadows are drawn in their swatch's inks through a coverage mask, so
craft-prepress flattens them per ink instead of an RGB image of the
object. validate-pdf takes --standard x4|x1a; the Export PDF dialog gains
the PDF/X-1a:2003 preset and standard and a Transparency Flattener preset.
A stop can name a colour swatch (spot, process, tint, [Paper]) next to its
display colour. swatch.create {gradient}, object.gradient, the Gradient panel
and IDML StopColor keep the reference; renames follow it, swatch edits refresh
the stop's colour and a deleted swatch leaves a plain colour. The field is
optional and omitted for plain stops, so saved documents load and save as
before.
Found while testing apps/designcraft-cli/src/main.rs:361 (`--scale` parse) and :386 (export); crates/render/src/lib.rs:422-423 (render_page)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Graphic literals (cli perf, doc assets/item, engine datamerge/export/file/links/mathexpr/media/table/sample, epub, format, pdf, pptx, render, tools, ui-egui properties, and tests): name the branch's clip and wrap (#561, #381) and the PR's fill.
crates/doc/src/item.rs: Graphic keeps wrap and gains fill.
crates/idml/src/import.rs: read the image swatch, then the branch's styled FrameFittingOption lookup; the graphic keeps its wrap.
crates/idml/src/tests/assets.rs: keep both new tests.
crates/render/src/lib.rs: keep #362's crop-box sizing of placed PDFs and colour the resulting pixmap.
crates/render/tests/colorize.rs, crates/epub/tests/grouped_reading_order.rs, crates/compose/src/tests.rs: literals name the branch's Asset and Graphic fields.
crates/pdf/src/export.rs: the soft mask interpolates by the standard's raster mode (the branch has no per-exporter interpolate switch).
docs/gaps.md, docs/target-app-parity.md: status lines list both sides' changes; gap text keeps the clipping-path items and adds the image swatch.
main (#731) and next fixed the same panic separately; main reads null as no
options and tests that the export succeeds, next's test listed null among the
errors. The CLI keeps main's reading.
frame.create, story.setText, text.insert and the other commands that take
text kept U+000B as a character, drawn as a missing-glyph box. It is the
forced line break of Word and PowerPoint text (Chr(11), Shift+Enter), and
what people porting scripts try first, so text_param now reads it as
U+2028, the forced line break, next to the CR handling it already has.
script --help and docs/agents.md say so.
Conflicts and how each was resolved:
- crates/idml/src/import.rs: main's linked_assets and insert_asset kept; the
crop is read beside main's page (PDFAttribute as a child or a property), and
linked_assets is keyed by (path, page, PdfCrop) so two crops of one page are
two assets.
- crates/idml/src/export.rs: PDFAttribute with PageNumber, PDFCrop and
TransparentBackground is written for every placed PDF, replacing main's
page > 0 block.
- crates/idml/src/tests.rs: main's file plus the `$ID/` font tests.
- crates/engine/src/cmd/file.rs: main's exact PDF page size, place cursor and
add_asset kept; Crop to stores the choice on the asset (the graphic spans
the box), so the place cursor's frame crop box is gone.
- crates/engine/src/cmd/{book,interchange}.rs: main's confined and reporting
imports, with the PDF crops resolved after them.
- crates/engine/src/cmd/print.rs: main's print_with, exporting with the
measured PDF boxes.
- crates/engine/src/cmd/links.rs: main's text links plus pdfCrop.
- crates/engine/src/cmd/{datamerge/fill,mathexpr,media,table}.rs,
crates/format/src/lib.rs: main's add_asset with the asset defaults.
- crates/engine/src/cmd/package.rs, crates/engine/src/tests_idml.rs: both
sides' tests.
- crates/fonts/src/fontdb.rs: main's style traits, looked up through
plain_family.
- crates/render/src/lib.rs: pdf_crop_box replaces pdf_page_box; main's
UserUnit test checks pdf_crop_box.
- crates/ui-egui/src/dialogs.rs: main's dialog with the content-bounds crops.
Also: Asset::same_file compares the PDF crop, so placing or saving no longer
merges two crops of one page; Asset literals added on main take the defaults;
main's PageNumber test checks the crop and that page 1 is written; new tests
a_pdf_page_placed_with_two_crops_imports_as_two_assets and
pdf_assets_with_different_crops_stay_apart.