Commit Graph
2932 Commits
Author SHA1 Message Date
Brandon Thomas fd207a0d7c Merge pull request #1998 from Silvertree2010/fix/h3964-tagged-text-import-trims-style-n
Keep leading and trailing spaces in Tagged Text style names
2026-10-29 21:52:46 -04:00
Brandon Thomas f9fabc3334 Merge pull request #1840 from dotCooCoo/fix/print-test-driver-default-color
Print: the Windows print test expects the driver's default color
2026-10-29 21:52:42 -04:00
Brandon Thomas b4463b5b26 Merge pull request #1911 from Hamza0101/fix/merge-image-reads
Data merge: an image named by many records is read once (#1595)
2026-10-29 21:52:38 -04:00
Brandon Thomas 1f20480194 Merge pull request #1948 from Silvertree2010/fix/h3527-placing-a-plain-text-file-keeps
Strip story marker characters from placed plain text files
2026-10-29 21:52:33 -04:00
Brandon Thomas 45369565ad Merge pull request #1872 from Hamza0101/fix/pdf-name-escaping
PDF export: a name escapes every byte a name cannot hold (#1582)
2026-10-29 21:52:30 -04:00
Brandon Thomas e62583dfc4 Merge pull request #1905 from Silvertree2010/fix/h2977-rtf-import-a-short-or-invalid-hh
Fix RTF import swallowing characters after a short \'hh escape
2026-10-29 21:52:27 -04:00
Brandon Thomas c0d1acd2b3 Merge pull request #1568 from Hamza0101/fix/table-insert-size
A table size Create Table cannot mean is refused
2026-10-29 21:52:22 -04:00
Brandon Thomas 352d9c526d Merge pull request #1860 from Silvertree2010/fix/h2768-tagged-text-export-does-not-esca
Escape font family and typeface values in Tagged Text export
2026-10-29 21:52:16 -04:00
Brandon Thomas 61adb2063e Merge pull request #1306 from Hamza0101/reproducible-pdf-export
PDF export: SOURCE_DATE_EPOCH stands in for the clock, so exports repeat
2026-10-29 21:52:12 -04:00
Brandon Thomas 0a05c1631c Merge pull request #1233 from seanth/running-header-order
[fix] Running headers read a page in text order, not by height
2026-10-29 21:52:08 -04:00
Brandon Thomas 4777269ee6 Merge pull request #1976 from hscabral/idml-export-story-preference
IDML export: write the document's default StoryPreference
2026-10-29 21:52:04 -04:00
Brandon Thomas bb210adf99 Merge pull request #1932 from devius/ka-new-strings
i18n(ka): Georgian for keyboard shortcut sets, command forms and labels added since #1573
2026-10-29 21:52:01 -04:00
Brandon Thomas 9d73109012 Merge pull request #1908 from Silvertree2010/fix/h2951-xml-export-writes-control-charac
Drop XML-forbidden control characters in file.exportXml
2026-10-29 21:51:57 -04:00
Brandon Thomas cddf074cc4 Merge pull request #1868 from Hamza0101/fix/table-allocation-cap
Tables: a shape a table cannot hold is clamped, not allocated (#1583)
2026-10-29 21:51:53 -04:00
Brandon Thomas 3e15ebc7b0 Merge pull request #2008 from Silvertree2010/fix/h4052-name-with-color-value-produces-r
Fix "-0" in Name with Color Value for slightly negative RGB
2026-10-29 21:51:49 -04:00
Brandon Thomas f920aa0d93 Merge pull request #1971 from KnottyDyes/squad/tables-clear-footnotes
Compose: tables clear the footnote area
2026-10-29 21:51:45 -04:00
Brandon Thomas 1f04ec6d61 Merge pull request #1915 from Silvertree2010/fix/h3022-idml-export-duplicates-cross-ref
Fix duplicated xref format text before an unclosed '<' in IDML export
2026-10-29 21:51:42 -04:00
Brandon Thomas 2b15bf79a2 Merge pull request #1842 from Silvertree2010/fix/1835-document-not-updated-on-image-re
Repaint the canvas when a placed file is relinked or updated
2026-10-29 21:51:38 -04:00
NetterSilverandClaude Haiku 5.5 6c6efe2f7a Fix "-0" in Name with Color Value for slightly negative RGB
Found while testing crates/color/src/swatch.rs:94-101 (value_name)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 15:43:45 +00:00
NetterSilverandClaude Haiku 5.5 e9d7a19994 Keep leading and trailing spaces in Tagged Text style names
Found while testing crates/textimport/src/tagged.rs:~190-205 (the ParaStyle and CharStyle branches of import: `unescape_value(value.trim())`)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 11:58:54 +00:00
Hamza 18f2dc2bde A spent cell budget stops reading tables, rather than keeping one each
Keeping a 1 x 1 table for every further `<Table>` was unbounded in the one
direction the budget exists to close. `<Table></Table>` is fifteen bytes, so
the 512 MiB a part may decompress to holds some 36 million of them, and a
`Cell` is 1,360 bytes: tens of gigabytes, with every table inside both caps.
The test I wrote said as much without my noticing -- its invariant was
`reserved <= MAX + tables`, and `tables` had no bound.

`budgeted_size` now returns `None` once the budget is spent and the table is
not read at all, so the cells one import reserves are at most the budget,
exactly. The invariant is now `reserved <= MAX_IMPORT_TABLE_CELLS`, with no
term that grows with the file.

The warnings needed the same treatment, for the same reason: a file of 400,000
tables each claiming one row and two columns got a line about every one of
them. At most 20 are named, then one line saying the rest were the same, and
the file is told once that its tables ran out of cells between them.
2026-10-29 16:43:49 +05:00
Henrique CabralandClaude Opus 5.5 50b5936b78 IDML export: write the document's default StoryPreference
InDesign always writes a document-level StoryPreference in
Resources/Preferences.xml; DesignCraft only wrote one per story. IDML readers
that take the spread reading order from the document's preference, such as
Okapi's IDML filter, failed on DesignCraft's files without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:07:42 -04:00
KnottyDyesandClaude Opus 5.5 ed88f3e4b5 Compose: tables clear the footnote area
Reserve column and spanning footnotes when fitting inline table row groups. Continue rows in the next column or frame when the remaining space is too small, and include cell-local footnotes in measured row height.

Fixes #1901

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:44:34 -06:00
aiongg 855d606325 Show file and message dialogs without blocking the frame (fixes macOS crash) (#1961)
* Show file and message dialogs without blocking the frame

A blocking rfd dialog inside the egui frame runs AppKit's modal loop; a
file dragged from Finder and held over the window then re-enters winit's
handler and aborts the app. Dialogs now go through craft-dialogs: a
command without a path asks with a continuation and returns, the host
shows the dialog at the end of the frame, and the continuation runs with
the answer on a later frame, in the document it was asked for. Commands
given a path stay synchronous. The replace-file question is a message
box from the same queue. Quit and Close wait for a Save As dialog's
answer. clippy.toml disallows rfd::FileDialog and rfd::MessageDialog.

* Raise the open file panel when File > Open is chosen again

* Repin craft-dialogs to the craft-libs PR head

* Queue dialogs asked by different code or documents; hold answers behind newer dialogs

Two asks for the same file dialog were treated as one, so Place for one
document and then Choose Image, or Place again for another document, ran
the first ask's action. An ask is now skipped (and the open panel
raised) only when the same code asked for the same dialog for the same
document. An answer that would replace an egui dialog opened since the
asking (an unsaved-changes question, Preferences) waits until it closes;
Quit counts it as pending.

* Repin craft-dialogs to the craft-libs PR head

* Allow the file dialogs' existence checks in the file guard

* Repin craft-dialogs to storytold/craft-libs main
2026-10-28 17:13:45 -04:00
Claude 94e0e75249 Merge storytold/main (v0.14.0) into running-header-order
The running-header tests move to cmd/variables/tests.rs with upstream's split.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
2026-10-28 18:00:57 +00:00
NetterSilverandClaude Haiku 5.5 79b871b94f Strip story marker characters from placed plain text files
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>
2026-10-28 15:18:35 +00:00
Hamza 87b49d9973 PDF export: SOURCE_DATE_EPOCH stands in for the clock, so exports repeat
A PDF export recorded the second it was made in, so exporting one document
twice gave different files although the pages matched -- and the instance id
krilla derives from the bytes followed the timestamp, so nothing about the two
files was comparable.

SOURCE_DATE_EPOCH stands in for the clock when the export is given no date, as
the reproducible-builds specification defines it. A date passed in still wins,
and a value the PDF date format cannot hold -- malformed, negative, or past
the range -- is ignored rather than refusing the export or writing a date no
reader can parse. With the epoch pinned the two files are identical byte for
byte.

Rebased onto the post-split main: export_time and pinned_or live in
export/metadata.rs beside now_unix, which they stand in front of, and the call
site is in export/pipeline.rs. The ROADMAP keeps upstream's 2026-10-17 entry
with this one above it.
2026-10-28 19:09:25 +05:00
Hamza 352609117f A table size Create Table cannot mean is refused
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.
2026-10-28 18:20:13 +05:00
Dylan M. Taylor d04c8b2e58 Refactor ui-egui for strict Clippy checks 2026-10-28 11:39:26 +00:00
Dylan M. Taylor 5b21472180 Refactor engine for strict Clippy checks 2026-10-28 11:39:26 +00:00
Dylan M. Taylor 072dd02f73 Refactor render for strict Clippy checks 2026-10-28 11:39:26 +00:00
Dylan M. Taylor be9b8f360e Refactor idml for strict Clippy checks 2026-10-28 11:39:26 +00:00
Dylan M. Taylor 0104122f06 Refactor doc for strict Clippy checks 2026-10-28 11:39:26 +00:00
Dylan M. Taylor e6ff9d5aac Refactor fonts for strict Clippy checks 2026-10-28 11:39:26 +00:00
Dylan M. Taylor 71a579e8da Refactor compose for strict Clippy checks 2026-10-28 11:39:26 +00:00
Dylan M. Taylor 07366af1b6 Refactor pdf for strict Clippy checks 2026-10-28 11:39:26 +00:00
Dylan M. Taylor bcb9546f5d Refactor tools for strict Clippy checks 2026-10-28 11:39:26 +00:00
Dylan M. Taylor f1a5744e8c Refactor textimport for strict Clippy checks 2026-10-28 11:39:25 +00:00
Dylan M. Taylor c2869280a2 Refactor epub for strict Clippy checks 2026-10-28 11:39:25 +00:00
Hamza 1995fb5feb IDML import: the tables in one file are capped between them
Review pointed out that capping each table leaves a file free to hold
many capped tables: three at the per-table limit claim 750,000 cells,
about 1 GB of Cell, with every table inside its own limit. The package
reader's limits count input bytes, which say nothing about how many
cells a row and column count claim.

budgeted_size clamps each table against what the import has left, and
the cells are reserved before Table::new so a cell's own story cannot
spend the budget the table around it is counting on. The budget is
400,000 cells: a Cell carries a whole story and is 1,360 bytes, so that
is about the 512 MiB one part of the file may already decompress to,
and the same count the spreadsheet importer collects at most.

A table is never read as nothing. One row and one column are kept, so
the table stays in the document and still says where its text went, and
a file pays one cell for a table it really does contain -- which its own
size already bounds. The warning says which limit cut the table down.

The arithmetic is tested rather than the allocation: 400,000 cells is
544 MB and three such tables are not a thing to ask CI to allocate. The
sequence test runs 32 claimed sizes, including usize::MAX, and holds the
invariant across all of them.
2026-10-28 16:31:34 +05:00
Hamza b49535624c Tables: a shape a table cannot hold is clamped, not allocated (#1583)
`Table::new` allocates `rows * cols` cells up front, and the counts reach it
from IDML and DOCX, where they are whatever the file claims. Measured here:
`size_of::<Cell>()` is 1,360 bytes, so the reported IDML case -- one `<Table>`
with 5,000 `<Row/>` and 5,000 `<Column/>`, about 1 KB zipped -- asks for 25M
cells, some 34 GB. That allocation does not fail politely: it aborts the
process through `handle_alloc_error`, which no `catch_unwind` can catch, so
every open document's unsaved work goes with it.

A table is clamped to InDesign's own limits, 10,000 rows and 1,000 columns,
and to 250,000 cells whatever its shape -- the row and column limits alone
would still allow 13.6 GB. Columns are clamped first and rows take what is
left, because a table is far more often long than wide.

The IDML importer walks the clamped slices and reports the difference.
Indexing its rows and columns by the file's own counts would be out of
bounds, which is how a 34 GB abort would otherwise have become a panic.

Rebased onto the post-split main: the constants and `clamped_size` live in
`doc`'s `table` module root, `Table::new` in `table/grid.rs`, and the IDML
table reader in `import/table.rs`.
2026-10-28 16:26:13 +05:00
Hamza a30744fed1 Drop the unused ImageCache inspection methods
`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.
2026-10-28 11:53:56 +05:00
Davit MatchakhelidzeandClaude Opus 5.5 20d6722820 i18n(ka): keep the Letter, Legal and Letter - Half paper sizes in English, like Tabloid
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-28 05:00:07 +04:00
Davit MatchakhelidzeandClaude Opus 5.5 40083be3f2 i18n(ka): Georgian for keyboard shortcut sets, command forms and labels added since #1573
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-28 04:07:38 +04:00
aiongg 1abeda4459 Merge pull request #1802 from dotCooCoo/fix/column-rules-span-the-text
Text frames: column rules run from the top line's ascent to the bottom line's descent
2026-10-27 13:45:19 -04:00
aiongg 5d8739b3d8 Merge pull request #1795 from dotCooCoo/feat/story-editor-depth-ruler
Story Editor: a depth ruler beside the text, and View › Story Editor › Show/Hide Depth Ruler, as in InDesign
2026-10-27 13:44:46 -04:00
aiongg d36e5fb63f Merge pull request #1927 from Silvertree2010/perf/export-speed
PDF export: ask the composition cache once per story and page
2026-10-27 13:41:05 -04:00
NetterSilverandClaude Opus 5.5 1db1635a88 Ask the composition cache once per story and page in PDF export
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>
2026-10-27 16:47:25 +01:00
aiongg d77fdf2d4c fix(ui): dropdown menus grow back after a search with no matches (#1917)
* fix(ui): font menus grow back after a search with no matches

Every combo box is craft_ui::ComboBox (craft-libs pinned at 62bb0e1d), whose
menu follows its contents' height while open (egui#5225); clippy.toml bans
egui::ComboBox.

The Font menu splits into a header (search field, filters) and a list
(font_menu_header, font_menu_list). Font fields show it in a
craft_ui::combo::Menu and the style dialogs' Font Family combo boxes in
ComboBox::show_ui_with_header, so only the rows scroll and the search field
stays in place. The blank and Parent's Font rows sit in the header, above
the search.

* chore(deps): craft-libs 60d60d48 (dropdown fixes)

* chore(deps): craft-libs 4d7b82f7 (dropdown fixes)

* chore(deps): craft-libs 92d392cf (#387 merged)

* chore(xtask): pdf-deps expects craft-libs 92d392cf
2026-10-27 10:27:32 -04:00
dotCooCoo 9ef2e64f9f Merge main into the column rule extent fix 2026-10-27 07:16:50 -07:00