Cells and cell styles had no first-baseline setting, so every cell set its first line by the
Ascent offset (the font's typographic ascender). IDML documents that set FirstBaselineOffset on a
cell style (CapHeight is common, often through a table style's body region) composed every row
taller than the document asked for. With a font whose typographic ascender is a full em (Avenir),
each row grew about 2.6 pt and long tables overset, losing their last rows.
- doc: Cell.first_baseline / first_baseline_min; CellStyle.first_baseline / first_baseline_min,
applied by CellStyle::apply_to and carried through resolve_cell_style's BasedOn chain.
- idml: FirstBaselineOffset and MinimumFirstBaselineOffset read and written on cells and cell styles.
- compose: a cell's text frame uses the cell's first baseline offset and minimum.
- engine: table.setCell, style.cell.create/edit take firstBaseline / firstBaselineMin; table.get
reports them; style.cell.create fromSelection captures them.
- tests: import through BasedOn and a table style's body region, a cell's own override, IDML round
trip; composer cap height vs ascent and a fixed minimum; command parameters and validation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The conflict was in links.rs, where main's atomic relink work (b050343a) and
this branch's path gate both changed `update`. Kept main's mechanism (build the
replacement with relinked_asset, then insert it) and this branch's gate (a link
craft-docpath refuses is missing and never read).
relinked_asset now takes the path to read and the link to record separately.
Previously they were one argument, safe only because update passed the raw stored
link; passing the resolved path to the old signature would have rewritten every
relative link to an absolute one on each refresh.
Also passes None for the document in main's embedded-status test, where the asset
has no link so the document is irrelevant.
Found while testing crates/idml/src/import.rs:2748 (stroke_from: StrokeWeight, MiterLimit), also El::num in crates/idml/src/xml.rs:96 and the plain `.parse().ok()` calls around import.rs:1833, 2823 and 2832
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve 12 conflicted files. Where both sides changed the same code:
- format: keep main's bounded-read load_zip (MAX_ENTRIES, read_entry) and
this branch's u64 version reporting, so a version above u32::MAX is named
as written rather than clamped. Both sides had fixed the same truncation.
- render: main's natural_size supersedes image_natural_size (it already
reads an EPS bounding box and also handles PDF pages); callers in table
and data merge now use it.
- images: keep the DSC-aware header scan and (atend) trailer lookup, take
main's classic-Mac line endings, and carry its overflow guard into
parse_box. eps_proxy draws main's simple rectangles before falling back
to the placeholder, still over a retained EPS source.
- engine/file: take main's placement shape (add_asset, crop box geometry,
place cursor, finite_rect, object effects) and keep this branch's
validate_placement in both branches; edit() works on a clone and commits
only on Ok, so validating inside it stays all-or-nothing.
- engine/links: keep building the replacement before edit() so a file that
cannot be read leaves the document untouched, and move main's crop_pdf
refresh into relinked_asset, which both relink and update go through.
- engine/export: EPS preview warnings join the font-embedding warnings
rather than shadowing them.
- idml: an EPS element keeps ActualPpi (its pixels are a preview raster);
main's PDF exclusion is unchanged.
Cargo.lock regenerated. Tests and doc strings from both sides kept.
Links, data sources, IDML package links and book chapters go through
io::document_path (craft-docpath) before they reach the file system:
relative paths resolve beside the document, and paths to other computers,
devices and URLs are missing, with the reason in links.list and Preflight.
IDML and INDD packages read a linked file where its link points on this
computer, then from the link's trailing names beside the package, only
when it is a regular file up to 256 MB holding an image or a graphic. A
file URI that names a host keeps it as a network path.
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.
crates/engine/src/dtd.rs: keep the branch's version; #319 already bounds DTD group nesting (with the same limit of 64) along with the entity budget and matching fixes, so this PR's DTD part is dropped. The XML element limits are taken.
crates/idml/src/import.rs: keep the branch's strict Mojikumi row reader (#129), which refuses a non-finite value; take the PR's composite-font filters.
crates/idml/src/tests.rs: the PR's test drops its Mojikumi NaN row, which #129 refuses on purpose.
crates/idml/src/lib.rs: crate doc keeps the branch's feather list and adds liquid guides.
crates/idml/src/tests.rs: keep all test modules from both sides.
docs/file-format-parity.md: keep both change-log rows; status line names both changes.
ROADMAP.md: drop the blank line the union merge left inside the progress log, and a stray one further down.
next writes the document's ruler origin (Spread, Page or Spine) to IDML; main
measured guide locations from the spread's left edge, as for SpreadOrigin only.
A page-origin document's guides came back one page width to the right. The
round-trip test covers all three origins.
Each included style gets pageNumberStyle, separatorStyle and sort.
toc.generate and toc.update apply the character styles to the page
number and the separator of every entry, and list the entries of a
sorted style alphabetically among their siblings, each with the
lower-level entries under it. Unknown character styles are errors.
Renaming or deleting a character style updates the definition. IDML
reads and writes PageNumberStyle, SeparatorStyle and Sort.
Composite-font and mojikumi metrics were taken from the file with no
sanity check, and Document::check rejects a zero, NaN or infinite
relative_size / horizontal_scale / vertical_scale / baseline_shift — so
one attribute anywhere in the package (RelativeSize="0",
BaselineShift="NaN") failed the import with "invalid composite font
metrics" and left the file unopenable. Every neighbouring number in this
importer already filters; the metrics now do too and fall back to a
usable value.
Regression test: unusable_cjk_metrics_do_not_refuse_the_whole_file —
fails on main with Invalid("invalid composite font metrics: Rubies"),
passes with the fallback.
Keep GuideType=Liquid on document and parent pages, with literal XML and
mixed-guide regression coverage. Leave object liquid constraints separate.
Co-Authored-By: Codex <codex@openai.com>
The DOCX and IDML element-tree builders accepted any depth, and the
tree is then walked and dropped recursively; a DTD content model's
groups recursed without a budget. Each parser now refuses nesting past
a fixed budget with an error, before any recursive consumer runs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011K1peyrh5MdZ6ttHkVq8Gh
Conflicts:
- crates/doc/src/build.rs: kept parent_page_of/parent_page/parent_guides,
sync_parent_margins and mark_differing_margins_local; inherited_guides(abs)
stays as a wrapper over parent_guides for the guide commands and dialogs.
Kept main's single_sided_page_shows_first_parent_page test next to
differing_margins_from_older_files_stay_local.
- crates/doc/src/page.rs (auto-merged): Spread::parent_page_index takes main's
rule (first parent page for left and single-sided pages).
- crates/engine/src/cmd/layout.rs: the parent block uses sync_parent_margins
instead of main's old-margins equality check; parent accepts prefix, label or
index; document pages set here become local. Main's
parent_margins_change_the_parent_and_follow_unoverridden_pages stays; added
applying_a_parent_gives_its_margins_and_local_margins_stay_local.
- crates/tools/src/snap.rs, crates/ui-egui/src/canvas.rs: guides read through
parent_guides (no per-page Vec); main's current_spread_page and
current_parent_page kept.
- crates/ui-egui/src/panels/properties.rs: main's parent-target margin fields.
- crates/format/src/lib.rs: upgrade and merge_duplicate_assets, then
mark_differing_margins_local.
- crates/engine/src/tests_idml.rs: both sides' tests.
- ROADMAP.md: main's file plus this PR's progress-log line.
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.
Conflicts:
- crates/compose/src/lib.rs: kept main's line placement (line_metrics with first
baseline tops, baseline grid snap) and this branch's leading model and Aki Below
advance. The Hyphenation Zone check uses Composer::single_line(), so the Japanese
single-line composer gets the zone too.
- crates/idml/src/tests.rs: kept both tests at the conflict (composer kinds, text
stroke alignment).
Also resolved:
- crates/idml/src/import.rs: removed the package scan for a Japanese composer and the
drop of [No paragraph style]'s leading model; the import keeps the file's leading
model and the composer kind, and compose applies the model per paragraph.
- crates/idml/src/tests.rs: the default-leading-model test lays out the document and
checks Roman spacing under the non-Japanese composers and the Aki Below spacing
under the Japanese ones; a new test round-trips a Japanese composer document.
Guides in an IDML page (<Guide> children of <Page>) were dropped on import,
and DesignCraft's own guides weren't written. Montilla Design's Flex Layout
file keeps its 16 column guides on the B-Parent page; none came in.
Import: Location is measured from View Preferences › Ruler Origin (the
spread's top left, the page's, or the spine); Orientation, FitToPage (off:
a spread guide), Locked and ItemLayer are kept. Export writes each page's
guides under its <Page> in spread-origin coordinates, which is the ruler
origin DesignCraft writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InDesign writes LeadingModel="LeadingModelAkiBelow" on [No paragraph
style] of every document, Western ones included, but only its Japanese
composers use the leading model. DesignCraft applied it to every paragraph,
so a 24 pt paragraph after a 32 pt one sat 34.53 pt below it instead of the
Roman 32.8 (4 pt space after + 28.8 auto leading) in Montilla Design's
Flex Layout exercise file, and its fitted captions came out overset.
The import now drops that default when no part of the package applies a
Japanese composer (HL Composer J / HL Single J). Leading models set on
text, other styles, or in documents that use a Japanese composer are kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A placement of a multi-page PDF carried no page number through IDML: graphic()
always made the asset with page 0 and keyed linked_assets by path alone, so
every placement of one file showed page 1 (InDesign's PDFAttribute PageNumber
was ignored). Import now reads PDFAttribute/@PageNumber (1-based) into
Asset.page, linked_assets is keyed by (path, page), and export writes
PDFAttribute back for pages past the first.
Regression test: the same file placed at pages 2, 1 and 2 imports as two
assets with pages 1 and 0 and round-trips through export.