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.
#469's Document Setup sets every page's and parent page's margins; next's parent-margins model (470aac7d) keeps pages marked local_margins out of later parent edits. After a Document Setup margin edit, pages that now match their parent page (margins and columns) lose the local mark, and parent margins are synced; pages with their own columns stay local.
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
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
Conflict: crates/doc/src/build.rs: next's parent_page_for (it calls Spread::parent_page_for_side); the PR's test kept
Conflict: crates/tools/src/snap.rs: shown_parent_page_index no longer exists (snap calls Spread::parent_page_for_side); the PR's test kept, calling parent_page_for_side on a two-page and a one-page parent
Conflict: crates/doc/src/page.rs (auto-merged, edited): parent_page_for_side picks the parent's first page for PageSide::Single, the PR's rule moved to where next makes the choice
Conflict: crates/doc/src/edit.rs: next's thread_frames (append_story); every rev += 1 is rev.wrapping_add(1)
Conflict: crates/engine/src/cmd/fonts.rs: next's replace (the local-formatting pass moved out of this hunk)
Conflict: crates/engine/src/cmd/text.rs: next's format_chars (apply_char_attrs, typing format); rev += 1 is rev.wrapping_add(1)
Conflict: crates/compose/src/tests.rs: next's baseline-fit and keep tests, then the branch's OS/2 offset doc and code-page constant
Conflict: crates/fonts/src/tests_vertical.rs: testing imports from both (with_base, with_code_pages)
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.
A font's em box is its BASE em box; else, for a CJK font (its font
menu group isn't Western: meta or name languages, CJK code pages, else
kana, Hangul or ideograph coverage), the OS/2 typo ascender when the
typo ascender and descender span exactly one em; else 0.5 em + cap
height / 2; else 0.88 em. Latin fonts with a one-em typo box, such as
DejaVu Serif, centre on the cap height.
IDML import found every Bounding Box crop by rendering the PDF page at
2048 pixels, once per placement, so opening a 17-page document that
places one Illustrator file 16 times took about 54 s of CPU. Import now
measures the box only for a graphic without GraphicBounds; drawing finds
it through designcraft_render::shown_box (cached per file, page and crop)
and PDF export through with_pdf_boxes.
A font's em box is its BASE em box; else it tops at the OS/2 typo
ascender when the typo ascender and descender span exactly one em; else
it is centred on half the cap height (OS/2 sCapHeight, else the H
glyph's top), 0.5 em + cap / 2 above the baseline; else 0.88 em. The Em
Box Height first baseline, vertical text and declared_em_box all use
this one em box; declared_em_box is None only for the 0.88 em fallback.
Asset::same_file compares pdf_crop (#362) with type, page and bytes: one
PDF page shown with two crop boxes keeps two assets, so add_asset,
adopt_asset, insert_asset (IDML/INDD import) and the load/save merge
never point a placement at another crop. Test in
identical_files_share_one_asset.
The copy clipping (edit.rs clip_doc) keeps the assets used_assets finds
in it, so graphics anchored in copied text, graphic table cells (#379),
notes and media posters keep their files; it collected only graphics on
the copied items before. Test:
copied_text_frames_keep_the_graphics_anchored_in_their_text.
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.
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
Conflict resolutions:
- crates/engine/src/cmd/layout.rs: next's start_number, chapter_numbering and
layer.move validation kept; the branch's set_layer added after them;
spread_rotation_input_tests and layer_options_tests both kept.
- crates/engine/src/cmd/style.rs: next's delete_tests and the branch's
swatch_options_tests kept as separate modules.
- crates/ui-egui/src/dialogs.rs: both module declarations (style_cjk,
options) and both confirm arms (deleteCharacterStyle, swatchOptions /
colorGroupOptions / layerSettings) kept.
- crates/ui-egui/src/i18n.rs, i18n/{ar,it,ja,pt_br,uk}.rs: both sides' rows
appended; no key was duplicated.
- crates/ui-egui/src/panels/layers.rs: #378's drag-and-drop row
(Sense::click_and_drag, deferred cmds, payloads) kept; the branch's
accessibility name, double-click -> app.layerOptionsDialog (after the
eye/lock/triangle/selection-square branches, before activate) and the
Layer Options for "<name>"... context entry added through cmds.
- crates/ui-egui/src/panels/swatches.rs: next's apply_swatch kept, with the
branch's single-click-only guard so a double-click opens Swatch Options.
Integration:
- Document::rename_swatch_refs also renames table cell stroke gap colours
(CellStroke and CellStrokeAttrs gap_color), kenten and ruby fill/stroke
colours (CharAttrs kenten_fill, kenten_stroke, ruby_fill, ruby_stroke)
and paragraph border gap colours (ParaAttrs border_gap_color).
- The swatch rename test covers a table's cell gap colour, kenten fill,
ruby stroke and border gap colour.
- The Layer Options double-click test checks that the double-click does
not reorder layers.
- German rows for the 29 new shared keys in i18n/de.rs.
- swatch.options: rename, Name with Color Value, process/spot, colour mode
and values, tint, in one undo step; special swatches keep their names,
and a taken or empty name changes nothing.
- Document::rename_swatch_refs points every use at a renamed swatch:
tints, colour groups, inks, paragraph/character/object/cell/table
styles, item fills, strokes, gaps, effects and column rules, text
colours, rules, shading, borders, table cells and the footnote rule.
- layer.set takes every Layer field (show guides, suppress wrap when
hidden), a layer colour name, and rejects empty or duplicate names and
unknown colours.
- Swatches panel: double-click or Swatch Options… opens the dialog;
colour groups get Color Group Options…. Layers panel: double-click or
Layer Options for "name"… opens Layer Options (app.layerOptionsDialog).
- table.setCell starts each chosen edge from the stroke rendering draws there (the winning
copy of a shared edge, the row/column pattern, or the table border) and writes the result
to both copies; table.getCellStroke reports drawn edges, counting each shared edge once.
compose::table::cell_side_strokes shares the resolution with the renderer.
- Commands accept dash patterns of 1-64 lengths, each 0 or 0.1-10000 pt. Screen and PDF
draw a dash or dot pattern that would repeat more than 20000 times along a path as solid.
- Stripes, wavy, hash and gap colour extend by half the weight at table corners.
- Cell Options: an edge change reseeds every stroke field and tolerates a non-object seed.
- Edge proxy: a triple-click decides from the choice before the first click.
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.
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.
Conflicts: compose lib.rs imports (both; Composer unused), idml tests.rs (both test modules), ui-egui
panels/mod.rs (next's ruler origin with the branch's graphic wrap for Direct Selection).
A right-to-left bound spread lists its right page first, parent spreads
included. The parent page shown behind a document page was picked by list
index (first = left), so every page of an imported right-to-left document
showed the other side's parent items. Pick it by position instead: the
leftmost parent page for a left page, the rightmost for a right page.
New parent spreads in a right-to-left document list their pages right
first, and changing the binding reorders existing parent spreads, so IDML
export writes them in binding order. Spread bleed on the canvas and in
spread PDF export takes the outer edges by position as well.
Kanji numbering style (一, 二, 三, …, written digit by digit: 10 is 一〇)
for sections, footnotes, endnotes and lists. IDML PageNumberStyle and
FootnoteNumberingStyle read and write Kanji and the leading-zero styles;
the list format "一, 二, 三, 四..." maps to it.
A placed graphic carries its own text wrap (Graphic.wrap). IDML import
reads TextWrapPreference on the Image/PDF element and export writes it
back there. Layout treats it as an obstacle around the part of the
graphic its frame shows, plus the offsets, and also collects wraps of
items inside groups. object.textWrap acts on the graphic when the
selection is the frame's content, and the Text Wrap panel shows its wrap.
Conflicts: fonts fontdb.rs (the branch's em box with its from-BASE flag beside next's ICF cache); idml
tests.rs (the branch's copies of tests already on next, and the mojikumi test #129 replaced, dropped;
the Em Box Height test kept). German entry for Em Box Height.
The first baseline sits the em box top below the frame's top inset: the
font's BASE em box (idtp, else ideo + 1 em), else 0.88 em, for the
largest size on the first line. IDML FirstBaselineOffset="EmboxHeight"
imports and exports as itself; the footnote options list it and the
frame and footnote option commands take emboxHeight.
Conflicts: ui-egui dialogs.rs (next's numberingSection arm and the branch's cellOptions arm; the branch's
copies of the dialog test helpers already on next dropped, its cell options test kept); idml tests.rs and
the i18n tables appended at the end on both sides (both kept; the branch's Gap Tint: and Tint: rows that
next already had dropped).
CellStroke and cell/table style edge attributes carry gap colour and gap tint
(IDML ...EdgeStrokeGapColor/GapTint, ...BorderStrokeGapColor/GapTint). The table
renderer and PDF export draw every stroke type on cell edges, with the gap colour
under dashes, dots and stripes.
table.setCell validates stroke values, takes gapColor/gapTint and an edges list
(top/left/bottom/right are the target range's outer sides; innerHorizontal and
innerVertical the lines between its rows and columns). table.getCellStroke reports
the shown values of the chosen edges, null where they differ.
Conflict: ROADMAP.md: next's FONT1-3 lines with the branch's CJK1 line
Conflict: docs/cjk.md: the branch's rows, with next's aki below and Japanese composer text kept in the leading model row
Conflict: crates/fonts/src/fontdb.rs: both fields (family/style aliases and the ICF cache)
Conflict: crates/compose/src/ruby.rs: the branch's ruby module; its PlacedGlyph (and kenten.rs's) carry next's rendered_char
Conflict: crates/compose/src/lib.rs: modules both; rtl and para_block both; the branch's grid placement (gyoudori, grid references) replaces next's snap, its paragraph-gyoudori span uses next's line_metrics with mark leading and #330's aki below advance; a wrap push moves the line by whole grid lines and its grid end with it; next's trimmed spaces and drop cap, then the branch's ruby and kenten annotation
Conflict: crates/compose/src/shape.rs: next's pieces loop and StyleRuns with the branch's auto tate-chu-yoko split (after marks join their base) and fit, and Upright mode in every shape_segment call; next's label fallback passes the mode on
Conflict: crates/compose/src/tests.rs: both sets of tests appended at the end
Conflict: crates/idml/src/export.rs: next's metadata and text variable writers, then the branch's CJK settings writers
Conflict: crates/idml/src/tests.rs: both tests
Conflict: crates/ui-egui/src/dialogs.rs: the branch's Ruby dialog default section (next's duplicate characterStyleOptions default dropped); next's section arms with the branch's style_cjk::section_ui