* 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
* Search: Delete Workspace is left out of Quick Apply and command search
It needs a saved workspace's name, which a search entry can't supply,
so choosing it there failed. The application bar's workspace menu lists
the saved workspaces to delete.
* Search: leave out the interface commands that fail or reset a setting there
Quick Apply and the application bar's search ran their entries without
parameters. Commands that need a workspace, a command and shortcut, a
menu item, a panel, a window edge, an app page, a recent file, or a
swatch or color group failed when chosen there. Interface Color Theme
and UI Scaling switched to the dark theme and 100%, Workspace switched
to Essentials, and Reset Shortcuts cleared every custom shortcut without
asking. Search leaves all of them out; each stays in the menu, panel or
dialog that gives it the value.
* Search: the flattener preset, Screen Mode, Zoom To and the Task Bar pin stay out of Quick Apply
The report asks for a placement the export walk reaches whose asset is still a PDF, so it cannot
name a choice anchored inside a table cell or a note. The paragraph now says that rather than
leaving the scope to the sentence above.
The placed-PDF doc said every placement counts. Both exports and the PDF/A-2b rasterisation pass
walk document and parent spreads, groups and objects anchored in a story; a placement anchored
inside a table cell's or a footnote's text is not reached yet, so the doc now says what happens
to it instead of claiming it is carried. Gap 19 states the same boundary in one line.
The parent-page regression covers a placement a page inherits. The other placement the export
walks reach is an object anchored in a story: the IDML copy gets a variant that shows every
layer, an external-only copy is refused, and PDF/A-2b rasterises it and warns. The nested-story
regression that used to cover this case also asserted placements anchored inside a table cell and
a footnote, which this pass does not reach; this one keeps the anchored case alone.
Both exports walk Document::placed_items: document and parent spreads, groups, and the objects
anchored in a story. That is the walk the parent-page and anchored fix introduced. The
nested-story traversal (table cells, notes, endnotes), Document::update_placed_items and the
fail-closed refusals that went with it are not part of it: that is the traversal change on its
own, not the placements this pass carries. update_items keeps its reach, so the two exports clear
the choice where the placement actually is again.
A choice anchored inside a table cell's or a note's text therefore stays on the document and is
not carried into the embedded IDML copy, as on main; the refusal that turned that silent
difference into an export error goes with the traversal it guarded, since asking the walk's own
predicate it could never fire. The stale-field half keeps: has_applicable_pdf_layer_overrides
still asks for a graphic whose asset is a PDF, so relinking a placed PDF to an image no longer
makes the package report name a choice that does not exist.
Tests: the nested-placement regression and the relink integration test of the removed refusal
go; the parent-page regression, the stale-field predicate test and the format 4 cell-only reader
test stay. The comments no longer claim every placement counts.
Graphic gained an optional layer_options field, so the test-only literals in the PDF
crate and in the engine's optional-content support need it. No behaviour changes.
The workspace file guard scans test modules too when they are declared `pub(crate) mod` (it only
skips a plain `mod`), so a temp file written inside pdflayers.rs' tests failed
no_crate_touches_the_file_system_outside_the_checked_readers. The unit test now builds the state a
relink leaves behind - layer fields on an item whose asset is an image - and asserts the refusal
predicate ignores it while all three exports run. The user path (a real links.relink to a PNG) moves
to tests/pdf_optional_content.rs, where the guard exempts fixtures written by test files.
Comment-only: the refusal in both exports cannot fire while the walk and the predicate cover the
same placements, so the comments now say it is a tripwire for a future nesting construct and ask
nobody to narrow the walk to match it.
Document::update_placed_items called Arc::make_mut on every spread and story, and the story
walk on every table and note, so an export pass copied the whole document even when nothing
matched. A story/spread/table/note whose subtree holds no match is now skipped, as
Document::update_items already does.
Test: update_placed_items_leaves_untouched_spreads_and_stories_shared asserts the spreads,
parents and stories are still the same allocations after a pass that matches nothing, and
that a matching pass still reaches an anchored object inside a table cell.
The refusal guard asked whether any item still had the layer fields, while the export walks
also require a graphic whose asset is a PDF. Relinking a placed PDF to an image keeps those
fields on the item (links.relink replaces the asset in place), so a stale field blocked IDML
and PDF/A-2b exports that used to work. The guard, and the package report, now ask the same
question the walks ask: has_applicable_pdf_layer_overrides requires the PDF asset too, so a
stale choice is simply ignored and the tripwire still fires if a future construct ever puts a
real choice where the walk cannot reach it.
Regression: a_stale_choice_on_a_relinked_image_does_not_block_exports places a layered PDF,
hides a layer, relinks that asset to a PNG and exports IDML, PDF/A-2b and plain PDF - all
succeed, and the document reports the stale field while the applicable helper does not.
* 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
Kept separate from the parent-page fix and from the all-on export fix: this is the traversal
remediation on its own.
The detection helpers already walked parent spreads, groups and the stories in table cells,
notes and endnotes, while the two exports walked document spreads only, and the write side
(Document::update_items) did not reach placements nested in those stories either. So a choice
on a frame anchored in a cell's text or in a footnote asked for the newer file format and was
named by the package report, yet the IDML copy carried the file's defaults. The walk was
narrow on main as well (see the probe evidence), so this is pre-existing for the older
hidden-list state; it becomes a wrong claim only because the paired PR added the variants and
said every placement counts.
Document::placed_items recurses those stories (depth budget 64, like the detection side) and
a new Document::update_placed_items applies the change where the placement actually is,
copying only the branches holding a match. update_items is untouched, so unrelated commands
keep their reach. Both exports also refuse rather than fall back when a document has a choice
but the walk found no placement to carry it.
Regression: nested_placements_keep_their_layer_choices_in_saves_and_exports runs a placement
anchored in a table cell's text, then in a footnote: the save keeps the choice, the embedded
IDML copy shows every layer, external-only IDML is rejected, PDF/A-2b export succeeds.
An explicit all-on choice is Some(&[]) for the placed PDF, not the file's defaults, so the
item walk now passes it whenever the placement has a layer choice. Without that, a sheet
that inherits a placed PDF with "show every layer" exported the source's default-hidden
layers instead.
The second half is the fail-closed rule: prepare() skipped writing a group's state when a
visible, named layer needed no change, so an all-on choice on a file whose defaults already
show everything embedded the untouched source - no rewrite, so no error. It now writes every
group's state when a choice is explicit, which is the same catalog rewrite the canvas does,
so a history that cannot take one is reported instead of being embedded.
Regression: the two existing engine tests that were failing (placed_pdf_all_on_is_distinct
_from_defaults_and_survives_exports renders the exported page; explicit_all_on_validates_an
_actual_rewrite_before_editing expects the error for a 33-section xref history).
Gap 19 now states in one sentence that DesignCraft does not apply retained IDML
graphic-layer visibility to raster or PDF output, so it no longer reads as contradicting
placed-PDF layer choices, which are applied. The placed-PDF doc records that parent-page
and anchored placements get variants, and what a package's IDML copy cannot carry.
A package's IDML copy links the placed files, so a per-placement Object Layer Options
choice cannot travel in it, while the packaged DesignCraft document keeps the choice. The
report now says so instead of leaving the two copies to differ silently.
The graphic-layer test covered a mixed document. A save whose only layer settings sit in a
table cell has to choose version 4 too, asserted against the saved metadata.
A dialog opened without the menu's fields has no useDefaults field, and confirming it with
no layer unticked sent useDefaults:false with an empty hidden list: an explicit all-on
choice, which turns a PDF's default-hidden layer on and makes a PDF/A-2b export rasterise
the placement. An absent field now resets to the file's defaults when nothing is unticked,
and only an explicit false means all-on.
The IDML layer-variant pass and the PDF/A-2b rasterisation pass both walked only
document-spread items, so a choice on a parent-page placement (or an anchored one) was
replaced by the file's defaults on export, and an external-only IDML export succeeded
when it could not carry the choice. Both now cover every placement through
Document::placed_items, and the choice is cleared where the placement actually is.
Regression: a parent-page placement with an explicit all-on choice keeps all layers in
embedded IDML, external-only IDML rejects it, and PDF/A-2b rasterises it.
Graphic gained an optional layer_options field, so the test-only literals in the PDF
crate and in the engine's optional-content support need it. No behaviour changes.
Preserve contributor commits and integrate current main. Plain/PDF-X4 exports retain placed PDF optional-content defaults and hidden-name choices; prepared variants are cached, trailer history is parsed structurally, null OCProperties is treated as absent, and unreadable non-null Prev refuses safely. Preserve the recent form/table exporter refactors.
Verified exact integration tree with all nine cargo xtask ci gates (3,209 tests passed, 6 skipped; 16 WASM-safe crates), 118 original-head PDF tests, independent flat/null and Prev export matrices, and 12 qpdf checks. GitHub CI passed; fresh CodeRabbit review covers the exact head with no new actionable comments. Sonar workflow succeeded but its scan was skipped because the fork lacks SONAR_TOKEN.
This landing does not include or release the format-v4 pair #1647/#1671, held separately pending parent-placement IDML layer-choice preservation.
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.
layout_line set a line with a tab or a right indent tab without the optical
margin or CJK edge hangs, while the greedy breaker and the paragraph composer
counted them. A line that fit only because its period hangs was then squeezed
into the measure or set past it.
layout_line now hangs the edge glyphs of every line. Tabs are measured from the
hung start, so tab stops stay where they are, and a right indent tab fills to
the hung end. A tab at a line edge hangs nothing. Lines without tabs lay out as
before.