paint.setFill, paint.setStroke, paint.none, Apply Fill to Stroke, Apply
Stroke to Fill and a library gradient or pattern applied with library.use
stored the topmost fill or stroke row of a graph series mark on the graph
even when that row was hidden, and stored a stroke set on a Filled Lines
band. When the graph was drawn again, every mark of the series took a
paint that the edited mark never showed, and a band took the stroke color
as its fill.
These commands now follow the rules of the Appearance commands:
- The graph stores the paint only when the row the command wrote is the
topmost visible row of its kind, or when the command changed what the
mark shows. With `item`, that is the named row.
- A series that draws Filled Lines bands stores no stroke paint or
weight; the band keeps its own outline.
- A row without paint before and after the command keeps what the graph
stores, so None on a marker without a stroke no longer removes the
line's stored stroke.
- When one command writes several marks of a series, the frontmost mark
sets what the graph stores, whatever the order of the ids.
- Apply Fill to Stroke and Apply Stroke to Fill on a mark whose row
already has the paint store it, as paint.setFill does.
A document without graph series marks among the edited objects skips the
comparison of paints.
Docs: docs/mcp.md and the params text of the paint commands.
$.line and $.stack counted lines in the run's expanded source. In code
that comes after an #include they gave lines that match no file, and
this included code in a file $.evalFile ran and code a named engine
kept from an earlier run.
Both now map each frame through the run's source maps, as uncaught
errors already were. $.stack writes each frame as name@file:line,
outermost first, with the run's name as the file of a script given as
source. Code that eval or Function compiled keeps the line of the
source they compiled, in [eval]; code whose place a named engine no
longer keeps is at line 0 of [unknown]. The 64-frame limit, [global]
and [anonymous] are unchanged.
Docs: docs/mcp.md, docs/target-app-parity.md.
$.fileName gave the file being run, so a function defined in a file
that #include inserted, in a file $.evalFile ran, or in an earlier run
of a named engine reported the calling file instead of its own.
- vectorcraft_js: HostItem::SiteGetter gives a getter the CallSite
(source tag, line, column) of the code that reads it.
- $.fileName maps that place through the run's source maps, or the
maps a named engine kept, to the file the code is in. It is empty in
code from a script given as source and in kept code whose map was
dropped. Code that eval or Function compiled still gets the file
being run.
Docs: docs/mcp.md, docs/target-app-parity.md.
A blend made with Create Spine set to Never now shows the Appearance
widget with the Selection and Blend tools, so its color Shift can be
dragged on the canvas as on a blend with a spine. The Easing widget
stays hidden, since easing needs a spine.
The widget sits 16 px above the halfway point, by length, of the
straight lines between the key centers that the steps are laid out
along, and a drag follows those lines' direction there. The lines are
not drawn. `blend_layout_line` exposes that line; `blend_spine` still
returns `None` for such a blend.
An embedded image whose appearance is one Warp effect, with no opacity
mask, is written to the editing data with an art style of that Warp:
its bent pieces as its drawn look and the image itself on `%_` lines,
as other styled objects are written. The reader takes such an image
with its Warp live when the live warp draws as the drawn look, using
the comparison and budget it uses for warped type; otherwise the drawn
look stays, with the drawn-look note. A linked image with a Warp is
written as its drawn art, as before, without its live Warp. Images with
an opacity mask and images with another effect keep their drawn art.
An embedded image whose samples are on `%_` lines is now read as the
hidden copy it is. Before, it came in as a second visible object beside
its drawn look.
The eye and lock on a guide's row in the Layers panel hide or lock that
guide alone, in one undo step, and a drag down either column takes guide
rows along with the other rows it passes. A hidden guide is not drawn and
does not snap, and its row is dimmed; a locked guide is drawn and snaps
but can't be selected, moved or deleted. Both can be changed while the
guide's layer is hidden or locked, and the guide shows or can be edited
only when its layer allows it as well. The state is kept through save
and reopen.
- layer.setProps takes ruler guide node ids; every other command still
refuses them.
- guide.list rows carry the guide's node id and its own hidden and
locked state.
- Alt-click and Ctrl/Cmd+Alt-click on a guide row's eye or lock act on
the other layers, as on any row in that layer.
- Object > Lock > Selection and Object > Hide > Selection lock or hide the
selected ruler guides with the selected art, in one undo step, outside
isolation mode. Unlock All and Show All, document scope, also unlock or
show guides locked or hidden on their own and select them as guides.
- Guide rows dropped on the Layers panel's trash delete their guides
(guide.remove {indexes}). A click on a guide row clears the highlighted
rows, so Delete deletes the guide; with no row highlighted the trash
deletes the selected guides.
- guide.remove and guide.setLayer refuse index together with indexes and
an indexes value that is not a list, and count a repeated index once.
guide.select, guide.remove and guide.setLayer refuse an indexes list of
more than 10,000 entries; guide.setLayer cut such a list short before.
- Moving, deleting or moving to another layer a guide that is hidden or
locked on its own fails with "the guide is hidden or locked"; a guide
on a hidden or locked layer still fails with "the guide is on a hidden
or locked layer".
Docs: docs/mcp.md, docs/target-app-parity.md, docs/ui-parity.md.
When the layers with their live envelopes didn't draw as the page, and
didn't with the drawn art of every live envelope put back either, every
envelope of type got its drawn art back wherever the layers were still
used: the editing data trusted, the objects that differ coming in as
the page's art, or a .ai whose layers would open alone opening alone.
Art that differed anywhere on the page cost every envelope of type its
editable type, though putting its drawn art back changed nothing.
Now each envelope of type is weighed in the page check's own drawing,
on the pixels that it and its drawn art ink (the two drawn together on
a layer of their own, without the rest of the layers). It keeps its
drawn art and hidden type where the layers with it differ from the page
in more of those pixels than its own check allows against its drawn art
(10%, or 3% with all their neighbours), or where the envelopes that stay
live would together differ there in more of a page than half of what
the page check allows (a budget of its own, the same size as the one
their own checks share). Art differing on its pixels still holds it.
It also keeps its drawn art, as before, where the layers don't draw it
where it is and as itself, on the page or off it: under a clip or an
opacity mask, in a group with an appearance of its own, or in another
envelope, a repeat or a blend. Another envelope on no page stays live,
except on a page that holds all the art (an EPS's art box), where one
reaching past the page keeps its drawn art. Past 12 on the pages, all
keep it.
A .ai whose layers would open alone still opens alone: its envelopes of
type are settled again as they were with the page, in the same order
and with the same checks, so each that opens live draws as it was
weighed, and those the page held get their drawn art back. Where the
differing objects are to come in as the page's art and can't with
envelopes of type live (an object that holds type isn't replaced),
every envelope of type gets its drawn art back and that is tried again,
as before. Where putting back the drawn art of every live envelope
makes the layers draw as the page, it is still put back.
On 224 of our own files (used locally), envelopes of type that open
live go from 174 to 186 in 8 files, none fewer; no file changes between
its layers and its PDF part.
Part of #3179.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The namespace list that inScopeNamespaces(), namespace(),
namespaceDeclarations(), the XPath namespace axis, XPath prefixes and
XPath name() collect from a node and its ancestors was built in Rust
buffers the heap limit did not see, so a node with many namespaces in
scope let a script grow past its limit.
The scope now holds the bytes of its list and prefix table until it is
dropped, and namespaceDeclarations() also holds its ancestor table and
the declarations it returns. The queried node stays rooted while the
scope is built, and each declaration is read in place instead of
copying every node's namespace list.
Other copies in the same files are held against the limit too:
- addNamespace(), setNamespace(), setName() and removeNamespace() read
an element's attributes one at a time instead of copying the list,
and removeNamespace() holds its walk stack.
- child(index), comments(), text(), elements(), processingInstructions()
and normalize() on an XMLList hold the copy of its items, and
replace() holds the copy of the children it matched.
- XPath name() holds the units of a prefixed name it builds.
- The XML, XMLList, Namespace and QName constructors, isXMLName(),
attribute(), namespace(), propertyIsEnumerable(), setName(),
setLocalName(), addNamespace(), removeNamespace() and setNamespace()
reserve the bytes of the flat copy of a long (rope) string argument
before they make it, and attribute() reserves the copy of a name
without its leading @.
- QName.prototype.toString() reserves its result before it builds it.
While a Type tool (Type, Area Type, Type on a Path and their vertical
versions) adds or edits type, the task bar shows the font family and
style menus, the Font Size combo and the Fill chip. Type selected with a
selection tool shows the same font controls beside its Create Outlines,
conversion and Type on a Path items. The controls change what the
Character panel's would: the selected characters, else every selected
type object. The Touch Type tool still shows no bar.
While a Type tool edits type, the Fill chip shows the selected
characters' fill, with "?" where their fills differ. On a canvas too
narrow for one row, the font controls go on a second row and narrow to
fit. Screen readers name each control.
When a document opened with notes and with linked files that changed on
disk (Update Links: Manually) or, on the web, can't be read, the line
about the linked files replaced the "Opened with N notes" line, and the
notes could no longer be listed on hover or by a screen reader. The
status bar now shows the line about the linked files, then counts the
notes and names the first, and keeps the notes for that line.
- The Linked File Not Found and Find Linked Files dialogs keep the
open's notes (field `notes`), so the line about modified files shown
after them carries the notes of that open, not of an earlier one.
- File > New from Template... shows the notes of the file it opens, as
File > Open does.
- New message "{notice}. Opened with {n} note(s): {first}" in every
catalog that translates "Opened with {n} note(s): {first}".
Docs: docs/control-protocol.md, docs/target-app-parity.md.
The sweep filtered by transparency kept every open box in one tree, so
a box without transparency met every open box without transparency it
overlapped, though such pairs never join. Both sweeps closed an open
box early only when a later box held it, so in a nest of boxes that
share their left and top edges and get smaller to the right and
bottom, each box met every earlier box around it. One see-through
object inside 512 nested opaque objects took 130,817 meets in the
unfiltered sweep and 131,328 in the filtered one.
The filtered sweep now keeps open see-through boxes and open opaque
boxes in separate trees. An opaque box looks only in the see-through
tree; a see-through box looks in both.
In both sweeps, a box inside an open box it met, ending no further
right, is not opened when that box joins everything it overlaps (any
box without the filter, a see-through box with it). Whatever later
overlaps the inner box overlaps the outer one.
The nest above now takes 512 meets in each sweep, and a nest of 512
boxes alone takes 511 in the unfiltered sweep where it took 130,816.
The groups are unchanged.
The Brush libraries row named four libraries. It now names the nine in
BUILTIN order (crates/engine/src/cmd/brushlib.rs), as the Window ›
Brush Libraries menu shows them, and says nine built-in libraries.
A double-click on a paragraph style's row and the panel menu both open
Paragraph Style Options (ui.paraStyleOptions, paraStyle.edit) with its
seven sections. The type-parity and ui-parity rows still called it the
rename dialog and named ui.paramDialog with paraStyle.rename; they now
name the dialog, its sections and its tests.
The target-app-parity entry said each section has a Reset Panel. Reset
Panel is unavailable in General, and the entry now states that.
An image placed several times in an EPS file (the pieces of an envelope
or a Warp effect, copies of one image, a symbol's instances) wrote its
whole ASCII85 data at every placement: 200 copies for one envelope at
the default fidelity, enough for a large photo to pass the 256 MB page
limit and lose pieces.
The samples of such an image are now written once in the setup as
VCim<i>, and each placement reads them from there:
- Level 3: a ReusableStreamDecode stream, rewound with resetfile and
read through a new FlateDecode filter at each placement.
- Level 2: an array of strings of at most 49,152 compressed bytes, fed
to RunLengthDecode by a procedure.
An image placed once is written where it is drawn, byte for byte as
before. The page limit counts a repeated image's samples once, and a
colorized image is reused per ink.
A Fit to Artwork Bounds step of `command.batch` that names neither
`index` nor `indices` now fits the artboards the Artboard tool has
selected when the step runs (two or more), as the command does when the
app runs it on its own. Before, the step took the batch's `artboard` as
its `index`, so an action played from the Actions panel fitted only the
active artboard while several were selected.
- A step's own `index` or `indices` is kept, and the batch no longer adds
an unused `index` to a step that names `indices`.
- The batch's journal entry records the `indices` the step took, so a
replay fits the same artboards. An action leaves them out and fits the
artboards selected when it plays.
Docs: command.batch params text, docs/mcp.md, docs/target-app-parity.md.
- With art, the bar is 10 pt tall and the Edit tab leaves out the gaps
above the wheel and above the sliders. The dialog is as tall on the
Edit tab as on the Assign tab, and all of it but the color chips fits
a 1024 by 576 screen.
- recolor.weigh counts a use for the first row that is not excluded
when an excluded row lists the same color first.
A separator that ends a group of the Control bar's controls is now painted
only when a control follows it in the bar. When the groups after it are off
in the panel menu, do not apply to the selection or the tool, or have no
room in the window, the bar ends with its last control. Two separators with
nothing between them (the artboard Transform link and the artboard Align
group, or tool options and the envelope controls) show as one. Each
separator keeps its room, so the groups keep their places and widths.
- Fill and Stroke, Opacity and Graphic Style, Character, image and Image
Trace options, the Mirror & Cut, Crop Image, Puppet Warp and shape tool
options, the artboard Transform link and the envelope controls draw
their outer separator through group_separator. Paragraph and area type
vertical alignment end through group_end, which draws its separator
through boundary.
- Regression test: no_separator_is_left_at_the_end_of_the_control_bar
sweeps window widths from 300 to 1500 points for each case.
A built-in brush library's panel now shows each brush's name in the UI
language in its list rows and tooltips, and Find matches either the
shown name or the English name. User Defined and Other Library...
libraries, and the document's Brushes panel, keep names as stored. A
click, a drag, Add to Brushes and brush.addFromLibrary still use the
English name, so the brush comes into the document under that name.
The shared library panel gets a label hook for this, and its rows take
the library id. The 16 built-in brush names that had no catalog row
(Notched Ribbon, Double Line, Wave, Teardrop, Striped Ribbon, Fine Nib,
Broad Nib, Chisel, Round Pen, Pressure Pen, Beads, Dash Dot, Scallops,
Stars, Triangles, Sparkles) get a row in each of the 27 catalogs, and a
test requires a row for every built-in brush name.
A paragraph format given to the empty paragraph after a frame's last
paragraph break was lost when the story flowed again: the paragraph is
shared with the next frame's first paragraph, and the next frame's old
style replaced it, in that frame and in the frames after it.
- The thread reflow also notes which frames' last paragraph changed
style, and story_paras_restyled keeps that style against the next
frame's unedited first paragraph, across unedited empty frames too.
When the same edit formats the next frame's first paragraph, that
format applies, as before.
- story_paras keeps its behavior for its other callers.
- Tests cover area type and type on a path on either side, a next frame
holding text, three frames, undo and redo, a format that changes
nothing, and numbered list counts.
plugin.install wrote the module and its grant to the Plug-ins folder,
attached the plug-in's settings and cache and turned a disabled plug-in
on before the registry's last approval check. When another session
revoked the grant, or recorded one for other bytes, after the command
recorded its own grant and before that check, the install was refused
but left the new module and grant in the folder for the next launch.
A plug-in another session installed under the same id between the
command's checks and the install took no part in the decision, so the
install replaced it without `replace: true`.
- registry::install_checked records the grant (or forgets it) and
installs the plug-in as one step, after a caller check that sees the
plug-in installed under the id and its grant; a refusal changes
nothing.
- plugin.install commits through it before any store write, refusing
with the existing messages when another plug-in now needs `replace`
or the grant its approval came from changed. The store writes, the
put-back on a failed write and the disabled-list update now run after
the commit; their logic is unchanged.
- The grant call-site audit also counts registry::install_checked.
Docs: docs/plugins.md, docs/target-app-parity.md.
Insert Menu Item… now lists the Object › Align items and the View menu
commands the app runs itself (Outline, Pixel Preview, Trim View,
Presentation Mode, Screen Mode, zoom, fit, Rotate View, and the show,
hide and snap items).
- An Align step holds the params its click sends at insertion, so a
later Align To choice does not change it. A step that aligns to the
key object stops the playback when the selection has no key.
- A View menu step plays in its place at every speed and adds no undo
step. At Accelerated the steps on each side of it are one undo step
each. Playing it while recording records it.
- Batch… refuses an action with an included View menu step.
- An action set file with a View menu step is version 6.
- The steps before a stop now play as saved steps, as other playback
does, so a key that an older version recorded no longer fails there.
Docs: docs/mcp.md, docs/target-app-parity.md.
A stroke weight set with stroke.set or stroke.weightUp/weightDown, a
gradient made, placed or edited with paint.editGradient,
paint.setGradientGeom or the freeform gradient edits, and colors changed
by paint.invert, paint.complement, the Edit Colors recolor and Blend
commands, recolor.apply, addon.color.filter,
addon.color.applyNearestSwatch or the ink limit of
addon.color.printFixes changed a series mark but not the paint the graph
stores, so the mark went back to the old paint when the graph was drawn
again.
appearance::edit_items now stores what its edit changes on the topmost
visible fill and stroke of series marks, as the Appearance commands do,
and the recolor pass does the same for the series marks it reaches,
including those inside a series group or a graph it is given. When one
command changes several marks of a series, the frontmost one sets the
stored paint. A gradient is stored with its placement.
These commands do not store the colors they give a graph's axes and
labels. docs/mcp.md, the params text and the Graphs line in
docs/target-app-parity.md say what is stored.
The Materials tab and threeD.mapGraphic now map graphics onto
Revolve's swept side, unrolled flat: x runs around the axis (the
angle times the profile's distance from the axis at that point) and
y along the profile. A graphic keeps its size at every distance from
the axis, so it reaches farther around where the profile comes nearer
the axis. Each face of the side draws the part that falls on it: a
graphic narrows toward the silhouette, follows the profile along its
length, is shaded with the surface (several lights included) and is
hidden where the side faces away. A part that runs past the end of
the profile reaches the curved rim and stops there. The middle of the
side faces the viewer with the axis on either edge. On a compound
profile or a group the graphics go on the longest contour.
- three-d: with Revolve::map_surface set, side faces carry a
SurfaceCell (corners on the page and on the unrolled side, whether
the outside faces the viewer) and a closed profile off the axis
takes an anchor where its side is cut; without it the faces are as
before. revolve_surface_size gives the side's size for a new
graphic's fit.
- effects: Revolve takes `graphics` (front only); surface_parts cuts
and maps them per face; curves are cut into straight pieces one at
a time, and commands refuse graphics past the point limits;
switching between Extrude, Plane and Revolve keeps the front's
graphics.
- The Materials tab hides Face for Revolve; params text, docs/mcp.md,
docs/three-d.md and the progress docs describe the change.
- i18n: the complete-message catalogs translate the three new
refusals in place of the planar-faces refusal they replace, and ja,
es, zh-hans and zh-hant translate the Materials tab's label.
- recolor.weigh drops another row's entry for a color that moved, so
recolor.apply recolors it with the row it moved to and the shares
describe the recolor. A color that the first color of another row
with a new color also lists stays where it is.
- Linked swatch colors are grouped through a map, so a row with many
unlinked colors no longer compares each one with every group.
- The Prominent Colors bar reads the shares again when a row gains or
loses a new color, since that changes which row takes a shared color.
A row held in the 28-point band at the list's top or bottom edge scrolled
the list by a fixed distance each frame, so the list moved farther in the
same time on a display that redraws more often.
`scroll_at_edges` now scrolls by the time each frame took: 60 points a
second at the band's inner side and 340 at the edge. A stalled frame counts
a tenth of a second at most. The scroll is applied without the scroll
animation. The Libraries panel's list uses the same function.
Test: `panels::layers` test
`a_held_row_scrolls_the_list_as_far_in_the_same_time_at_any_frame_rate`
holds a row for half a second in 15 frames and in 60 frames and compares
where the rows come to rest.
Docs: docs/target-app-parity.md.
A whole pick from a source with no fill row, or no stroke row, removes
that row from the target. When the target is a graph series mark whose
row had paint, the pick now replaces the row opacity and blend mode the
graph keeps for that kind of row in the series. A row that had no paint
before the pick still leaves the kept values as they are, and a pick of
some attributes from such a source is unchanged.
Part of #2224.
A Smooth tool drag only refitted the anchors in its reach within
Fidelity, so a wobble wider than Fidelity stayed, and a second drag over
the same place changed nothing. Repeated refits also drifted away from
the path, and a drag over a whole closed path left a corner at its first
anchor.
- path.smoothRegion now smooths the stretch in reach along its length
with a Gaussian whose spread is a third of the reach, then refits it
within Fidelity. The run's outer end points and handles stay fixed.
- Each refit stays within twice the distance the smoothing moved the
path (at least a tenth of Fidelity), so repeated drags do not drift
(SimplifyOptions::smooth_moved_only).
- A whole closed path is smoothed across its start as a loop, so a
bump at the first anchor is smoothed like any other and no corner is
left. Object > Path > Smooth smooths a closed path across its start
the same way.
When a painted live effect on a fill or stroke returned several fills or
strokes, the next effect in that stack ran only on the first of them and
the others took its geometry. A later live-effect plug-in, and a later
geometry effect on strokes, now runs once on each of them in paint order,
given the piece's fills and strokes and its own `_context.item`. Each
run's pieces replace only that fill or stroke; a run that fails, is
refused, is deferred or would pass the effects' budget leaves only its own
fill or stroke as it was. Fills and strokes that keep the same path stay
on one path, and a deferred run is listed at the source fill's or
stroke's place.
The results that are kept share the effects' budget, and each run's
result is kept or left out whole. They are first placed with all the
room the art leaves, and placed again with room kept for the piece's path
when a fill or stroke left on that path finds no room. A result on the
path before it costs nothing and is taken whatever room is left; a
built-in effect that finds no room is built again with all the room the
art leaves, and that result is taken only when it is on the path before
it. A plug-in result is held only while it is placed: one that does not
fit, or that moves onto the path before it, is let go at once, so the
results held at one time stay within the budget however many fills and
strokes the piece has. A result kept in the first placing is reused in
the second without running the plug-in again. A painted piece's share of
the budget is counted once at each later effect.
A group's or layer's geometry effects now run after its members' own:
the geometry effects of each visible path and compound path in it and
of their fills and strokes, and a nested group's geometry and
Pathfinder effects, are applied first, at any depth, and the group's
effects reshape the result. A symbol instance's geometry effects run
after those of its symbol's members in the same way. Before, the group
reshaped the members' outlines and their own effects ran afterwards:
a 100 pt square with its own 10 pt Offset Path in a group with a 50%
Transform drew 70 pt wide, and it now draws 60 pt wide. Existing
documents with such groups draw differently.
The canvas, export (including groups that keep their type), Expand
Appearance and the art that Pathfinder operations and compound shapes
take from such a group use the same order. The reply of
effect.expandAppearance names such a group and no longer names the
members whose effects the group's art already holds.
The renderer now drops the art it keeps for a group when a plug-in is
installed or removed and a member of the group has a plug-in effect.
Docs: docs/target-app-parity.md. Not yet: the own geometry effects of
images, symbol instances, paths with a linked opacity mask or a 3D
effect, and nested groups with a 3D effect in a group still run after
the group's.
With the Selection tool, the Tabs panel showed "No text selected" when
the first selected object was a shape, or a group holding text, even
with text selected. It now shows the ruler and stops of the first
paragraph of the first selected text object, looking into selected
groups as the Character and Paragraph panels do. A stop set on that
ruler goes to every selected text object, as text.tabs.set already did;
other objects are left as they were. A selection without text still
shows the empty state.
With Real-time Drawing off, a command that ends a drag first (Undo, Redo,
Clear History, a plug-in command and the others) applies the drag's
deferred preview, but dropped the warning that preview gave, which
releasing the drag shows in the status bar.
- The applied preview's warning is now kept while a top-level command or
the tool's actions run. A command's result carries it as
`previewWarning`, and as `warning` when the command has no warning of
its own, so the status bar shows it.
- Typing kept as its own step when a tool's drag begins, or when Cmd
lends the Selection tool, shows the warning in the status bar.
- The Cmd-lend path no longer ignores an error from keeping the typing.
Docs: docs/mcp.md (undo/redo row), docs/target-app-parity.md.
The Pencil and the Paintbrush send their Fidelity in points at the
current zoom, but path.freehand read at most 100 pt. At 3.13% zoom a
20 px Fidelity (639 pt) was fitted at 100 pt, the same as 3.13 px on
screen, so a stroke drawn zoomed out kept far more anchors than the
same stroke at 100%.
- path.freehand, and brush.freehand, which runs it, take a Fidelity
from MIN_FIT_TOLERANCE to MAX_FIT_TOLERANCE (100 / 0.0313 pt, the
100 px field maximum at the lowest zoom). A value outside that range
is now rejected with an error instead of being read as the nearer
end. The default stays 1.5 pt and path.blob keeps its 100 pt bound.
- The Pencil and the Paintbrush keep the value they send within that
range, so a stroke draws at any zoom.
- An extending stroke still closes the path within four Fidelities of
its other end, at most 400 pt, as before.
It conflicts with open #7089 in crates/tools/src/draw2.rs.
Docs: docs/mcp.md, docs/target-app-parity.md, path.freehand params.
learn.skills {} shows the Learn panel on its Skills tab with the list of
skills, with no skill and no tutorial open, and returns what learn.list
returns. Back to skills after a skill's last tutorial and the Quick skill
guide card on the For you tab now run it, so the control channel and MCP
can reach the same view.
- The command is listed in both UI command catalogs, and its label has a
row in every catalog.
- Docs: docs/mcp.md, docs/target-app-parity.md, docs/ui-parity.md.
On Linux the Paste menu's background check holds its own clipboard
handle. When the app quit while that check's X11 text read was still
unanswered, quitting stopped waiting for the check's thread after 5 s,
but the handle's read worker holds arboard's handle until 25 s after the
read was sent. The app's own handle was then dropped first, so it was
not the last one alive to hand what was copied to a clipboard manager.
- CLIPBOARD_PROBE_STOP_WAIT (27 s, exported from vectorcraft-ui-egui)
replaces the 5 s bound: the 25 s X11 read limit plus LOAD_WAIT, the
2 s limit on waiting for a reader to stop. Quitting still ends after
it when a host's handle never lets go.
- On Linux and the BSDs, on_exit hides the main window before eframe
drops the app, so the window closes at once while the process waits
for the clipboard handles. The window is hidden, not closed, and the
connection the clipboards use stays open. winit hides X11 windows,
XWayland ones included; it cannot hide a native Wayland window, which
stays on screen until the wait ends. macOS and Windows are unchanged.
- Probe's Drop waits through an injectable function, so a test drives
the wait with a controllable clock while the probe's handle is still
being dropped and checks that it is dropped before the app's.
- A Linux test in the app checks the bound against X11_READ_LIMIT and
LOAD_WAIT, so raising either fails it.
- Comments and docs/development.md describe the wait.
A text chunk placed by an absolute x was measured from the pen of its
first character to the farthest glyph end. The first character of a
right-to-left chunk is drawn at its right, so a chunk of several such
characters was measured as that one character: anchored by middle or
end, or starting a line beside absolutely placed characters, it opened
off its position by the width of its other characters.
- A chunk's left and right edges are the smallest glyph start and the
largest glyph end over all its characters, as SVG 2 anchors a chunk
(https://www.w3.org/TR/SVG2/text.html#TextLayoutAlgorithm). The first
character's pen is placed at its own distance from the left edge.
- The layout that measures the chunks has the direction the imported
text gets: left to right unless the text's direction is rtl. Text
starting with a digit and holding right-to-left letters was measured
right to left.
- The chunks are measured in the first of the layouts that kern the
characters onto their x, so anchored text is laid out once less.
A live-effect plug-in that paints its pieces draws each piece's brushed
stroke along the piece's path at the piece's stroke width. Culling left
room for brush art from the object's own stroke width around the
pieces, so brush art of a moved or wider piece was cut off at the edge
of the view. Culling now leaves 16 x the piece's stroke width + 8 pt
past each brushed piece, and does so for a symbol instance whose art
holds such a path (measured once per symbol) and for a blend, envelope
or repeat that holds one. Object > Rasterize measures the brush art
drawn along each piece and leaves out the object's own brush art, which
is not drawn.
Groups and layers now leave room for their members' brush art. The
cull bounds of a group or layer united its members' bounds without the
room each brushed member leaves, so the painted bounds of a group, and
the area redrawn when it changes, left out brush art past its members'
strokes. That applied to plain brushed paths as well as painted pieces.
Each member is now measured as the renderer measures it when it draws
the group, in the members of a group with a fill, stroke or adjustment
of its own and in a group with crop marks or Vector Halftone too.
A toolbar made with New Toolbar now docks in a lane along the window's
left or right edge (on the left, right of the Tools panel docked there).
Dropping its title bar on an edge docks it, with the target lit while
the title bar is dragged over it; dragging it out of its lane floats it
under the pointer, and Escape or losing the window's focus during the
drag puts it back. The title bar's context menu has Dock to Left, Dock
to Right and Float Toolbar.
- Toolbars docked at one edge stack from the top, each with an equal
share of the lane's height, and the lane is as wide as its widest
toolbar. A lane too short for its stack wraps it into more columns,
up to half the width beside it; past that the toolbars show only
their title bars, a lone toolbar included, and a column too short
for those scrolls with the mouse wheel.
- With Keep Tools Beside the Canvas on and Tools docked on the left,
the left lane is laid out inside the canvas panel right of Tools, and
the canvas keeps room for one column of it and its 200 points.
- A release in the frame the pointer leaves the lane floats the toolbar
with its corner where the drag put it.
- window.toolbar.dock {name, side?, order?} and window.toolbar.float
{name, x?, y?} do the same from the control protocol; bad params are
errors and change nothing. window.toolbar.list reports floating, side
and order.
- CustomToolbar keeps dock (place in the edge's stack) and dock_right;
both default, so toolbars saved before load floating. Workspaces and
preferences keep the placement; New Toolbar still starts floating, a
copy floats, and deleting a docked toolbar closes up its stack.
- Dock to Left, Dock to Right, Dock Toolbar and Float Toolbar have rows
in every catalog.
Part of #1759
Offset Path now turns the overlap sweep the way Outline Stroke and
Unite do. When the estimate for its fill, the ring stroked around it
and the outline of its open subpaths is under two thirds as large for
the parts turned a quarter turn, the faces are found on the turned
parts, the faces within the offset distance of the path are dropped by
testing their points turned back, and the result is turned back.
On an open path of 10,000 anchors in rows of 1,000, offsetting by 2 pt
takes 1.0 s of processor time instead of 1.8 s in a release build.
The offset of a long, low path has as many subpaths as the offset of
the same path stood on end, and the area where the two differ is no
larger than the refit precision times the outline's length.
With an action selected, Begin Recording records into it: Stop puts the
recorded commands after the selected step, or after the action's last
step, and the steps after them keep whether they are excluded. With no
action selected it still adds a new Action N.
- ui.action.record takes {set?, action?, after?}; action needs set.
The place stays between the same steps while a step is inserted,
deleted or moved in that action during the recording, and the selected
step and an open Record Stop dialog follow their step when Stop adds
the recorded steps before them.
- New ui.action.moveStep {set, action, step, toSet?, toAction?, before}
moves a step within its action or into another action, also an empty
one or one in another set. The step keeps its params and exclusion,
the selected step and an open Record Stop dialog follow their step,
and a move past 10,000 steps or 8 MiB is an error that changes
nothing.
- In the Actions panel a step row drags: a line shows where the step
goes, the release moves it once, and a release off the actions' rows
leaves it where it was. The list scrolls near its edges.
Help page: https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/add-commands-to-actions.html
Docs: docs/mcp.md, docs/target-app-parity.md.
An Eyedropper pick that includes transparency now counts a fill or stroke
row that had paint before the pick and has none after it when it stores
the series' row opacity and blend mode on the graph, as graphic styles
already do. Before, such a pick left the opacity and blend mode another
mark of the series had stored, and the series was drawn with them again
after its data changed. A row that had no paint before the pick still
leaves the stored values as they are.
A top-object envelope gives each piece of a side a share of the content
in proportion to its control polygon's length. When anchors split a
curve without changing its shape, the pieces' polygons don't divide the
side where the curve's own parameter does, so the envelope bent its
contents differently although its outline was the same: Add Anchor
Points on a top object moved the art, and a top object whose .ai mesh
saves each side's curve split into thirds drew its type up to about
half a point off its drawn art, which follows the whole curve's
parameter.
Coons::from_top_object now checks whether a side's pieces, from corner
to corner, are all pieces of one cubic. The pieces' spans of that
curve's parameter come from the handle lengths either side of each
split, the curve from the outer ends and handles, and every piece must
lie within 0.02 pt of the curve's piece over its span (each control
point). Such a side runs along that curve, by its parameter. Any other
side keeps the polygon shares, including a side only partly made of one
curve and a smooth side drawn in many pieces that isn't one curve as a
whole. Corners found from the outline can still move when anchors are
added, as before. Gradient meshes made from a path are unchanged.
Existing top-object envelopes with such sides redraw.
a_split_curve_moves_the_top_object_share_to_its_new_anchor, which
pinned the earlier behaviour, becomes a_split_curve_keeps_the_top_object_patch.
Tests: a_split_curve_keeps_the_top_object_patch,
a_top_object_joins_a_split_curve_saved_with_rounded_coordinates,
a_top_object_keeps_smoothly_joined_curves_apart,
a_top_object_side_partly_of_one_curve_keeps_the_polygon_shares and
a_top_object_side_of_many_arcs_stays_on_them (vectorcraft-doc),
envelope_top_object_keeps_its_look_when_anchors_are_added
(vectorcraft-engine) and
a_top_object_whose_top_is_one_curve_split_in_thirds_opens_live
(vectorcraft-eps).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While layer or object rows are dragged in the Layers panel, holding the
pointer within 28 points of the list's top or bottom edge scrolls the
list that way, faster nearer the edge, and it keeps scrolling while the
pointer stays still, so a row out of view can take the drop (a move, or
a copy with Alt). The Libraries panel's edge-scroll helper moves to
panels.rs and both panels use it. Scrolling stops on release, when the
pointer leaves the list's width, and for other drags such as the eye or
lock column.
Where arboard has neither data-control nor X11 (the Flatpak on a GNOME
Wayland session), Copy and Paste used smithay-clipboard on the window's
connection, which carries text only. They now use a core wl_data_device
client on that connection.
- One Copy offers the PNG as image/png, the SVG as image/svg+xml (with
Include SVG Code on) and the text as text/plain;charset=utf-8,
UTF8_STRING and text/plain. A format with no data is not offered.
- Paste reads SVG, then text, then PNG. Blank text, or text that only
names a picture, gives way to the PNG, and a failed or empty SVG read
falls back to the types after it. The Paste menu item is enabled by the
types offered, without reading any data.
- The clipboard holds VectorCraft's Copy while its data source is not
canceled and the offer has the Copy's types.
- A read takes 64 MiB at most. Text waits 2 s, SVG and PNG 10 s off the
UI thread. Another app's paste is sent on its own thread, 8 at once.
smithay-clipboard is no longer a dependency of the app.
A PDF-compatible .ai saved with Include Editing Data for Other Apps now
writes warp, mesh and top-object envelopes as live envelope records in
its editing copy: the grid or contents, Envelope Options, Fidelity,
Edit Contents, a warp's settings and the contents' box, beside the
distorted art, in the form the .ai envelope reader reads. The file
opens with the same live envelopes.
Envelopes the reader would open in another shape or with other
settings are written as their distorted art with a warning: type in
the contents, a turned frame, an old warp map, a mesh laid out another
way round, a top object whose sides do not pair up, a Fidelity that is
not a whole number, bounds margins kept with Distort Appearance off,
envelopes in opacity masks, pattern tiles, clipped art or another
envelope's contents, on a large canvas, and after the first 256.
A Linear pick that the Smart Guides left where the pointer already was,
for example straight below the first point on the vertical construction
guide through it, or exactly in line with an object's center, was taken
as not snapped. The pick then moved to the closest point of a segment
within 8 pixels while the guide still showed it on the guide. The same
happened to a point already on a grid crossing with Snap to Grid on, or
on a whole pixel with Snap to Pixel on.
The pick now keeps the snapped point whenever the snap shows guides or
grid or pixel snapping is on. With Snap to Grid or Snap to Pixel on, a
pick off an anchor is no longer moved from the grid or the whole pixel
onto a segment. A point that no
anchor, guide, grid or pixel snapping places still lands on the closest
point of a segment. An anchor within 8 pixels, a segment within the
Smart Guides snapping tolerance and a nearby guide crossing still take
the point as before.
Docs: docs/dimensions.md, docs/target-app-parity.md.