#550 places a selected graphic at x/y from its page's left edge; next measures
transform.set x/y from the rulers' zero point (view.zeroPoint, ruler origin), and the
Transform fields show a graphic's bounds from that point. transform_content now targets
x/y from the same origin as the frame path, so a typed value lands where the fields
say it is.
Conflict: crates/engine/src/cmd/object.rs: transform.set doc string joins next's "measured from the rulers' zero point" with the PR's `content?`; test modules: kept next's copy_ids_tests/tint_alone_tests and the PR's content_transform_tests
Conflict: crates/ui-egui/src/panels/mod.rs: kept next's swatch_list_tests and the PR's content_selection_tests
Conflict: ROADMAP.md: kept both progress-log lines
Conflict: crates/ui-egui/src/lib.rs: import_file takes the PR's `styles` early return first, then next's content-sniffing `layout` line
Conflict: ROADMAP.md: kept both progress-log lines
Styles could not be taken from one document into another; a client's
styles had to be rebuilt in every new file. The Paragraph Styles and
Character Styles panels now have Load Paragraph Styles, Load Character
Styles and Load All Text Styles: pick a .designcraft or .idml file,
tick the styles to load and choose what happens to one the document
defines differently (redefine, keep, or load under a new name). The
styles, swatches and lists a loaded style names come along.
`style.load` does it in one undo step.
With a placed graphic selected, transform.set acted on the graphic but
the Control bar and Properties still read the frame's values: after
setting the image of a -45° frame to 0°, a spinner step sent -44, and a
200 pt frame holding a 400 pt image showed W 200, so 201 halved the
image. transform.info now reports the graphic's values at the top level
in that case, and sel_info gives the graphic's bounds.
Also: `id` names the target explicitly, like `ids`, so it isn't taken
as a content selection; and a content selection of locked frames
records no empty undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a frame's content selected (Direct Selection, or selection.set
{content: true}), transform.set still rotated, scaled and moved the
frame. It now transforms the placed graphic inside the frame and leaves
the frame alone, as InDesign's Transform panel does: rotation, shear and
scale are absolute and measured as transform.info reports the content;
x, y, width and height are the graphic's bounds on the page. A
`content` parameter picks either explicitly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ase::read accepted any number of colors in a swatch exchange file, and
swatch.load looked up each incoming name with a linear search through
the document's swatches. The time to load a file grew with the square of
its number of colors: in a test build, 50,000 colors used 2.5 s of CPU
and 100,000 used 9.6 s. No limit applied to larger files.
ase::read now returns an error for a file with more than 100,000 colors
(ase::MAX_COLORS). load_swatches looks up each name's first swatch in a
HashMap, and each color added from the file is indexed too. A file of
100,000 colors now loads in under 0.1 s. The merge follows the same
rules as before: names already in the document are kept unless replace
is set, a name repeated in the file matches its first copy, and a
locked swatch is left unchanged.
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
Conflict: ROADMAP.md: both progress lines (next's, then #556's)
Conflict: crates/compose/src/shape.rs: #556's empty-marker zero-width glyph; next's digit mapping, styles.at(i)/styles and sub.upright() in both arms
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.
#84's Add to Swatches renamed the colour in footnotes and endnotes through its own helpers; next's Document::rename_swatch_refs (#416), which add_to_swatches calls after the merge, covered footnotes but not endnotes. rename_swatch_refs now walks each story's endnotes too, and #84's test checks an endnote.
#84 gives autoflow the id of the command that autoflows, for its errors; #266's odd/even page-break test called autoflow with the old three arguments. The test passes "file.place".
Review fixes for text editing, paste and threading (fix84-text,
fix84-thread), the Italian/Japanese/Portuguese/Ukrainian Swatches hint
and #83's update merged into the PR branch.
Conflict: ROADMAP.md: header combines #84's threading change with next's 2026-10-11 changes; progress lines and change-log rows both (#84's 2026-10-12 first); stage line #84's (alpha ~5–7 h away)
Conflict: docs/gaps.md: #84's header text plus next's kerning/fonts/print changes
Conflict: docs/roadmap.md: next's M1 row (#186 done), #84's M2 row
Conflict: crates/engine/src/cmd/style.rs: next's side; add_to_swatches calls next's Document::rename_swatch_refs, #84's rename_in_story/rename_in dropped (next's covers text, tables, cells, notes and items); #84's test kept
Conflict: crates/idml/src/tests.rs: next's side (#84 carries #83's tests.rs, already merged in 9196a164)
Conflict: crates/tools/src/select.rs: overlays keeps next's content-move ghosts and live corners, plus #84's snapped loaded-cursor drag frame; a Place click without a drag shows nothing
Conflict: crates/ui-egui/src/i18n/it.rs: next's side (the Swatches hint entry is already further down)
#403 added validate_char_fill with its own message; #520 added known_swatch for object fills and strokes, which also suggests the intended name. validate_char_fill now calls known_swatch with key "fill", so one error path covers both. #403's tests still pass (the message names the fill, swatch.create and the returned name).
Brings #514 (MCP request replay), #523 final (already in next), #403
(character fill must name a swatch), #520 and #521 finals (already in
next as their PR heads).
Conflict: crates/engine/src/cmd/object.rs: next's side throughout (transform.move doc with content?, object.fill with tint alone from #525, move_content, reorder/content_move/group/tint_alone test modules); upstream's copy_ids_tests already in next
Conflict: crates/engine/src/cmd/text.rs: both; #403's validate_char_fill after next's ruby_cmd
Conflict: crates/pdf/src/export.rs: next's side (item/item_guarded cycle guard, frame_objects; upstream's anchored loop dropped as in d11ccad3)
Conflict: crates/render/src/lib.rs: next's side (drawing_items, draw_item/draw_item_inner)
Return ordered item IDs from Move, Duplicate and Step and Repeat while preserving the existing moved and created counts, selection and undo behavior. Scripts can use the IDs to operate on copies.
Validation: eight independent probes covering both swatch validation and returned IDs, plus all six cargo xtask ci gates on main after #520 (1,188 native tests passed, 3 ignored; all 15 applicable crates passed WASM compilation checks). Both ROADMAP entries and command regression modules are retained.
Task: #521
Autoflow fell back to the last document page when the story's last
frame was on a parent spread, threading a parent frame to new document
pages. It now refuses a story that ends on a parent spread.
Reject unknown object fill, stroke and gap swatch names before editing the document, and suggest an existing name where possible.
Validation: three independent atomicity and compatibility probes, plus all six cargo xtask ci gates against current main (1,187 native tests passed, 3 ignored; all 15 applicable crates passed WASM compilation checks). ROADMAP progress entries are retained.
Task: #520
Conflict: ROADMAP.md: took #83's (main's restructured roadmap); the Threading inventory line goes into docs/target-app-parity.md
Conflict: crates/compose/src/lib.rs: kept this branch's undrawn trailing spaces after warichu; comment covers both the spaces and the drop cap
Conflict: crates/idml/src/tests.rs: took #83's (drop cap style as a Dropcap nested style, table style fills through the cascade); this branch has no changes of its own there
Reject unknown character fill swatches before modifying text or undo history. Registered swatch names remain valid, including names that resemble hex colours.
Validation: invalid-name atomicity and save/reopen regressions, plus all six cargo xtask ci gates on main after #514 and #523 (1,186 native tests passed, 3 ignored; 15 crates passed WASM compilation checks).
Task: #403
Parent-page text now uses the absolute page index as its cache key, so repeated page numbers and renumbering do not reuse another page's composition.
Validation: targeted canvas and PDF regressions, plus all six cargo xtask ci gates on main after #514 (1,179 native tests passed, 3 ignored; 15 crates passed WASM compilation checks).
Task: #523
Conflict: ROADMAP.md: took main's restructured roadmap; this branch's inventory changes (Keep Lines Together default, IDML defaults, drop caps, IDML Kashidas) go into docs/target-app-parity.md, where the inventory lives now
Conflict: crates/format/src/lib.rs: load runs the format upgrade, then merges duplicate assets; kept both sets of tests
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>
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
* Place: an image placed into a frame fills it once and leaves Auto-Fit off
* Place: the fitting test names its fitting, since a placed image no longer auto-fits
A CMYK component of 1 or less was read as a fraction even when the other
components were percentages, so {"c": 0.7, "m": 0, "y": 12.2, "k": 0}
(a pale cream) became C 70 % Y 12.2 % and exported as
`0.7019608 0 0.12156863 0 k`. The same applied to RGB arrays.
Now a colour is read as percentages (0..100, or 0..255 for RGB) when any
component is above 1, and as fractions only when every component is
within 0..1.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Conflict: crates/engine/src/cmd/print.rs: the PR's print_with/craft_print::submit; print_with and the test's expected bytes export through next's interchange::with_pdf_boxes
Conflict: Cargo.lock: next's lock plus only craft-print (the auto-merge had moved windows-sys back to 0.59/0.60 for several crates)
Conflict: Cargo.toml: craft-print entry moved beside craft-ui with a comment
Conflict: ROADMAP.md: status Change texts combined; both progress-log lines (the PR's 2026-10-11 line under the 2026-10-12 ones)
Conflict: docs/gaps.md: status Change texts combined
Conflict: docs/hardware-parity.md: status Change texts combined
Conflict: crates/engine/src/cmd/text.rs: next's selection_attrs (mixed, typing format, mixed_para_attrs, link_style) with the PR's selectedText added to its output; both parameter descriptions
Conflict: ROADMAP.md: both progress-log lines
Conflict: crates/engine/src/cmd/object.rs: object.fill takes the PR's swatch?/tint? form with next's known_swatch check (#520) on a given swatch; params doc names the swatch format; both test modules
Conflict: crates/ui-egui/src/i18n/it.rs: both sets of rows
Conflict: crates/ui-egui/src/panels/properties.rs: swatch_menu keeps the PR's popup (Apply To, Tint) with next's swatch_list for the swatches (its own width); tests keep next's Pathfinder test and the PR's three
Conflict: ROADMAP.md: both progress-log lines
Conflict: crates/engine/src/cmd/table.rs: next's setCell (StrokeEdit/EdgeSel, edit_table with ID) with the PR's text_param for the cell text