* Styles: renaming a style keeps its Export Tagging
Renaming a paragraph or character style, or moving it into a style
group, moves its Export Tagging to the new name. Deleting a style drops
its tagging and leaves the replacement's own tagging unchanged.
* Styles: a paragraph style can't be renamed onto another style's name
* Styles: a refused rename says the name is taken and shows the General section
Renaming a paragraph or character style onto a name another style has
now says so ("a paragraph style named `Body` already exists"); empty
or bracketed names keep the generic message. The Paragraph Style Options
dialog switches to General, where the name field is, when the rename is
refused. Paragraph style names are trimmed like character style names,
and a renamed style never picks up an export tag left under its new name.
* Styles: the rename test's stale tagging takes the Split Document field
* ROADMAP: date the style rename line 2026-10-24
* EPUB: Split Document in a paragraph style's Export Tagging
* EPUB: Split Document as a document setting (off, one style, or export tags)
The document stores EPUB Export Options › Split Document (DocSettings.epub_split):
off (one content file, the default), a single paragraph style, or the
paragraph styles' Export Tagging › Split Document flags. document.epubSplit sets
it as one undo step; file.exportEpub takes a split that overrides it for one
export. A Style setting follows a renamed style and a deleted style's
replacement. IDML reads and writes EPubExportPreference BreakDocument and
ParagraphStyleName. A split before a list item or preformatted line closes the
open list or block first, so each content file is balanced.
* EPUB Export Options dialog with Split Document before Export EPUB
File › Export EPUB… opens EPUB Export Options, seeded from the document's
Split Document setting: a Split Document checkbox with Single Paragraph Style
(and its style) or Based on Paragraph Style Export Tags. OK stores a changed
setting through document.epubSplit, then continues to the save dialog; Cancel
does nothing. app.exportEpub with a path skips the dialog. Docs and the
progress log describe the setting.
* EPUB: the navigation test exports one content file with Split Document off
* EPUB export from the menu passes its options (cover, split) to the engine
* EPUB: Split Document on a style's EPUB tag map counts when the style says false; a caller's cover: false stays
Preserve contributor commits and adapt optional-content registration and table-cell graphic calls to the current PDF exporter refactors. Keep current-main roadmap corrections.
All nine cargo xtask ci gates passed on tree d0c21f347e: 3209 tests passed, 6 skipped, 16 WASM-safe crates. The exact PR head also passed 118 PDF unit/integration tests. Independent production-export controls: 8 flat/null cases succeeded; 7 unreadable Prev values refused, 4 compatibility controls succeeded; all 12 emitted PDFs pass qpdf.
This PR is independent of the format-v4 pair (#1647/#1671), which remains held for a reproduced parent-placement IDML issue.
* Type: story Optical Margin Alignment with a size, and Ignore Optical Margin (engine, IDML)
A story's Optical Margin Alignment applies to every paragraph except those
with Ignore Optical Margin; hang amounts scale with the story's size.
story.options sets it, type.para and paragraph styles take
ignoreOpticalMargin. IDML imports and exports StoryPreference
OpticalMarginAlignment/OpticalMarginSize and IgnoreEdgeAlignment. The
per-paragraph opticalMargin switch keeps hanging at the text's own size.
Part of #947.
* Type: the Story panel, and Ignore Optical Margin in the Paragraph panel menu and Paragraph Style Options
Window > Type & Tables > Story sets Optical Margin Alignment and its size
for the stories of the text selection or the selected text frames through
story.options, and is disabled without either. type.selectionAttrs
reports the stories' values as storyOptions, null where they differ.
The Paragraph panel menu toggles ignoreOpticalMargin, and Paragraph Style
Options > Indents and Spacing has an Ignore Optical Margin checkbox.
Part of #947.
* ROADMAP: the story Optical Margin Alignment entry names its default and scope
The new commands note.previous and note.next move the insertion point
to the previous or next editorial note in its story and put the caret
after the note's anchor. Previous Note skips the note whose anchor the
caret directly follows, so repeated use reaches every note. Each item
is unavailable when no note lies in its direction in the story, or when
the insertion point is in a table cell or a footnote. The items follow
the Notes panel item in Type › Notes, with translations in the catalogs
that already translate Next Document and Previous Document.
The new command note.convertFromText removes the selected story text
and puts it into a new editorial note whose anchor sits where the text
was. It takes an optional author, and Convert Note to Text puts the text
back. DesignCraft refuses a selection in a table cell, or one that holds
a note, footnote, endnote, anchored object, index marker, text anchor,
cross-reference or table, and leaves the story unchanged. The item
follows New Note in Type › Notes, with translations in the catalogs that
already translate Convert Note to Text.
Update, Relink and Relink Folder all reload a linked graphic through `reload`,
which read the file with an unbounded `read`. A document whose link points at a
multi-GB file exhausted memory on Update. How large a linked file is belongs to
whoever wrote the document, not to whoever opens it: the path comes out of the
document.
`reload` now reads through `read_capped` at the limit packaging already applies
to a linked file (`MAX_LINKED_FILE_BYTES`, 256 MB), and a file over it is
refused by name instead of read. `reload_capped` takes the cap so a test can
show both sides without a 256 MB fixture.
Not changed: `file.place` reads a path the same unbounded way, but there the
user picked the file, where a link's path can come from a document someone else
wrote. Whether that read wants the same cap is a call for a maintainer, so it
is reported rather than changed.
Found in the audit of #1596 (DC-15).
Found while testing crates/compose/src/notes.rs:133 (also crates/doc/src/notes.rs:265 footnote_start and crates/doc/src/endnotes.rs:150 endnote_start)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Angle field was greyed out with Use Global Light on. It now shows
the document's global light angle and changes it for every effect that
uses it, applied with the effects in one undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
package_refuses_windows_directory_redirects made its redirect with
symlink_dir. Without Developer Mode or elevation that call fails with
os error 1314 (ERROR_PRIVILEGE_NOT_HELD), which std does not report as
PermissionDenied, so the skip arm did not match and the test panicked.
When symlink_dir fails, the test now makes a directory junction with
mklink /J, which needs no privilege. is_redirect treats a junction as
a redirect, so the test still checks that File > Package refuses the
redirected folder. It skips only when neither can be made. The package
code is unchanged.
a_missing_link_shown_by_its_preview_is_still_reported stored the link
/definitely/missing dir/photo.jpg in a document without a path. On
Windows that path names no drive, so document_path refuses it as not
absolute and the preflight message ends with "(the path is not
valid)", and the test failed. The test now stores a link under
std::env::temp_dir(), an absolute path with the default
temporary-folder settings on Windows, macOS and Linux, and expects the
message for that path. The preflight code is unchanged.
Main now aborts an export when a stored hidden layer can no longer be
applied (#876). Vector export follows it: a layer hidden in Object Layer
Options that the placed PDF doesn't have, because the file has no layers
or was relinked, fails with an error naming the file instead of exporting
everything visible. The image-path tests for oversized images and
undecodable content export as PDF/A-2b, which still rasterizes placed
PDFs with hidden layers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep source defaults and per-frame hidden layers in plain and PDF/X-4 vector exports, including independent repeated placements, booklet output and book merging. Accept usage-application arrays and resource-only groups without a catalog configuration.
Use file-named warnings and all-ON defaults for recoverable default-configuration problems and unsupported base states. Add readable groups omitted from OCGs without discarding their visibility, combine concurrent recovery warnings, and generate missing names only when no frame override needs name matching. Retain errors for contradictory states, unresolved references and ambiguous name overrides.
Cache prepared variants per asset and hidden-name set while replaying recovery warnings through the existing deduplicating channel. Keep original source bytes when every group is visible and named, so reader-repaired cross-reference tables remain usable without an incremental layer update. When hidden groups or generated names require writing, reject an unusable cross-reference table with a file-named damage error and retain the first placement failure.
Cover visibility, recovery, names, independent placements, repaired cross-reference tables and vector output with 46 optional-content integration tests, plus preparation-cache and shared-buffer unit tests.
* 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>
Reject a non-null Prev entry that cannot be read as an offset instead of silently treating it as the end of the guarded trailer chain. Keep absent/null end markers and readable numeric offsets unchanged.
Reproduced CodeRabbit discussion_r4366245854 with an independent probe and a failing native regression before the fix. The synthetic regression covers seven unreadable value types plus absent/null and integer/real history controls.
Validation: all 9 cargo xtask ci gates passed (3063 tests passed, 6 skipped; 16 WASM-safe crates). One unrelated GPU test was labelled leaky by nextest in that run and passed cleanly in an isolated rerun. Independent checks: 17 original supported PDF variants still pass qpdf, logical preservation and Poppler pixels; 6 original refusal cases remain refused. All 11 additional Prev controls pass (7 refused, 4 accepted).
Integrate current main (d8d78851) without rewriting the contributor's commits, retaining craft-libs 9f75d3dc.
Accept literal null trailer entries while rejecting actual or ambiguous encryption/hybrid markers. Structurally inspect bounded xref/trailer history instead of adopting trailer-looking comments. Preflight all selected source variants before editing/undo; preserve reset-to-defaults. Suppress stale unrewritable canvas overrides rather than revealing source content. Keep conservative all-content crop fallback without overcropping layerless/null stubs.
Validation: cargo xtask ci, all 9 gates passed; 3062 tests passed, 6 skipped; all 16 WASM-safe crates checked. 17 supported variants passed independent qpdf, logical preservation and Poppler pixel checks; 6 unsupported/repair-only cases refused. Direct-harness Claude Opus 5 reviewed the exact main-relative delta with no blocking findings.
No native-format changes or unrelated layer features. Synthetic regression fixtures are generated in test code.
Found while testing crates/engine/src/cmd/table.rs:1742
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Anchored objects: Relative to Spine mirrors custom positions on right-hand pages
* Anchored objects: page side from the frame's spread position; footnote cache keyed by page side
* Links: use resolve_absolute for stored paths
* Links: check for a missing linked file with is_ok_and
* CI: release only with git dependencies from storytold
The winit patch from skylartaylor/winit at its pinned rev is the one other source allowed.
* Links: Relink to Folder only uses a name that stays a file name in the folder
* CI: the git-sources check reads Cargo.lock as TOML, needs it current and refuses config patches
* CI: the git-sources check parses .cargo/config and .cargo/config.toml for source replacement, patches and paths
* Links: rustfmt
* Links: Relink to Folder only takes a file directly in the chosen folder
* Links: Relink to Folder compares folders as the file system resolves them