crates/ui-egui/src/dialogs.rs: the branch's dialog body is laid out differently; the Color Theme body goes into its match beside Text Frame Options, and its OK label into the branch's translated labels.
crates/ui-egui/src/menus.rs: keep the branch's Effects dialog opener and add the Color Theme opener.
crates/ui-egui/src/panels/swatches.rs: keep both menu rows (Swatch Options… from #416, Color Theme…).
crates/tools/src/tests.rs: the PR's Graphic literal names clip, wrap and fill.
docs/agents.md: keep both sections.
ROADMAP.md: the PR's line moves under "Newest first."; its copy of an existing 2026-10-13 line is dropped.
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.
Conflicts:
- crates/engine/src/cmd/text.rs: took #1000's unit_at split of select_at; its two revision bumps use the batch's bump_revision().
- crates/tools/src/text.rs: kept the batch's preedit and table-border fields, BorderDrag and the press-time border check; #1000's `unit` replaces `unit_selected`, and the drag branch checks the border first, then #1000's unit drag.
Conflicts:
- crates/engine/src/cmd/text.rs: kept the kern_pair_tests module and added pick_up_format_tests after it.
- crates/tools/src/xform.rs: the one hit test keeps its spread (`hit` is the whole `cx.hit` result) and feeds both color.sample's matchFrom and type.pickUpFormat.
- ROADMAP.md: progress-log line moved under "Newest first."
A click with the loaded text cursor on a text frame inside a group showed the
not-allowed cursor and did nothing: `Document::hit_item` walks the spread's
top-level items, so the hit was the group, and the group is not a text frame.
`item_hit` already looks inside a group, but only to answer whether the group
was hit at all -- the frame in it was never named. InDesign threads into it.
`hit_descendant` names it: the frontmost descendant under a point that a
predicate accepts, through nested groups, front to back and skipping hidden
children. `ToolContext::hit_text_frame` asks for a text frame, and the thread
tool resolves its target through it, falling back to the plain hit so a click on
anything else means what it meant before. `hit` is untouched, so selection still
picks the group.
One fix covers the cursor and the click, since `click` resolves through
`target`. The test asserts the cursor, that the click threads to the frame and
not to the group, that `hit` still returns the group, and that a group holding
nothing threadable is still refused.
Double-clicking selected a word, but holding the button and moving did
nothing. `text.extendTo` now takes `unit`: after a `text.selectAt`, the
selection grows by whole units from the unit first selected, in either
direction, as InDesign does. The Type tool sends the unit its clicks
chose with every drag.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011K1peyrh5MdZ6ttHkVq8Gh
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/tools/src/lib.rs, crates/tools/src/pen.rs and
crates/ui-egui/src/cursors.rs (add/add). The cursor work already in main is
kept as is (canvas.rs, ui-egui/src/lib.rs, tools tests.rs, ROADMAP.md take
main's version).
- pen.rs: the end-point check moves into hover_at as Hover::Continue, ahead
of Delete and Add; Down has one Continue arm and cursor() derives from
hover_at alone, so over a selected open path's end point the click
continues the path instead of deleting the anchor. Continuing from either
end (continue_from) is main's. The module doc is complete again.
- cursors.rs: Badge::Continue, the PenContinue glyph, and PenContinue in the
glyph test list.
- tests.rs: pen_over_a_selected_open_paths_end_point_continues_it.
In InDesign, selecting text and clicking other text with the Eyedropper
copies that text's character and paragraph formatting onto the selection
(Typefool's grid tutorial does it at 12:04 to give body copy the header's
settings). DesignCraft's Eyedropper always ran object.matchAttributes,
copying the clicked frame's fill and stroke onto the text frame.
New command type.pickUpFormat {frame, point}: the character format
(style + overrides) under the point goes to the selected characters, its
paragraph's style and overrides to their paragraphs, in one undo step.
The Eyedropper sends it when text is selected and a text frame is clicked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tool cursors are glyphs drawn in code (black ink, white halo): the filled
arrow for Selection, the hollow arrow for Direct Selection, the pen nib
with an add, delete, close or convert badge, and a curved double arrow
for rotating. Linux and Windows show them as OS cursor bitmaps,
rasterized per pixels-per-point and cached; macOS paints them at the
pointer, since it sizes cursor bitmaps in points; the web build keeps
the system cursors. Text, hand, zoom and resize cursors stay system
cursors.
The Pen tool reports what a click does: over the first anchor of the
path being drawn it closes the path, and between paths a click on a
selected path's anchor deletes it and a click on its outline adds one.
The Add, Delete and Convert Direction Point tools show the matching pen
badges.
The glyph geometry and the rasterizer are adapted from VectorCraft's
code-drawn tool cursors (same organisation, original work).
Fixes#438
Add multi-click text selection, pending caret formatting and clipboard support, text-aware colour controls, guide/grid snapping, and frame threading with loaded-port interactions.
Include the layout, drop-cap and IDML updates carried from #83, with safeguards for generated and linked stories, external text references, local IDs, locked frames and atomic colour edits.
Fixes#23.
Co-authored-by: Sean Hammond <sean.hammond@und.edu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chris Cicchino's "Adobe InDesign – Page Tool and Liquid Layout" (6:32–
9:58) clicks an object with the Page tool on an object-based page and
gets InDesign's on-canvas liquid controls: a pin outside each edge
("Object is pinned relative to the page edge. Click to release.") and a
padlock on the width and height lines ("The object's width is locked.
Click to let the object change width as the page changes width."). The
pins and resize settings existed (liquid.object, the Liquid Layout
panel) but the canvas showed none of it.
- tools: on an object-based page a Page tool click selects the object
with its page (layout.selectPage takes `ids`); its pins (14 px outside
each edge's middle) and width/height locks (beside the centre) toggle
liquid.object on click, show InDesign's tooltips on hover and a hand
cursor.
- canvas: LiquidPins (orange dashed width/height lines with padlocks,
open when the object follows the page; hollow pins, or a line to the
page edge when pinned) and Tip overlays; tooltip translations.
Builds on the Page tool PR (#862).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chris Cicchino's "Adobe InDesign – Page Tool and Liquid Layout" (0:42–
2:06) selects a page with the Page tool, gets its bounding box with
handles, drags a handle (the page springs back: a liquid-rule preview),
holds Option to really resize it, Shift for proportions, and reads the
size in a tooltip by the cursor. DesignCraft's Page tool only opened a
generic Page Size dialog on click.
- tools: the Page tool selects the page under a click (none on the
pasteboard); the selected page shows its outline, eight handles and
its liquid page rule ("Scale", "Object-Based"…) above it. Dragging a
handle previews layout.pageSize (the liquid rule re-flows the
objects) and springs back on release; Alt resizes, Shift keeps the
proportions; a W/H readout follows the pointer; resize cursors on the
handles. Double-click a page for the Page Size dialog.
- doc: Selection.page (the Page tool's page); engine: layout.selectPage
(no undo step).
- layout.pageSize `anchor` (0-8): the page grows away from the
reference point and its objects and guides keep their place relative
to it (a handle drag anchors the opposite edge); liquid rules work
from that.
- canvas: PageHandles and Tag overlays; translations for the tags and
the Resize Page undo label.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a placed graphic selected (Direct Selection, or the content
grabber), the graphic's own brown bounding box has eight handles:
dragging one resizes the image inside its frame (Shift keeps its
proportions, Alt from its centre) and leaves the frame as it is.
- tools: a content-handle hit test on the selected graphic's bounds
(after the frame's anchors under Direct Selection), a ContentResize
drag previewing `transform.set {content, bounds}`, the handle cursors,
and the content ghost while dragging. The frame's handles don't resize
the frame while its graphic is selected.
- engine: `transform.set` takes `bounds` for content: the graphic's new
bounding box on the spread (normalized, never empty).
- canvas: the brown content box gets its handles; the frame's handles
are hidden while the graphic is selected.
(cherry picked from commit 8e0e6c2f2e)
As in InDesign, the Eyedropper applies a colour sampled from a placed image
to the fill or the stroke, whichever is in front in the toolbox: with the
Stroke box in front a selected line takes it on its stroke (it has no
fill to show it). The toolbox's state reaches tools through
ViewInfo/ToolContext::stroke_in_front.
Depends on #572.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cycle checks walk based_on with a seen-set over all parents, so a cycle
longer than the drawing depth cap is still found. Snapping lists parent
items through shown_parent_pages instead of recursing, and offers base
items on a parent spread. A base parent's text on a parent spread is
composed for that spread, so page-number markers show its prefix.
Cmd+Shift-click hits what is drawn: it honours Show Parent Items, hidden
and locked layers, the facing-page filter and layer order. IDML import
warns when a parent spread's pages name different bases or their own
spread.
As in InDesign, the Eyedropper applies a colour sampled from a placed image
to the fill or the stroke, whichever is in front in the toolbox: with the
Stroke box in front a selected line takes it on its stroke (it has no
fill to show it). The toolbox's state reaches tools through
ViewInfo/ToolContext::stroke_in_front.
Depends on #572.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selected type on a path shows its start and end brackets across the path,
as in InDesign, and the Selection and Direct Selection tools drag them
along it (Move Bracket, one undo step): the text starts and ends where
they are, and text past the end bracket is overset.
- doc: PathType.end (None: the path's end)
- geom: PathWarp::nearest, the arc length of the nearest point
- engine: type.pathOptions {end}; brackets stay on the path and can't cross
- compose: the line runs from the start to the end bracket
- idml: EndBracket read and written
- tools: Cursor::PathBracket
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As in InDesign, the Pen over an end point of an open path (selected, or
under the pointer) shows a continue cursor; clicking it carries on drawing
that path, new anchors going on its subpath, and clicking its other end
closes it. Dragging from the end point first reshapes its direction
lines. From the first anchor the subpath is reversed first.
- engine: path.reverse (Object > Paths > Reverse Path), path.setAnchor;
path.appendAnchor and path.close take a subpath, and appendAnchor
refuses a closed subpath
- tools: Cursor::PenContinue
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict: crates/engine/src/cmd/object.rs: object.fit's doc keeps next's text and adds the `axis` clause; test modules from both sides
Conflict: crates/ui-egui/src/canvas.rs: kept next's input code (it sends the second press as Down with clicks: 2) and dropped the PR's release-time DoubleClick; the PR's UI test stays
Conflict: crates/tools/src/select.rs: the handle double-click joins next's second-press preamble, after the port checks and before editing text and the frame/content toggle (Down or DoubleClick with click_count 2 on a handle runs object.fit along fitting_axis); the DoubleClick arm stays empty; module doc from both
Conflict: crates/tools/src/tests.rs, ROADMAP.md: both sides
color.sample takes `matchFrom`: with no opaque image pixel under the point (an empty part of an image frame, a transparent pixel), it applies that object's attributes as object.matchAttributes does, in the same command; the tool passes the object under the pointer. sample_item and the tool walk shown_children(), so only a multi-state object's active state is sampled, and sample_item skips points outside the graphic's clipping path. object.matchAttributes's body is a function both commands share.