Commit Graph
195 Commits
Author SHA1 Message Date
aiongg 7cc6f6db72 Merge idml-deleted-text (update): deleted text inside nested paragraphs stays deleted; a deleted break doesn't break (#581)
Conflict: crates/idml/src/tests.rs: both sides' tests (#473's SameParaStyleSpacing tests, then the branch's deleted-text tests)
2026-10-12 17:25:31 +00:00
aiongg bfaebb76ee Integration: an explicit SameParaStyleSpacing Ignore survives a native save (#473)
same_para_style_spacing uses deserialize_explicit_value, so Some(None) round-trips through
serde. Test: SameParaStyleSpacing as a Properties element on the based-on Normal style closes
the gap between consecutive headings; SetIgnore keeps the usual spacing.
2026-10-12 17:18:26 +00:00
aiongg dad91f0c74 Merge #473: Honor IDML SameParaStyleSpacing between same-style paragraphs (#449)
Conflict: crates/compose/src/lib.rs: same-style gap replaces the pending space; otherwise next's text_above span space-before rule
Conflict: crates/doc/src/attrs.rs: same_para_style_spacing before the drop-cap fields and their doc
Conflict: crates/compose/src/tests.rs: both sides' tests
Conflict: crates/idml/src/tests.rs: both sides' tests
2026-10-12 17:16:10 +00:00
aiongg f8d9f3f3a3 IDML: deleted text inside nested paragraphs stays deleted
A paragraph range inside a tracked change takes the change into its character
attributes, so the mark holds for its character ranges however their format is
built.
2026-10-12 17:05:30 +00:00
aiongg 90890e37aa Compose: a deleted break doesn't break
A column, frame or page break character that tracked changes delete, or a
hidden condition hides, no longer sends the next paragraph on. Deleted
paragraphs between a live break and the next paragraph don't stop the break.
Tests cover a deleted paragraph range inside a change, a deleted frame break in
both nestings, and a live break before a deleted paragraph.
2026-10-12 17:02:46 +00:00
aiongg e2eae1c62b Merge idml-deleted-text: IDML: tracked deleted text takes no room in the layout (#581)
Conflict: crates/idml/src/import.rs: the branch's `over.change` line moved into next's `range_format()`, which takes the enclosing format for it (its CrossReferenceSource caller passes it too); next's `TextVariableInstance` arm kept beside the branch's `Change` arm, with next's ignore list
Conflict: crates/idml/src/export.rs: kept next's metadata, text-variable and CJK settings helpers and the branch's `change_type`
Conflict: crates/idml/src/tests.rs: kept next's `$ID/` font test and the branch's tracked-change test
2026-10-12 12:36:40 +00:00
aiongg f1a1f318ba IDML: tracked deleted text takes no room in the layout (#581) 2026-10-12 12:00:42 +00:00
MarekSpodBiedry 11b9a0f104 Export explicit SameParaStyleSpacing Ignore so it round-trips (#449) 2026-10-12 08:04:46 +02:00
aiongg 62520485e8 Integration: format 2026-10-12 05:48:15 +00:00
aiongg 64b834f2e0 Merge anchored-settings: IDML: anchored objects take unset anchor settings from their object style (#555)
Conflict: crates/idml/src/import.rs: next's with_style kept after attr_or_style (#555 has no counterpart there); the anchored arm merged as next's header (TextFrame, no has_text guard) with #555's body
2026-10-12 05:41:43 +00:00
aiongg af389f9a0b Merge clipping-paths: Images: clipping paths from IDML clip the image on screen and in PDF
Conflict: ROADMAP.md: both progress lines
Conflict: docs/gaps.md: next's header, clipping paths added to its change list
Conflict: docs/target-app-parity.md: next's header, clipping paths added to its change list
Conflict: crates/doc/src/item.rs: both Graphic methods (next's shown_bounds, the branch's clip_region)
Conflict: crates/idml/src/names.rs: both imports (ClippingKind, ContourType)
Conflict: crates/idml/src/export.rs: both pushes, TextWrapPreference first, then the clipping settings
Conflict: crates/idml/src/import.rs: both lets (next's wrap, the branch's clip)
Conflict: crates/engine/src/cmd/file.rs: next's size and transform, plus clip: None
Conflict: crates/engine/src/cmd/datamerge/fill.rs: next's Graphic with wrap, plus clip: None
Conflict: crates/engine/src/cmd/mathexpr.rs: next's Graphic with wrap, plus clip: None
Conflict: crates/pdf/src/tests.rs: both tests
2026-10-12 05:37:19 +00:00
aiongg b7b0001cce Integration: IDML AppliedFont keeps a family's spaces through font_family_in (#558 × next's $ID/ handling)
#558 keeps an AppliedFont family exactly as written apart from line breaks; next routes the value through names::font_family_in (for InDesign's $ID/ prefix), which trimmed it. font_family_in strips CR/LF and the $ID/ prefix and keeps spaces; a blank or $ID/-only value is still no font. #558's applied_font_keeps_spaces_and_font_style_nothing_inherits fails without this.
2026-10-12 05:36:23 +00:00
aiongg 8f60e6349b Integration: idml tests.rs: restore the closing brace of hostile_custom_wrap_boundary_is_dropped
The keep-both resolution of tests.rs in the #558 merge (0ff65578) put #558's test after next's last test without its closing brace, which git had shared between the two sides.
2026-10-12 05:36:23 +00:00
aiongg 0ff65578b0 Merge idml-font-names: IDML: font family names keep their spaces; FontStyle Nothing inherits (#558)
Conflict: ROADMAP.md: both progress lines
Conflict: crates/fonts/src/fontdb.rs: next's style_traits/traits_of/linked_style, canonical_family lookup, family_kind and postscript_face; #558's alternate_names (as written, then trimmed) now chains plain_family ($ID/ prefix and format suffix) and is the fallback in style_traits, face (canonical_family, then substitute_family) and has_family
Conflict: crates/idml/src/import.rs: #558's CR/LF-only stripping (next's f.trim() dropped), next's composite_names lookup (by the trimmed name) and names::font_family_in; #558's FontStyle Nothing → unset
Conflict: crates/idml/src/tests.rs: both tests
2026-10-12 05:35:36 +00:00
aiongg ab231cde62 IDML: a left-aligned anchored object's X offset reads in the layout's direction (#555) 2026-10-12 05:32:59 +00:00
aiongg e111bb74f7 Images: declare a graphic's clipping path beside its transform 2026-10-12 05:32:11 +00:00
aiongg 8125f2490b Images: clipping paths from IDML clip the image on screen and in PDF
A placed graphic keeps its clipping path (Detect Edges, Alpha Channel,
Photoshop Path or User-Modified Path) in graphic space, with the
options IDML carries. IDML import reads ClippingPathSettings and its
saved ClippingPathGeometry (anchors capped, non-finite subpaths
dropped); export writes them back. The canvas and PDF export clip the
image to the path inside the frame clip; an inverted path shows the
image outside it. Without saved geometry nothing changes.

object.clippingPathOptions reports a graphic's clipping path, inverts
it or removes it (Object > Clipping Path > None).

Fixes #450.
2026-10-12 05:31:03 +00:00
aiongg 25c215faf9 IDML: anchored objects take unset anchor settings from their object style (#555) 2026-10-12 05:20:12 +00:00
aiongg 9061475831 IDML: font family names keep their spaces; FontStyle Nothing inherits (#558) 2026-10-12 05:14:04 +00:00
aiongg 8d3b2b78da Merge upstream main f70f93de
Main's squashes of #510, #528, #420, #538 and #356 equal their PR heads; next has
all of them except the two maintainer merge commits a01bfcde (#538, ROADMAP only)
and 139e7c2e (#356: import-warning alert, malformed-URI assertion).

Conflict: ROADMAP.md: next's log lines (main's #538 line already in next)
Conflict: crates/doc/src/assets.rs: next's side (later same_file comment, Asset fields)
Conflict: crates/engine/src/cmd/datamerge/fill.rs: next's side (Asset with pdf_crop)
Conflict: crates/engine/src/cmd/file.rs: next's side (INDD description, Asset ..Default, crop box sizes)
Conflict: crates/engine/src/cmd/interchange.rs: next's side (INDD conversion warnings, resolve_pdf_crops, grep_style_warnings)
Conflict: crates/engine/src/cmd/links.rs: next's side (Asset ..Default)
Conflict: crates/engine/src/cmd/mathexpr.rs: next's side (Asset ..Default)
Conflict: crates/engine/src/cmd/media.rs: next's side (Asset ..Default)
Conflict: crates/engine/src/cmd/style.rs: next's side
Conflict: crates/engine/src/cmd/table.rs: next's side (Asset ..Default)
Conflict: crates/format/src/lib.rs: next's side (pdf_crop fields and tests)
Conflict: crates/idml/src/import.rs: next's side (linked_assets keyed by path, page and crop; CJK settings)
Conflict: crates/idml/src/tests.rs: both (main's file:/a%bé assertion added)
2026-10-12 01:57:47 +00:00
aionggandskylartaylor f70f93de44 fix(idml): report missing parts and show import warnings
Match IDML part names leniently, report missing parts, and show import warnings when opening a file. Cover the malformed URI crash with a regression test.

Validation: all six cargo xtask ci gates passed on the current main merge candidate; 1,176 native tests passed and all 15 WASM packages checked.
Hosted FreeBSD tests and Windows ARM64 packaging and install/run checks passed.

Task: #356

Co-authored-by: skylartaylor <26588250+skylartaylor@users.noreply.github.com>
2026-10-11 19:39:34 -06:00
aiongg 223dedfe5a feat(assets): deduplicate files and omit unused assets on save
Share duplicate file data while keeping distinct link paths independent, and leave unused assets out of saved documents.

Validation: all six cargo xtask ci gates passed on the merge candidate; 1,170 native tests passed and all 15 WASM packages checked. Independent regression checks cover updates, relinking, embedding, undo and save/reopen.

Task: #420
2026-10-11 19:23:58 -06:00
aiongg edaea097e8 Merge asset-dedupe: files with different paths stay separate assets (#420)
Conflict: crates/doc/src/assets.rs: same_file doc comment names both the PDF crop box (next) and the link (asset-dedupe)
2026-10-12 00:32:16 +00:00
aiongg 10546a6e66 Assets: files with different paths stay separate assets
Two linked files with identical bytes were merged into one asset, so updating, relinking or embedding one changed both placements and saves dropped one path. Assets now match only with the same link; identical bytes still share one buffer in memory.
2026-10-12 00:22:34 +00:00
aiongg d240ecb0b9 Merge idml-spread-rotation: IDML keeps a spread's view rotation
ROADMAP.md: both progress-log lines
crates/idml/src/tests.rs: next's test modules plus spread_rotation
docs/target-app-parity.md: next's lines; Rotate Spread names IDML's spread transform
2026-10-11 20:11:34 +00:00
aiongg a8fc6a5219 IDML: read and write a spread's view rotation (#484)
Rotate Spread is the rotation part of the Spread element's ItemTransform.
Import maps the three quarter-turn matrices to view_rotation on the spread's
first page and treats anything else as no rotation; pages and items keep
their unrotated spread coordinates. Export writes the rotation part for
document spreads; parent spreads stay identity.
2026-10-11 20:11:05 +00:00
aiongg a5b8d99164 Merge parent-margins: Layout: parent pages pass margins and guides to their pages
Conflict: crates/doc/src/page.rs: next's Spread::parent_page_for_side is the one parent-page choice (single-sided pages take the first page); the branch's parent_page_index is dropped and its IDML import caller (crates/idml/src/import.rs) uses parent_page_for_side
Conflict: crates/doc/src/build.rs: the branch's parent_page_of/parent_page/parent_guides/margin sync on next's parent_page_for_side; tests both
Conflict: crates/engine/src/cmd/layout.rs: test modules both
Conflict: crates/engine/src/tests_idml.rs: tests both
Conflict: crates/format/src/lib.rs: next's upgrade and duplicate-asset merge, then the branch's mark_differing_margins_local, in both loaders
Conflict: ROADMAP.md: both log lines
Conflict: docs/target-app-parity.md: next's snapping line plus the branch's parent-page guides
2026-10-11 13:12:06 +00:00
aiongg 1daf0a8ec8 Integration: custom wrap boundaries with graphic text wrap
wrap-boundary gives TextWrap a custom path (so it is no longer Copy) and drops it when
object.textWrap sets an offset or contour; next lets object.textWrap edit a placed graphic's
own wrap. The command drops the boundary of the wrap it edits, and next's graphic-wrap IDML
test clones the wrap it compares.
2026-10-11 13:11:02 +00:00
aiongg b6cc59d349 Merge wrap-boundary: IDML: import and wrap around custom text-wrap boundaries
Conflict: crates/compose/src/lib.rs: the custom boundary goes into next's shared wrap_exclusion(); free_slots keeps next's JumpObject handling outside it and takes the branch's combined BoundingBox/Contour arm
Conflict: crates/engine/src/cmd/object.rs: params doc has next's content flag and the branch's custom-boundary note
Conflict: crates/engine/src/tests.rs: both tests
Conflict: crates/idml/src/export.rs: the boundary goes into next's text_wrap_pref()
Conflict: crates/idml/src/import.rs: next's object-style-aware text_wrap() plus the branch's boundary parsing for the item's own wrap; pdf_page_in and wrap_path_of both kept
Conflict: crates/idml/src/tests.rs: both sides' tests
2026-10-11 13:08:12 +00:00
aiongg 470aac7d10 Layout: parent pages pass margins and guides to their pages 2026-10-11 13:02:23 +00:00
aiongg 47d17ca6f5 IDML: import and wrap around custom text-wrap boundaries 2026-10-11 12:53:50 +00:00
MarekSpodBiedry 30cbd67e3d Honor IDML SameParaStyleSpacing between same-style paragraphs (#449) 2026-10-11 11:43:38 +02:00
aiongg c94d7ce8c8 Merge mojikumi-base: CJK: mojikumi tables based on Nothing and the one-em line-end set apply their rules
Conflict: crates/compose/src/tests.rs: next's tests, plus the branch's one-em line-end test after its neighbour
2026-10-11 05:18:58 +00:00
aiongg b77ed8c300 Merge fix/idml-interchange: PDF: Bounding Box crops are measured from the page's drawing, not a render 2026-10-11 05:18:36 +00:00
aiongg 0437ab27f5 PDF: Bounding Box crops are measured from the page's drawing, not a render
Finding a placed PDF's Bounding Box crop rendered its page at 2048
pixels and took the bounds of the painted pixels. PDF export measures
every such crop that import left unmeasured, so exporting a 17-page
document that places one complex illustration took about 4 s longer
than before crops were supported. The box is now the union of the
page's painted paths (strokes included), glyphs and images, each cut
to its clip, collected by interpreting the page without rasterizing
it: the same box, exact instead of to the nearest pixel, in
milliseconds.

Test: content_crop_box_is_measured_without_rendering_the_page.
2026-10-11 04:56:32 +00:00
aiongg 45032b527f CJK: mojikumi tables based on Nothing and the one-em line-end set apply their rules 2026-10-11 04:38:04 +00:00
aiongg 54b9a4d314 Merge mojikumi-base: CJK: custom mojikumi tables without a base set apply their rules
# Conflicts:
#	crates/engine/src/tests.rs
2026-10-11 00:47:10 +00:00
aiongg 11631cde12 CJK: custom mojikumi tables without a base set apply their rules 2026-10-11 00:42:47 +00:00
aiongg ecdb9b2dc2 Integration: rustfmt after merging #389-#392, indd 8 and 9 2026-10-10 23:52:24 +00:00
aiongg cfd57cb3ca Merge asset-dedupe: Assets: one asset per image, and saves leave out unused ones
Conflicts:
- crates/engine/src/cmd/{datamerge/fill.rs, file.rs, mathexpr.rs,
  media.rs, table.rs}: the branch's add_asset calls, with next's Asset
  fields (pdf_crop, pdf_box) through ..Default::default(); file.place
  keeps next's PdfCrop-aware asset (crop_pdf) and adds it with add_asset;
  the data merge graphic keeps next's wrap field.
- crates/format/src/lib.rs: load runs next's upgrade(doc, version), then
  merge_duplicate_assets (zip and legacy JSON); tests from both, with the
  round-trip test placing its asset (saves drop unused ones).
- crates/idml/src/import.rs graphic(): the branch's linked_assets reuse and
  insert_asset, with next's PDF page and PdfCrop on the asset and the
  graphic's text wrap (#389 integration) in the shared graphic closure.
  linked_assets is keyed by (path, page, crop), so placements of one PDF
  showing different pages or crop boxes keep separate assets.

Also, so the merge compiles: Asset/Graphic literals in the branch's tests
(crates/doc/src/assets.rs, format, engine links.rs) take next's pdf_crop,
pdf_box and wrap fields.
2026-10-10 23:41:30 +00:00
aiongg 27312c1cf9 Integration: feathers inherit from object styles; merge closes its functions (#390)
The merge of idml-feathers left overlay() in crates/idml/src/import.rs and
imports_and_round_trips_text_wrap_side_and_contour_type in tests.rs
without their closing braces (both sides added code at the same place);
this closes them.

Test: feathers_come_from_the_object_style_unless_its_category_turns_them_off
(gradient and directional feathers come from the style through
with_style; EnableGradientFeather="false" in
ObjectStyleObjectEffectsCategorySettings drops the style's gradient
feather; the item's own Applied="false" wins).
2026-10-10 23:38:21 +00:00
aiongg 15bcbae754 Merge idml-feathers: IDML: gradient and directional feathers import and export
Conflicts:
- crates/idml/src/import.rs, free functions: next's effect_flag/overlay
  (#389) and the branch's finite/gradient_feather_in both kept. The
  feather reads sit inside the TransparencySetting block, which next reads
  through with_style, so object style feathers are inherited and their
  EnableGradientFeather/EnableDirectionalFeather category flags apply.
- crates/idml/src/tests.rs: tests from both.
2026-10-10 23:37:23 +00:00
aiongg 75ce3f88e0 Integration: placed graphics take text wrap from their object style (#389)
A graphic element that names an AppliedObjectStyle reads its
TextWrapPreference through with_style, so mode, side, offsets and
ContourOption come from the style where the graphic sets none. Test:
text_wrap_side_and_contour_come_from_the_style_for_frames_and_placed_graphics.

Also: swatch_ref's doc comment sits on swatch_ref again.
2026-10-10 23:37:01 +00:00
aiongg 06baf887fb Merge idml-style-effects: IDML: frames take corners, effects and text wrap from their object style
Conflicts:
- crates/idml/src/import.rs, style chain helper: next's object_style_chain
  (trims BasedOn, ends with [None], cycle-safe) kept; the branch's
  style_chain dropped. with_style and attr_or_style use object_style_chain.
- import.rs corners: next's corner import over the object style chain kept
  (per-corner then all-corners at each level, InDesign default radius);
  the branch's second corner pass dropped, so corners are read once.
- import.rs text wrap: with_style(e, "TextWrapPreference") feeds next's
  shared text_wrap() reader (side, ContourOption, offsets) instead of the
  branch's inline TextWrap build.
- import.rs TextFramePreference: next's chain-aware text_frame_options
  with column rule colour kept; BaselineFrameGridOption read through
  with_style.
- crates/idml/src/tests.rs: test modules from both.
2026-10-10 23:37:01 +00:00
aiongg daf1a0ccb3 Assets: one asset per image, and saves leave out unused ones
Placing a file that the document already holds reuses its asset (same
bytes, type and PDF page), in file.place, place.load, media and poster
placement, graphic table cells, math, data merge image fields, IDML
import (a linked file is read once) and paste/duplicate between
documents (which also maps ids that collide in the target).

Saving merges identical assets into the first and leaves out assets
nothing references; the open document keeps them for undo. Opening a
file saved with one copy per placement shares the identical bytes as
they are read and merges the assets. Package and Copy Links write only
used files, and links.list lists only used assets.

Refs #391
2026-10-10 23:28:24 +00:00
aiongg 505e748aac IDML: gradient and directional feathers import and export
Import reads GradientFeatherSetting (Applied, Type, Angle, Length,
GradientStart, first and last OpacityGradientStop) and
DirectionalFeatherSetting (Applied and the four widths) on page items.
Export writes both when the effect is on.
2026-10-10 23:21:17 +00:00
aiongg 5d872fbf1e IDML: frames take corners, effects and text wrap from their object style
A page item takes every value it lacks from its applied object style and
the styles that one is based on, attribute by attribute: corner shapes and
radii, TransparencySetting (blending, drop shadow, feather), text wrap,
text frame options, the baseline frame grid and frame fitting. Nested
elements merge level by level. Effects whose category the applied style
turns off come from the item alone.

Export writes the column rule attributes on every frame, so a frame keeps
its own rule settings over those of its object style on import.

Fixes #389.
2026-10-10 23:21:08 +00:00
aiongg 17c7bb3fd2 Merge contour-wrap: wrap both sides of an object and around its contour
Combines contour-wrap (wrap side, contour type, multi-segment lines) with
the placed-graphic and nested wraps already on next.

- crates/compose/src/lib.rs: exclusion() and the new graphic_exclusion()
  share wrap_exclusion() (offsets, spine sides resolved per page, contour).
  wrap_obstacles() builds every obstacle through them: the item's own wrap,
  the graphic's wrap around its shown bounds, and children of groups and
  pasted-into frames with their own transform (depth <= 64). Contour type
  Bounding Box follows the shown part of the graphic, other types the frame
  path. wrap_slot() returns all free segments (free_slots) and steps 1 pt
  at a time beside a contour; Jump Object stays in wrap_slot only. The line
  loop keeps grid gyoudori, Jump Object leading steps and grid snapping for
  a line's first segment, and carries the other segments (with the line's
  grid end) to the next lines; estimate_slots() lists every segment through
  wrap_slot(). hit_line() breaks ties between segments by x, used by hit()
  next to the drop-cap lookup.
- crates/compose/src/tests.rs: both sides' tests; the hostile-geometry
  test builds Exclusion with side and contour.
- crates/engine/src/cmd/object.rs: object.textWrap is text_wrap() with
  mode/side/contour (unknown values are errors) and content?, applied to
  the frame's or the graphic's wrap.
- crates/idml/src/import.rs, export.rs: ContourOption/@ContourType is read
  in the shared text_wrap() and written by text_wrap_pref() as a child of
  TextWrapPreference after Properties, so graphic wraps carry it too.
- crates/idml/src/tests.rs: both sides' tests. tests/wrap.rs gains a test
  of a contour, right-side wrap on a placed graphic shown in part.
- crates/ui-egui/src/panels/mod.rs: the Text Wrap panel's side and contour
  combos read the targeted wrap (tw), the graphic's when content is selected.
2026-10-10 21:39:26 +00:00
aiongg fbda023d7f Layout: wrap both sides of an object and around its contour
Lines beside an object that lets text through on both sides are set in
segments, left then right, and the paragraph runs on from one to the
next. One-sided wraps (left, right, spine sides, largest area) block the
other side. A contour wrap follows the object's outline band by band,
with the offset around it; graphic frames follow their contour type
(bounding box of the graphic, frame path, same as clipping; detected
edges, alpha channel and image paths follow the frame path). The Text
Wrap panel sets the wrap side and contour type through object.textWrap,
and IDML import and export keep ContourOption.
2026-10-10 21:28:03 +00:00
aiongg aa743eee88 Merge kanji-page-numbers: Kanji page numbers
Conflicts: engine layout.rs and notes.rs params docs (next's lists plus kanji).
2026-10-10 21:27:19 +00:00