fit() counted every break character in the overset text and every
Start Paragraph option. A break followed by a paragraph whose option it
already meets moved the text once but asked for two frames, and breaks
that tracked changes delete or a hidden condition hides asked for
frames that composing never uses. Autoflow added those pages and
removed them again: 200 chapters that end in page breaks and start on
the next page took 61095 ids and 430 ms, now 798 ids and 16 ms.
Each paragraph composing places now counts once, when a visible break
ends the paragraph before it or its start option sends it on; after a
break it is at the top of a frame, where the option only fixes odd or
even page parity. A visible break inside a paragraph counts once more.
previous_visible_break and the new visible_break in designcraft-compose
are public, so the estimate and the composer use one rule.
The start option lookup now reads the next paragraph composing places,
past tracked-deleted ones. Frames that start options and breaks force
are counted and asked for in one round, so a story with a paragraph per
page no longer gets one frame per round.
fit() counted the frame before a paragraph that starts on the next
column, frame, page or odd/even page as a full frame. With a short
title before such a paragraph, autoflow estimated a page per few bytes
left and added up to 2000 pages before removing them again: placing
2000 words took 35.6 s in the app, now 0.36 s, with the same pages.
The option is resolved through the paragraph style, so an imported
Word style counts the same as a local override.
Found while testing crates/textimport/src/lib.rs:140 (plain) and crates/engine/src/cmd/place_text.rs:372 (place_text)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A size that was given but is not one a table can have was corrected in
silence: {"rows": 0} made a table with one row, -1 and 1.5 made the default
four, and 10000 was capped at 500 -- so a script asking for a shape the
command cannot honour got a different table and no indication of it.
A size that is absent still takes the default; a size that is present and
outside the range is refused, naming the key and the range.
Rebased onto the post-split main: `table.insert` moved to
table/structure.rs and the tests to table/sizing.rs. The helper is named
`given` rather than `count`, which that module already imports from
table/target.rs for the repeat count of a row or column insert.
Review caught a wrong assertion in my own test. `\\?\C:\x` is the
extended-length spelling of `C:\x`: craft-docpath parses it as
Root::VerbatimDrive, is_absolute, and its own suite classifies it
PathKind::Local. The test asserted it was refused, so it would have
failed on Windows -- the one platform it runs on.
A data file can already name C:\secret\x.png, so refusing the long
spelling would have shut out long paths without shutting anything out.
It is now asserted the other way, and the refusal list keeps a device
path in its place.
Each shape was checked against the pinned craft-docpath through
resolve(), which recognises Windows forms on every platform, so the
verdicts are first-hand and not read off the source: Network for the
extended UNC prefix, Device for \\.\pipe, GLOBALROOT, CON and NUL.png,
and Ok for \\?\C:\secret\x.png.
`len` and `is_empty` were added for a test that ended up counting reads
through `io::touched` instead, so they were dead code -- and the gate runs
`cargo clippy --workspace --all-targets -- -D warnings`, which turns
`dead_code` into a failure. Hosted CI was red on this PR.
My local check had been `cargo clippy -p designcraft-engine --all-targets`
without `-D warnings`, which is why I missed it; re-ran it the way the gate
does.
Found by the review bot, confirmed against the CI log.
`place_image` stat'd and read its file for every record, so a sheet naming one
image in many rows read it once per row -- up to 256 MiB each time. The audit's
case is a 100,000-row CSV naming one large file: about 25 TB of I/O for one
merge.
A merge now carries an `ImageCache`: the bytes by resolved path, read once and
shared. The asset takes the cached `Arc` rather than a copy, so the records
share one buffer as well. The cache stops growing past 256 MB of distinct
files, so a merge over many large images is slow rather than out of memory.
Measured by counting what the engine hands the file system (`io::touched`):
without the cache 50 reads for 25 records (a stat and a read each)
with it 2
A note on the test, because the first one I wrote was useless: asserting on the
bytes the document holds passes either way, since `add_asset` already dedupes
identical content. Memory never showed this; the I/O is the bug, so the test
counts reads.
Audit DC-14. Its second half -- capping the pages a merge produces -- is not
here: how many pages a merge may make is a product decision, and the records
are the user's own data rather than something arriving from outside.
`image_path_tests` was `#[cfg(all(test, unix))]`, so the assertion that matters
most ran only where it matters least: a merge image field naming another
computer must resolve to nothing, because on Windows an outbound SMB connection
hands over NTLM credentials. That refusal was unverified on Windows.
The refusals now run everywhere -- nothing in them is unix-specific, since
`image_candidates` delegates to `craft-docpath` -- and the Windows shapes that
file refuses are asserted where they have meaning: `\\?\UNC\...`,
`\\.\pipe\...`, `\\?\C:\...` and the legacy device names.
Resolution beside the data and the document stays unix-only: it asserts what an
absolute path means, which cannot be checked on Windows from here.
No production code changes. On macOS both cross-platform tests pass; Windows CI
is the first real run of the Windows ones, which is the point.
Audit DC-20 (its testable part; the privacy note about absolute local paths
matches InDesign and is a product decision, not a bug).
Found while testing crates/engine/src/cmd/xml.rs:40 (esc, used by para_xml at :68 and the href at :104)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/cmd/xml.rs:~290 (the xml.mapStyle command; the export is in export_xml)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* refactor(engine): split convert_all's per-story plan into convert_plan
Resolves SonarCloud rust:S3776 on convert_all (cmd/variables.rs).
* refactor(pdf): split graphic_unclipped into per-kind helpers
Resolves SonarCloud rust:S3776 on graphic_unclipped (pdf/src/export.rs).
* refactor(compose): extract wrap exclusions and frame text area from frame_specs_placed
Resolves SonarCloud rust:S3776 on frame_specs_placed (compose/src/lib.rs).
* refactor(ui): extract command shortcut lookup from shortcut_key
Resolves SonarCloud rust:S3776 on shortcut_key (ui-egui/src/menus.rs).