A sample now shares one work limit, counted in objects looked at, across
hit tests, pattern tiles and the tile or symbol art it places for hit
testing, and gives no color when the limit runs out. Objects that give
no color are skipped by id instead of cloning the document for each
retry, each nesting level reuses one scratch document that holds only
the symbols, and patterns and images are looked up in the sampled
document. The sample runs on a deep stack and follows art inside art at
most 16 levels deep.
A symbol instance is now sampled as it draws: its art is stained by the
instance's fill and placed by the instance's transform without moving
the patterns in it or scaling its strokes. Between the shapes of its art
the sample shows what lies below instead of the stain, and a pattern
tile holding an instance is sampled wherever the symbol's art reaches.
Shift-click with the Eyedropper sampled solid colors, gradients and
images only; on a pattern fill or a gradient mesh it sampled nothing.
- A pattern gives the color of the front-most tile art at the point,
placed by the pattern's transform, at any tile size. Tile art that is
itself a gradient, image, mesh, symbol or pattern is sampled the same
way, and a pattern inside tile art moves with its tile. A gap between
the tile art gives the color of what lies below.
- A gradient mesh gives the patch color interpolated at the point.
- A symbol instance gives the color of its symbol's art at the point.
The Live Paint Bucket's Eyedropper sampling and Brush Options' Key Color
eyedropper use the same sampling.
With the Live Paint Bucket, Alt-click on vector art samples its fill,
stroke and stroke weight into the paint defaults, and on a placed image
it samples the pixel as a solid fill with no stroke. Alt+Shift-click
samples only the color under the pointer, including an intermediate
gradient color or image pixels, into the active fill or stroke. Neither
click paints or changes the selection or selected art, and the next
Bucket click paints with the sampled paint. While Alt is held, the
Bucket shows the Eyedropper cursor and no face or edge highlight.
appearance.copyFrom takes `defaultsOnly: true` to update only the paint
defaults, and paint.sampleColor takes `clearStroke: true` to clear the
stroke default after an image sample into the fill.
Holding backquote while dragging with the Selection tool (move, bounding-box
scale or rotate) or the Rotate, Reflect, Scale or Shear tool applies the drag
to the selected objects' pattern fills and strokes and leaves their geometry
in place. The key state at release decides the result: the key may be pressed
after the mouse button goes down, and releasing it before the button comes up
commits an ordinary transform. Objects without pattern paint do not move.
PointerEvent gains a `backquote` flag (false when absent). The desktop canvas
reads it from the held key on every pointer event, and `ui.pointer` and the
`pointer_gesture` tool accept it per event or as a default for the gesture.
Escape did not cancel a drag with the Anchor Point, Mesh, Shape Builder
and Measure tools, or a Direct Selection drag of a mesh point, a corner
widget or the brackets of type on a path: the drag went on until the
mouse was released. Escape cancels every other Direct Selection drag
and a drag with the Free Transform, Rotate, Reflect, Scale, Shear and
Gradient tools.
Escape now cancels an Anchor Point drag, a Mesh tool drag of a mesh
point or handle, and a Direct Selection drag of a mesh point or handle,
a corner widget or the brackets of type on a path. The art goes back to
how the press found it, no undo step is added, and the rest of the drag
and the release send nothing. Escape during a Shape Builder drag ends
it, so the release merges and deletes nothing; the tool takes Escape
only during the drag. Escape during a Measure tool drag ends it and
brings back the measurement shown before the press. Escape after an
Anchor Point or Direct Selection press that has not moved yet drops the
press. Otherwise, outside a drag, Escape does what it did before.
Docs: docs/mcp.md, docs/target-app-parity.md.
The Artboard Options help pages describe two display options
(https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/modify-display-settings-of-artboards.html,
.../set-video-display-options-for-artboards.html): a mark at the artboard's
center, and lines from the middle of each side through the center. The
document already had show_center_mark and show_cross_hairs on Artboard, but
nothing set or drew them.
artboard.setProps (each `artboards` entry too) and artboard.new take
showCenterMark and showCrossHairs as true/false, in the same single undo
step as the other fields; a locked artboard can still change them.
Artboard Options has both checkboxes, filled in from the artboard whether
it opens from the panel or a double-click with the Artboard tool, and its
New Artboard form passes them to artboard.new. The canvas draws them in the
new theme token artboard_mark, 1 point wide on screen at any zoom: a cross
12 points across at the center whose arms follow the artboard's sides, and
the two cross hairs. They are screen-only like the edges; exports and print
never read the fields. inspect_document reports them when on. Saved files
are unchanged: the two keys were already always written, so a document that
never shows them saves byte for byte as before, and a file without the keys
opens with both off. Video safe areas, video rulers and the pixel aspect
ratio are not part of this change.
Verification: artboard_display_marks_are_set_and_cleared_in_one_undo_step,
artboard_display_marks_round_trip_through_the_native_format,
artboard_display_marks_stay_out_of_exports (SVG and PDF identical with the
marks on and off), an_artboard_shows_its_center_mark_and_cross_hairs
(positions, screen-constant size under zoom, rotated view) and
artboard_options_show_center_mark_and_cross_hairs. cargo xtask ci: all 10
steps passed.
With the Artboard tool the arrow keys did nothing to the artboards: the
tool claimed only Escape, Delete and Backspace (tools/src/xform/artboard.rs
claims_key), so the arrows fell through to object.nudge in
ui-egui/src/shortcuts.rs, which moved the selected art on its own (or
nothing, with no art selected) and left the artboard where it was.
- artboard.rs: the tool claims the arrow keys and turns each press into
one artboard.move of the selected artboards that aren't locked, by the
keyboard increment (Preferences > General > Keyboard Increment, read
from the tool context as Touch Type does), Shift ten times as far as
object.nudge does, with moveArt from the tool's Move/Copy Artwork with
Artboard option, as a drag sends it. One undo step per press; nothing
during a drag or with only locked artboards selected.
- tests: arrow_keys_nudge_the_selected_artboards (tools: claims, step,
Shift, moveArt, several boards, locked, mid-drag) and
artboard_tool_arrow_keys_move_the_selected_artboards_with_their_art
(engine: the artboards and the art on them move, one step per press).
Verified live over --control: a 400x300 artboard with a selected 40 pt
square, Artboard tool, ArrowRight: before, the artboard stayed at x 0 and
the square moved to x 51 ("Move"); after, the artboard goes to x 1 and the
square with it ("Move Artboard"); Shift+Down moves both 10 pt; with
Move Artwork off only the artboard moves; increment 5 moves 5 pt.
The drag case in the report: a drag already moves the art, but only the
art that lies wholly inside the artboard and is unlocked and visible
(doc/src/lib.rs art_on_artboard; locked and hidden art only with
Move Locked and Hidden Artwork with Artboard). Art overlapping the
artboard's edge stays behind by design, which may be what the reporter
saw when dragging.
Hue Shift colorization can sample key color from canvas art via the
eyedropper next to the swatches, matching Illustrator's brush workflow.
Co-authored-by: Cursor <cursoragent@cursor.com>
With Use Preview Bounds on, the bounding box, the Transform panel,
Align, Distribute, Distribute Spacing and object.setBounds measured
each object without its geometry effects. A square with Offset Path
10 measured as the square, and type with Outline Object measured by
its layout box instead of its glyphs.
The new vectorcraft_effects::preview module measures objects as they
are drawn. A path is measured by its effected shape, with the
geometry effects of each fill and stroke that paints. Type, images,
live objects and symbol instances are measured by their reshaped art.
A group or layer with geometry or Pathfinder effects, or with a fill
or stroke of its own that has geometry effects or paints members with
geometry effects, is measured by its evaluated art. A blend, envelope
or repeat whose objects have geometry effects is measured by its
evaluated art, with those objects measured after their effects. Raster
effects are not measured. In one object, the art of at most 4,096 symbol
instances is measured with its geometry effects, and the other
instances are measured without them. With Use Preview Bounds off, the
measures are unchanged.
object.setBounds sizes objects with geometry effects by measuring
them after each scale it tries, so type with Outline Object set to
100 pt wide gets a glyph box 100 pt wide.
Selection::anchors_box is split out of Selection::anchor_bounds. The
doc comment of VectorcraftApp::selection_bounds said that it includes
effects; it now says that it leaves them out.
With the Artboard tool, a Shift-press on an artboard toggled it in the
selection and started a move, which Shift kept at 45° steps. A
Shift-drag on the pasteboard drew a square artboard. The reference
app's documentation gives Shift-drag inside an artboard as the way to
draw an artboard there, and Shift-drag on the canvas as a marquee that
selects artboards.
A Shift-click now toggles its artboard when the button comes up. A
Shift-drag inside an artboard draws a new artboard (artboard.new), and
a Shift-drag on the pasteboard toggles every artboard it touches, the
last one added becoming active. Shift no longer constrains an artboard
move or keeps a new artboard square, and no other modifier does. Shift
on the active artboard's handles still resizes it proportionally. The
hint bar names the Shift gestures in every complete catalog, and the
cursor is a crosshair over an artboard while Shift is held.
Escape while a new artboard was being drawn left the tool. Escape
during any drag, the new marquee included, now cancels the drag and
keeps the Artboard tool, as it already did during a move or a resize.
Docs: docs/target-app-parity.md.
A clip group answered a press only strictly inside its clipping path,
and there the art above the path came first in the hit test, so where
the clipped art extends past the mask's edge the clipping path's
segments could not be reached on the canvas.
With the new HitOptions::clip_outlines, which the Selection, Direct
Selection and Group Selection tools set
(ToolContext::selection_hit_options), a press within the selection
tolerance of a clipping path's outline, inside or just outside it,
hits the clipping path (HitKind::Outline) before the art it clips:
Direct Selection takes its segments and the Selection tool the clip
group. It applies to a clip group's clipping path and to a layer's
clipping mask, and only to one a click may hit (may_hit: visible,
editable, within reach). Inside the mask the art is hit as before.
The other tools, which act on what is drawn (the eyedropper, type,
gradients...), leave the option off, and hit_test_visible never
applies it, so they still take the art at the mask's edge rather
than the mask's empty appearance. Select Behind's one-pass walk takes
a layer mask's outline first too, so it lists what the repeated hit
test finds. Object Highlighting and a right-click's selection in the
canvas (selection_hit_at) take what those tools take; dropping a
swatch on art still hits what is drawn.
The reference app reshapes a clipping path with Direct Selection:
https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-clipping-masks.html
Tests in doc: a_clipping_path_is_hit_on_its_outline_before_its_art,
only_the_selection_tools_hit_a_clipping_path_on_its_outline and
select_behind_takes_a_layer_masks_outline_first; the random Select
Behind test now clips layers and sublayers and sets clip_outlines,
and fails without the Select Behind change. In tools,
a_press_on_a_clipping_path_takes_its_segment_over_the_art fails
without the change; select::a_press_on_a_masks_edge_selects_its_clip_group
fails when the Selection tool uses the plain options; the eyedropper
copies the art on the edge. Recorded in docs/ui-parity.md,
docs/gaps.md and docs/target-app-parity.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found while testing crates/tools/src/xform/measure.rs:20 (shown at :66, reported at :80)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Honor Measurement Labels during envelope-style corner drags on the Free Transform tool, matching the Move handle readout.
Co-authored-by: Cursor <cursoragent@cursor.com>
Hide the preview line and readout when Smart Guides › Measurement Labels is off; options still report the last drag.
Co-authored-by: Cursor <cursoragent@cursor.com>
Smart Guides spacing now applies when scaling with the Selection and Free Transform tools, not only on moves.
Co-authored-by: Cursor <cursoragent@cursor.com>
In a CMYK document, paint.setFill stores an RGB color as CMYK through
the color settings, but the other commands that take a color stored it
as RGB: text.create, text.createInPath, text.setStyle and
text.setRangeStyle colors, a shapeBuilder.merge hex fill,
paint.editGradient stops, freeform gradient points, gradient mesh
points, the Symbol Stainer's color, and swatch.new {colors},
swatch.newGroup, swatch.editGroup and swatch.edit. The Mesh tool sent
the fill as a hex value, so a CMYK fill came in as RGB.
These commands now store such a color through paint::new_color, as
paint.setFill does, and take keepModel to keep RGB (shapeBuilder.merge
inside its fill object). A color the swatch
or gradient already has keeps its model, so stops sent back with a
moved offset keep their colors (the Gradient panel's ramp and the
Gradient tool send keepModel), and a stop that names a process
swatch takes the swatch's color as it is, in paint.setFill gradients
too. Where the app copies an existing color, it keeps the color's
model: the Color panel's sliders for gradient stops and freeform
points, a swatch put on a stop or a point (the stop popover, Alt-click
in the Swatches panel, a drop on the Gradient panel's ramp), the fill
the Mesh tool gives a new point or the Symbol Stainer applies, and the
colors the Color Guide, Color Themes and Recolor Artwork save as
swatches.
swatch.new {color, mode} converted RGB into CMYK before converting it
into mode, so the New Swatch dialog in RGB mode stored #00ff00 as
R101 G176 B62 in a CMYK document. With mode, the color is now
converted once.
Docs: docs/mcp.md, docs/target-app-parity.md.
A Shaper Group keeps its original shapes as its first child, at no
opacity, and clicks do not pick them outside Construction mode. The
Lasso, the Add Anchor Point, Delete Anchor Point, Anchor Point and
Scissors tools still picked their anchor points and segments, also
where the group erased the face, and Smart Guides, Snap to Point and
the transform tools' reference point snapped to them. The Lasso and
the anchor tools also picked art outside the isolated group or object
in isolation mode.
The Lasso and the anchor tools now visit only the art a click can pick:
the isolated object in isolation mode, and a Shaper Group without its
original shapes unless they are isolated (Construction mode). Smart
Guides and Snap to Point leave the original shapes out of their
targets, and Spacing Guides leave out their bounds when a face moves.
The transform tools' reference point snaps to an original shape only
when that shape is selected, or under the pointer in Construction
mode. The Shaper Group test document moves into the shared test
helpers.
Docs: docs/shaper.md, docs/target-app-parity.md.
A bounding-box handle dragged with the Selection or Free Transform tool
snapped only with Smart Guides on, and then only to the other objects
and the artboards. View > Snap to Grid, Snap to Pixel, Snap to Point
with Smart Guides off and the ruler guides did not move it, while a
move snaps to all of them.
The corner or side a handle moves now snaps as a dragged point does.
With Snap to Grid on it lines up with the nearest gridline, and with
Snap to Pixel on it lines up with whole pixels. With Smart Guides off,
Snap to Point lands it on the other objects' anchors and on ruler
guides within its distance. With Smart Guides and Snap to Point both
on, it lines up with the ruler guides as well as with the other
objects. Shift still keeps the proportions. Without Alt the opposite
side stays put, and with Alt the box still scales about its center.
Docs: docs/target-app-parity.md.
Rearrange All Artboards, Select › All on Active Artboard (for objects
with no area), Copy's source artboard for Paste in Place, Align to
Artboard without an index, Crop Image, the Crop Image tool, Place with
`at`, CSS positions and a filter plug-in's artboard decided whether a
point or an object's center is on an artboard with Rect::contains,
which leaves out the right and bottom edges. A line along an artboard's right or
bottom edge was on no artboard: Rearrange left it behind, and All on
Active Artboard did not select it. Document::art_on_artboard counts
art flush with those edges (#890).
They now count the edges as art_on_artboard does, within a millionth
of a point. On an edge two artboards share, the artboard to the right
of it or below it still holds the point. All on Active Artboard
selects a line on a shared edge from either artboard. A press exactly
on a right or bottom edge now picks that artboard, and the Artboard
tool's cursor shows Move there.
In a CMYK document, an RGB color from the Eyedropper, a hex value or
paint.setFill was stored as CMYK by the profile-free formula
(k = 1 - max(r, g, b)), not through the color settings. RGB 83/170/223
came in as C63 M24 Y0 K13. A CMYK image was sampled through the RGB its
decoder shows, so its inks came back only when the formula happened to
round-trip them.
paint_from now converts an RGB color into a CMYK document with
Cms::convert and the settings' intent, as Document Color Mode and Swatch
Options already do. This covers paint.setFill and paint.setStroke,
gradient stops, swatch.new {color} and the Eyedropper. With Generic CMYK,
83/170/223 now separates to C59 M12 Y3 K0, and #ff0000 now separates to
C0 M97 Y100 K0 instead of C0 M100 Y100 K0. keepModel: true still keeps
RGB. paint.sampleColor with a stop stores the color the same way.
The Eyedropper samples a CMYK image's ink amounts (a four-component
JPEG, or an 8- or 16-bit chunky CMYK TIFF) and returns them unchanged
(ImageBlob::sample_cmyk_object), also in an RGB document. It takes an
RGB image's sRGB pixel into the working RGB space, as the screen color
sampler does, so the picked color shows as the pixel when the working
space is not sRGB.
Docs: docs/mcp.md, docs/target-app-parity.md.
In a rotated view the resize and rotate cursors were the ones for an
unturned view. With the view turned 90°, the right-middle handle of a
selection's bounding box shows at the middle of the box's bottom edge
and drags up and down, but its cursor was the left-right arrow. This
affected the Selection, Free Transform, Artboard, Slice Selection, Crop
Image, Perspective Grid and Perspective Selection tools, ruler guides,
and the spread handle of a freeform gradient.
Now each of these cursors turns with the view to the nearest 45°. The
Selection tool and the Perspective Selection tool turn the handle's
direction by the view's angle before they round it to a cursor.
ToolContext::view_angle reads the view's angle from the screen frame,
and Cursor::turned turns a resize or rotate cursor by it. The new
Cursor::rotate_about_in_view turns the pointer's direction from the
center by the view before it picks one of the rotate cursor's eight
directions. Cursor::rotate_about keeps its signature and behavior. At
0° every cursor is as it was.
Docs: docs/target-app-parity.md.
Node editing as the public manual describes it: Direct Selection's anchor squares (hollow
or filled), Reshape Segment over a curved segment, Group Selection's plus (also Direct
Selection with Alt), the arrow hollow while a drag snaps, and Alt-copy arrowheads. The
Anchor Point caret, the Curvature pen and Free Transform's side-shear arrows (ShearH,
ShearV) are drawn in code. Lasso, the freehand, symbolism and other tools return cursors
of their own that borrow their old look until drawn (Cursor::stand_in).
crates/tools/src/cursors.toml lists every Cursor value, when it shows and its hotspot,
for whoever draws them; `cargo xtask cursors` (in ci) warns about a missing, unknown,
repeated or incomplete entry and never fails. The busy cursor is listed as missing (G1).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A double-click on an object or group fits one artboard to the visible bounds and does not open Artboard Options. The second click does not fit another artboard. Other artboards stay. Double-clicking empty artboard space still opens Artboard Options. Double-clicking the name drawn above an artboard renames it.
Rarely set fields move into one optional boxed NodeExtras, and large NodeKind payloads (images,
live shapes, blends, envelopes) are boxed. Every object, and every undo step that copies it,
shrinks: a 50k-path move keeps ~24.5 MB per step instead of ~40 MB. Files stay byte-identical
(Node keeps its flat JSON through a borrowed Serialize and a from-wire Deserialize, no flatten),
and loading is slightly faster. Undo and redo reuse their step's size estimate instead of
walking the document again (8.3 -> 5.6 ms for a 50k-path move).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc
# Conflicts:
# crates/engine/src/cmd/graph.rs
# crates/svg/src/export.rs
The Artboard tool and the Artboards panel only ever had one artboard
selected, so Shift-clicking another didn't add it. As in Illustrator:
- Artboard tool: Shift-click adds an artboard to the selection (it
becomes active) or takes one out; a plain click on a selected one keeps
the selection, so a drag moves them all (Alt copies them all, the
copies becoming the selection), and Delete removes them all, one
artboard always staying. The other selected artboards show dashed
bounds; `selected` is a tool option.
- Artboards panel: with the Artboard tool, Shift- or Ctrl/Cmd-click on a
row adds or removes it, every selected row is highlighted, and Delete
Artboard removes them all.
- `artboard.move` and `artboard.delete` take `indices` besides `index`:
several artboards in one undo step, each with its art (an object moved
once where artboards overlap).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Beside every corner of the bounding box the curved rotate cursor bowed
up. It now bows from the box's centre toward the pointer, in 45° steps
(Cursor::RotateToward, made by Cursor::rotate_about): at the top right
it opens toward the bottom left, at the bottom right toward the top left.
The Selection tool shows it over the rotate zone and while rotating (it
follows the pointer round), and Free Transform does the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the Artboard tool, the active artboard's name was drawn twice: by
the canvas, as for every artboard, and by the tool, in blue and a little
higher. The canvas now leaves that one to the tool, which draws it where
the canvas names the others.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`artboard.setProps {scaleArt: true}` maps the art fully inside a resized
artboard (as Move Artwork with Artboard gathers it) and its artboard guides
from the old rectangle onto the new one, each side by its own ratio, in one
undo step, and returns the ids scaled. Strokes, effects and corners follow
Scale Strokes & Effects and Scale Corners (`strokes`, `corners`), pattern tiles
Transform Pattern Tiles (`patterns`); locked and hidden art follows
`lockedAndHidden` (default: prefs moveLockedWithArtboard). The resolved choices
are journaled, so a replay scales the same objects the same way. A move that
keeps the size scales nothing.
The Artboard tool's `scaleArt` option (saved with `moveArt`) makes handle
drags proportional, as the public help describes, keeps them proportional at
the 1 pt minimum, and leaves the scaled art out of the Smart Guide targets.
Move and Scale Artwork with Artboard are check boxes in the Control bar and
Properties' Edit Artboards; the artboard size fields there and in the
Transform panel pass the option. Translated in the eight catalogs that have
Move Artwork with Artboard. docs/mcp.md and the ROADMAP updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Artboard tool handled Delete/Backspace in Tool::key, but never claimed
the keys through Tool::claims_key. The UI only routes Delete/Backspace to a
tool that claims them, so the keypress went to the Clear shortcut instead
and the artboard stayed. The existing tests called the tool directly and
missed it.
The tool now claims Delete/Backspace while no drag is in progress and more
than one artboard exists. With only one artboard left, the keys stay with
the shortcut.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>