Ruler guides were a list kept beside the layers. Each guide is now an
object of its layer or sublayer, always directly in it and above the
layer's art. These behave as on main:
- Guides draw on screen only. Exports (SVG, PDF, PNG, PSD), Fit All,
Select All, the art bounds, clicks and marquees, and a layer's
Pathfinder and Transform effects leave them out. The Layers panel
lists them as guide rows of their layer, the highest guide.list
index first.
- Deleting a layer deletes its guides, and merging layers keeps their
guides in the merged layer.
- The guide commands number guides as guide.list does, in the order
they were made.
New: each guide takes an id from the document's object ids. An object
command given a guide's id fails with an error, and node.move cannot
put a guide into a group or out of its layers. A guide made while an
opacity mask, a symbol or a pattern is edited moves to the top
visible, unlocked layer when editing ends.
File format: native files still save the guides in the document's
guides list, in every file version, so older builds read them. Each
entry may now also carry the guide's id, name, locked and hidden
flags; older builds ignore those fields. Opening a file puts each
saved guide on its layer (on the top visible, unlocked layer when the
file names none or a layer it does not have), and each saved guide
counts against the object limit for opening a file. A ruler guide node
that a file holds below a layer's art moves up to the layer's guides,
and one anywhere else (in a group or another object, beside the
layers, or in a symbol) is removed. Ruler guides read from an .ai file
become guide objects on the layer they were on, numbered in the order
the file lists them.
After an aligned Remove Item or Duplicate Item, the active row was
re-pointed by the indices of the first object in paint order. When the
first selected object paints above the others, its own rows that changed
are different ones, so the active row could be dropped or land on the
wrong row, and a later paint edit changed that row instead.
The active row now follows the rows removed or copied in the first
selected object's own stack.
Follows #5591.
Direct Selection of a path, compound path or type inside a dynamic instance now applies transparency and gradient or pattern fills through the usual paint commands, and the G5 docs catch up on Overlap and Pattern auto corners.
Applying or redefining a graphic style on a series mark whose fill or
stroke had paint, when the style leaves that row without paint, now
stores the row's opacity and blend mode on the graph. Before, the
opacity and blend mode another mark of the series left stayed, so a
Filled Lines band's outline kept a stroke opacity after a style without
a stroke went on a stroked marker. A row with no paint before and after
the edit still keeps what the graph stores.
appearance.addFill, appearance.addStroke and appearance.duplicateItem can
put a new topmost row on a series mark with the paint the mark already
shows. When another mark of the series had set a different stored paint,
the mark's top paint did not change, so the graph kept the other mark's
paint and drew it again. A new topmost visible row now stores its paint,
as an Eyedropper append already does.
The params text of the Appearance commands that update a series' stored
paint said the graph stores the paint and weight of the topmost visible
fill and stroke. Each of them also stores that row's opacity, as
docs/mcp.md says. The text now names the opacity too.
paint.setFill, paint.setStroke, paint.none, Apply Fill to Stroke and Apply
Stroke to Fill stored a series' paint on the graph. appearance.setItem, the
Eyedropper, appearance.transfer, Clear Appearance, Reduce to Basic Appearance
and the add, remove, move and duplicate row commands stored no paint or weight.
An Eyedropper pick of a blue fill at 40% onto a red column drew red at 40%
after the next redraw.
These commands and Show All Hidden Attributes now store the paint and weight
of the topmost visible fill and stroke of a path directly in a series group,
in the same undo step. A color, weight or opacity that a command writes to that
row replaces the stored value even when the mark's own value does not change. A
row that has no paint before and after the command does not change the stored
paint. These commands store no stroke for a series that draws Filled Lines
bands, and a band no longer takes its color from an empty stored stroke.
The stored opacity also comes from the topmost visible row: hiding a top row
stores the row under it, and an opacity set on a hidden top row is not stored.
Follows #5501.
The help text of appearance.addFill, addStroke, clear, reduceToBasic,
removeItem, duplicateItem, moveItem, copyFrom, showAllHidden and transfer,
paint.default, and graphicStyle.apply and redefine said the graph stores
only the paint and weight of the topmost visible fill and stroke. These
commands also store that row's opacity and blend mode, as
appearance.setItem's help and docs/mcp.md already say.
An object opacity or blend mode set on a path directly in a graph series, and
the blend mode of its topmost visible fill or stroke, were lost when the graph
was drawn again. The graph now stores them per series next to the row opacity
it already stores, and puts them on every path directly in the series when it
draws the series again.
- transparency.set and object.setProps store the object's opacity and blend
mode. A value the mark already has still replaces the value another mark of
the series stored.
- appearance.clear, appearance.transfer, appearance.copyFrom with Transparency
picked up, graphicStyle.apply without add and graphicStyle.redefine store
the object's opacity and blend mode they leave on the mark.
- The other Appearance and style commands store them when they change them
on a mark.
- Every command that stores the opacity of the topmost visible fill or stroke
also stores its blend mode. A Filled Lines band's outline takes the stored
stroke opacity and blend mode and keeps its black 0.25 pt stroke.
- An opacity and blend mode are stored as a pair from the edited mark:
setting only the opacity of one mark also stores that mark's blend mode.
- A graph design's copy in a series keeps its own values.
Docs: docs/mcp.md, docs/target-app-parity.md.
Applying a graphic style, Redefine Graphic Style, Default Fill and Stroke
and Swap Fill and Stroke on a path in a graph series changed the path
but not the paint the graph stores for the series. When the graph was
drawn again, the series went back to its earlier paint.
These commands now store the mark's topmost visible fill and stroke on
the graph, in the same undo step as the command:
- graphicStyle.apply stores the paint, weight and opacity of both kinds
of row. With `add` it stores only the kinds of row that are visible
in the style.
- graphicStyle.redefine stores the paint, weight and opacity of the
marks still linked to the style.
- paint.default stores the white fill and the 1 pt black stroke, both
opaque.
- paint.swap stores the swapped paints and the stroke weight and keeps
the stored opacity. It swaps the topmost rows even when they are
hidden, and a hidden row it swaps is not stored.
The rule for each kind of mark is the one docs/mcp.md gives for the
Appearance commands. docs/mcp.md also says what the graph does not
store from a graphic style and that a series group given to a graphic
style command is not stored. The params text, docs/mcp.md and the
Graphs line in docs/target-app-parity.md list the commands.
paint.setFill, paint.setStroke, paint.none, Apply Fill to Stroke and Apply
Stroke to Fill stored a series' paint on the graph. appearance.setItem, the
Eyedropper, appearance.transfer, Clear Appearance, Reduce to Basic Appearance
and the add, remove, move and duplicate row commands stored no paint or weight.
An Eyedropper pick of a blue fill at 40% onto a red column drew red at 40%
after the next redraw.
These commands and Show All Hidden Attributes now store the paint and weight
of the topmost visible fill and stroke of a path directly in a series group,
in the same undo step. A color, weight or opacity that a command writes to that
row replaces the stored value even when the mark's own value does not change. A
row that has no paint before and after the command does not change the stored
paint. These commands store no stroke for a series that draws Filled Lines
bands, and a band no longer takes its color from an empty stored stroke.
The stored opacity also comes from the topmost visible row: hiding a top row
stores the row under it, and an opacity set on a hidden top row is not stored.
Follows #5501.
appearance.copyFrom and a fill or stroke change on a story selected by
Select All no longer skip locked frames, hidden frames and frames on a
locked artboard. They apply to every frame of the story, as the Character
panel's story edits do. appearance.copyFrom still leaves out a frame that
is its source.
With a threaded story selected by Select All, switching to the Eyedropper kept
only the edited frame's selection, so a pick styled one frame. The selection now
carries the story's frames, a pick or Shift-click applies to the text of every
frame as one undo step, and switching back to a Type tool restores the story
selection. A selection inside one frame and unthreaded type behave as before.
A pick and a fill or stroke change on a selected story leave locked and hidden
frames and frames on a locked artboard as they are. appearance.copyFrom leaves
out a frame that is its source.
Typing over a selected story no longer removes the text of a frame whose thread
was removed after Select All.
The remaining flags under crates/engine/src/cmd that read a boolean with
get(..).and_then(Value::as_bool).unwrap_or(..) now go through switch_or,
so a value such as "false" or 1 is an error instead of the default.
Omitting a flag or giving null keeps the old behavior.
- effect.move: copy
- aligned on the appearance.*, stroke.set, transparency.set and effect.*
commands, checked before any early return
- transparency.viewOpacityMask: on
- view.printTiling: on
- plugin.install: replace and allowHeavy, checked for every plug-in
- edit.clear: guides, and path.deleteAnchors' read of it
- text.create and text.createInPath: vertical (already refused by
check_switches, so their behavior is unchanged)
Paint and effect edits matched the active row across all selected
objects at once, so one object without an equal row made every object
edit the row at the raw index. ItemTarget::per_target and
effect_targets now match each object against the first one on its own,
as items_per_target does. effect_targets also looks up the row in the
stack it belongs to when deciding whether an effect edit is
object-level, so an effect on a row that sits lower in another object's
stack no longer fails with "no appearance item".
The comment in add_item said a new fill or stroke is a copy of the top
one. The code takes only the top item's paint, and for a stroke its
weight. The comment now says so, as the params text does.
appearance.addFill copies the active fill row only when the call has no
`ids`, no `id` and no `target: "contents"`; the params text now names
`id` too. appearance.addStroke gives the new stroke at least 1 pt, so a
0.5 pt top stroke gives a 1 pt stroke; the params text now says so.
Tests pin both: `id` alone leaves the active row alone and puts the new
fill on top, and Add New Stroke on a 0.5 pt stroke adds a 1 pt one.
Add New Fill again copies only the active fill row's paint; Duplicate
Item copies a whole row. The params text of appearance.addFill and
appearance.addStroke now says the new fill or stroke takes the paint
(and for a stroke, the weight) of the row it starts from, and a test
pins the paint-only copy.
With a fill row active, Add New Fill copied only the row's paint and
reset its opacity, blend mode, visibility, overprint and effects. The
new row is now a copy of the whole active row, as the command's
description says.
Reduce to Basic Appearance and Add New Fill matched the Appearance
panel's active row only when every selected object had an equal row.
With three objects whose third had a different lower fill, the second
object lost the match too: Reduce kept its top fill instead of the
active one, and Add New Fill put the copy on top instead of above the
active row.
items_per_target now matches each object against the first one
separately, so an object keeps the match whenever it has an equal row.
While the Type tool edits a frame of threaded text, Select All selects
the text of every frame of the story, highlighted in each frame. The
tool edits the first frame that holds text.
- Typing, Delete and Cut replace the whole story as one undo step
(`text.editRange` takes `clear`, the other frames whose text goes).
- Copy and Cut take the whole story (`text.getRange {id, story: true}`).
- Fills and strokes, `type.step`, `appearance.copyFrom`, the Character,
Paragraph, OpenType and text style panels, the Tabs panel's tab
stops, the font menus and the Justification dialog act on every
frame's text (`text.setRangeStyle {id, story: true}` styles them in
one edit).
- A caret move, a click or a word or paragraph selection ends it.
Select All in unthreaded type is unchanged.
Docs: docs/mcp.md, docs/target-app-parity.md.
A graph keeps the paint and stroke weight given to a series' marks, but not
the opacity of their fill or stroke row. An opacity set with
`transparency.set {item}` or `appearance.setItem {opacity}` on a column,
bar, line or other series path was lost the next time the graph was drawn
(a data, type or option change).
- `SeriesPaint` gains `fillOpacity` and `strokeOpacity`. A setItem opacity
on the topmost fill or stroke of a series path stores it, in the same
undo step, and the generator puts it on the topmost fill and stroke of
every path directly in that series' group. Graph designs keep their own
opacity. A value read from a file is held to 0..1.
- The Appearance commands that change the opacity of a series path's
topmost fill or stroke (add, remove, duplicate and move a row, Clear
Appearance, Reduce to Basic Appearance, `appearance.transfer` and the
Eyedropper) update the stored opacity. They do not change the stored
paint, which only `paint.setFill` and `paint.setStroke` set.
- An edit that leaves a series path fully opaque stores nothing for a
series the graph keeps nothing for.
- Drag strokes, fills, characters and effects to move them, or drop them
on the Duplicate or Delete button
- Select several rows with Shift/Cmd-click and move them as one block
- Delete and Backspace remove the highlighted rows, not the selected art
- Row links open their panel (Stroke, Character…) as a flyout
- Stroke weight spinner follows Units > Stroke
- Transparency panel edits the active fill's or stroke's opacity
effect.apply now reports the item of the first selected object, whose row
the active item is. With nothing selected, an explicit aligned item returns
the command's error instead of an internal error.
The Appearance panel's active row is a row of the first selected object's
stack, but fill, stroke, gradient and effect edits matched it against the
object that paints first. When the selection was made top object first and
the objects' stacks differ, an edit changed another row: with the red fill
of [red, white, Stroke] active above a [white, Stroke] object, Fill set the
white row instead.
These edits now match the active row against the first selected object (or
the compound path holding it), as Add New Fill and Reduce to Basic
Appearance already do. Gradient edits also read whether the active row is a
fill or a stroke from that object.
The targets are in paint order, so the first selected object is not always the first target. The active row is now matched against the first selected object wherever it is among them.
Reduce to Basic Appearance kept the topmost visible fill and stroke even
when another fill or stroke row was selected in the Appearance panel.
It now keeps the selected row in place of the topmost one of its kind,
and in each other selected object the row equal to it. With ids, or
with no selected row, it keeps the topmost ones as before.
With a fill row selected in the Appearance panel, Add New Fill now
inserts a copy of that row directly above it and selects the new fill.
Rows already above the selected one stay where they are. Before, the
new fill was always a copy of the topmost fill, added at the top.
The selected row belongs to the first selected object. In a mixed
selection, an object that shares that row gets the copy above its own
equal row; an object that does not share it gets a copy of its top
fill on top, as before. With a stroke row selected, or with `ids` or
`target: "contents"`, Add New Fill works as before.
The panel drops rows that were Cmd- or Shift-clicked together when the
active row changes outside the panel, so after Add New Fill (button,
menu or shortcut) only the new fill is selected.
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.
With nothing selected, a click with the Eyedropper picks up an object's attributes and an Alt/Option-click on another object applies them, as the reference app's Eyedropper does. Alt-click only copied from a selected object, and with nothing selected it failed. The session now keeps a copy of what appearance.copyFrom picked up last, and a reversed copy with nothing selected applies it.
Review follow-ups:
- items_per_target maps each requested row on its own. Rows deleted or
duplicated together, one shared and one not, fell back to the literal
indices in every object, so the other objects could lose the wrong
items. The first object takes its own rows; each other object takes
its copies of the shared ones only.
- effect.dialog takes `aligned` (the panel passes it on a mixed
selection's shared effect row), and its OK and preview run
effect.setParams with it, so the dialog edits each object's own copy
of the effect instead of the first object's index in every object.
Tests: removing a shared stroke with an unshared fill leaves the other
object's fills; an aligned dialog's command carries `aligned`.
With several objects selected whose appearances differ in anything (two
circles whose fills differ but whose stroke and effect are the same),
the Appearance panel showed only "Mixed Appearances": no stroke, effect
or opacity row, so attributes the objects share couldn't be edited
together. It now lists what they share, as the issue describes:
- `vectorcraft_doc::appearance::SharedStack` (with `align`, a stacking-
order longest-common-subsequence match) gives the fills, strokes and
object-level effects every object has an equal copy of, and where each
is in every object's own stack.
- The panel's object row names the objects' common kind ("Path",
"Ellipse"; "Mixed Objects" only when kinds differ, as the Control bar
does), with an italic "Mixed Appearances" row under it when they
differ, then only the shared rows with their usual controls. The
Opacity row reads "Mixed" when opacity or blend mode differ (an edit
sets them all). The object row shows no fill chip when the fills
differ. Rows aren't dragged in a mixed selection: where a row would go
in each of several different stacks isn't one place.
- The engine maps a shared row to each object's own item: the active
item (a row of the first selected object's stack) always does, so the
Color, Stroke and Transparency panels edit every object's matching
item, and the item and effect commands do with `aligned: true`
(appearance.setItem / removeItem / duplicateItem, effect.remove /
duplicate / setParams / lightingPreset, transparency.set), each as
one undo step. moveItem and effect.move refuse aligned rows of
differing stacks. Without `aligned`, explicit indices keep their
meaning for single objects and scripts.
- "Mixed" is added to every catalog.
Tests: the shared stack of the issue's case, of a stroke at another
index, of nothing alike and of equal stacks, and that matches never
cross; editing the shared stroke edits both strokes (at index 1 and 2)
in one undo step that restores both, also through stroke.set on the
active row; removing a shared effect and adding a fill reach both, and
reordering differing stacks is refused; the panel lists kind, note,
Stroke, Feather and Opacity, not Fill, the full stack for alike objects
and "Mixed Objects" for a path and type. The existing object-row test
now expects the kind for two rectangles and the shared rows when their
opacity differs.
object.transformEach refused reflectX, reflectY, random and copy given
as text, but read strokes, corners, objects and patterns given as text
as the key left out. With Scale Strokes & Effects on,
{"scaleH": 200, "scaleV": 200, "strokes": "false"} scaled the stroke,
recorded an undo step and kept the params for Transform Again, which
repeated the call. artboard.rearrange, artboard.move and
artboard.duplicate read their patterns option the same way, and
artboard.setProps reads its patterns, strokes and corners options the
same way.
These commands now refuse such a value with "`<key>` must be true or
false" before they change anything. So do the other options and
numbers of artboard.setProps and artboard.move, the art of Duplicate,
Copy and Cut Artboard, the shape commands and path.create, path.close,
path.setHandle, path.moveAnchors, the Pencil, Curvature, Reshape
Segment and Blob Brush options, text.create, text.createInPath,
text.setRangeStyle, text.editRange, text.getRange, text.touchType,
text.areaOptions, type.step, the font size steps,
type.opticalMarginAlignment, type.systemLayout, type.fractionalWidths,
type.ruby, the symbol and swatch commands, the Appearance panel's
commands, the gradient and freeform gradient commands, Shape Builder,
Live Paint, Image Trace, the opacity of object.setProps, the radius,
sides and relativeRadii of object.setLiveShape, the reference point of
object.setBounds and the artboard of Align, Distribute and Distribute
Spacing. They refuse a number given as text with "`<key>` must be a
number, not <value>", and a gradient stop's tint with "stop <n>:
`tint` must be a number". A null still reads as the key left out.
Docs: docs/mcp.md, docs/target-app-parity.md.
The reference app adds a new fill with Ctrl+/ on Windows and Command+/
on macOS, and it adds a new stroke with Ctrl+Alt+/ on Windows and
Option+Command+/ on macOS. VectorCraft had Add New Fill and Add New
Stroke on the Appearance panel, with no default key for either.
appearance.addFill now has the default Cmd+/ and appearance.addStroke
has Cmd+Alt+/, which are Ctrl+/ and Ctrl+Alt+/ on Windows and Linux.
Like the panel's buttons and menu items, each key runs its command with
no params and adds the item to every selected object. Keyboard Shortcuts
lists both under Window › Appearance with their keys, and press_key
runs them for agents.
Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.
Commands that read the selection through edit::selected_roots leave
out an object whose group is also selected (edit::roots_of). Fourteen
places that read `ids` kept an object listed together with its group.
Given ids that list an object and a group that holds it, Make Repeat
failed with "no such object", New Symbol failed or stored the object
twice in the symbol, and object.transform moved it twice (20 pt for a
10 pt move).
These places now pass explicit ids through edit::outermost, which
drops an id whose ancestor is also listed, with the filter that
roots_of runs on the selection (now edit::without_inner). Nothing else
is dropped or replaced: unlike roots_of, it keeps layers and
compound-path members. A list without nesting gives the same result as
before. Commands that take both ids and the selection as listed, such
as paint.setFill without item, are unchanged.
Docs: docs/mcp.md, docs/target-app-parity.md.