A compound path was assembled from its members in every frame it was
drawn, and Node::stroke_path grew its buffer by doubling, copying the
path into ever larger buffers for a compound path of many anchors. The
renderer's geometry cache was swept only once it held more than 1,024
entries, so while an object was dragged (a new node, and layer, in
every frame) the old copies stayed alive with all their members.
Now:
- a compound path is drawn from the path the renderer keeps for it in
its geometry cache, as a plain path already was, and is assembled
only when the node changes;
- Node::stroke_path sizes a compound path's buffer up front from its
members' segment counts (the same elements, in the same order);
- unused geometry entries are dropped after a few frames however few
entries there are.
Tests:
- crates/render/src/tests/memory.rs: the second frame draws the kept
path and the same pixels as the first and as a new renderer; after a
five-frame move the renderer no longer holds the compound path drawn
before it. The test fails without the renderer change.
- crates/doc/src/node/tests.rs: a compound path's path is its members'
paths in order (open, closed, empty and single-anchor members).
Not timed; vectorcraft-cli perf was not rerun.
Every click and hover measured each path and compound path along its
curves to cull it (Node::reach_bounds_with), and a compound path was
then assembled whole to test its outline and fill. For a compound path
of thousands of members that took milliseconds per click.
Hit testing now culls paths and compound paths by the box of their
control points (which holds the curves), grown by their strokes' reach,
so the box is never smaller than the reach box it replaces. A compound
path's outline and fill are tested against only the members whose
control box, grown by the tolerance, holds the point: a member outside
that box is farther than the tolerance and winds zero times round the
point. Painted strokes still follow the whole compound path (alignment,
dashes, width profiles) and are measured along all of it, as before.
Tests (crates/doc/src/hit/tests.rs):
- a 2,000-member compound path hits exactly as the whole-path test did,
at random points, on and next to anchors and on the edges of the
members' boxes, for both fill rules, in Outline view and with Object
Selection by Path Only;
- wide strokes painting past the control points are still hit, for a
path and a compound path;
- the cull box holds the reach box for paths and compound paths.
Measured in a debug test build (one run, machine not idle): a click on
a 20,000-member compound path went from about 18 ms to about 1 ms.
vectorcraft-cli perf was not rerun.
Apply wheel steps over the closed family fields in Character, Properties and Control, preserving event order and one undo step per effective notch. Document the supported controls and the application choices. Fixes#5253.
Preferences > User Interface > Open Documents As Tabs was stored but did
nothing. With the box cleared, each document opened or created afterwards
opens in a floating window of its own, at the next cascade place, as
Window > Arrange > Float in Window floats it. Documents already open, and
documents open when the app starts, keep their tabs; checking the box
again leaves floating documents in their windows.
The engine records the preference on each document as it opens
(DocState::opened_as_tab), and the UI finds new documents by uid, so a
command batch that closes and opens documents, or changes the preference
between opening them, places each one as the preference was when it
opened.
A document tab dragged more than 24 pt above or below the tab strip
floats its document in a window under the pointer and makes it the
current document (window.arrange.floatInWindow takes index and at for
this). A shorter drag, or one along the strip, leaves the tabs as they
are. A document window dragged by its title bar onto the tab strip goes
back under the tabs; the strip shows an outline while a window is over
it. A desktop document window is an OS window, so it has the document's
tab at its top: dragging that tab moves the window, and dropping it on
the main window's tab strip docks it (window.arrange.consolidateAllWindows
takes index for one document).
A document floated again opens where it is asked to, not where its last
window was.
Window > Arrange > Float in Window (window.arrange.floatInWindow) moves
the current document out of the tab strip into a floating window of its
own, and Float All in Windows (window.arrange.floatAllInWindows), a new
item, does so for every tabbed document. Cascade (window.arrange.cascade)
floats every document and stacks the windows from the upper left of the
document area to its lower right, 30 pt apart, in tab order. On the
desktop each window is an OS window; on the web and on Wayland it is a
window inside the app's window, as floating panels are. While the
control channel is attached, the windows also open inside the app's
window, so ui.click, ui.drag and ui.screenshot reach them.
The current document's window holds the canvas. The other windows, and
the main window while the current document floats, draw their documents
as Tile's panes do, and a press in one makes its document the current
one. A document's OS window that takes the keyboard focus makes its
document the current one, and keys typed there run the main window's
shortcuts. Closing a window closes its document, asking to save unsaved
changes. Consolidate All Windows brings floating documents back under
the tabs, and Tile brings them back into its grid.
Cascade is enabled with two or more documents, Float in Window while the
current document is tabbed, Float All in Windows while any document is
tabbed, and Consolidate All Windows while documents are tiled or
floating. ui.inspect reports the floating documents.
While a symbol with Enable Guides for 9-Slice Scaling on is edited in
symbol editing mode, the canvas draws its two horizontal and two vertical
guides as dotted lines across its art, with any tool and with ruler guides
hidden. With the Selection tool the pointer near a guide shows the Move
cursor, and dragging the guide sets it through `symbol.options`'s
`nineSliceGuides` as one undo step; Escape during the drag puts it back.
A guide within 4 screen pixels takes the press before the art and the
bounding box. A dragged guide stays within the art's box and stops short
of the opposite guide; positions outside the art are not supported yet.
A Transform effect in a fill's or stroke's own effect list now applies its
Scale Strokes & Effects and Transform Patterns options as the same effect on
the object does: the stroke's weight and dashes, the distances of the fill's
or stroke's later effects and its pattern follow the transformation, each
copy once more than the last, and with Transform Objects off the geometry
stays and the options apply once. The fill or stroke becomes an object of
its own in the art the canvas, export and Expand Appearance draw, and Use
Preview Bounds measures that art too (also for the object-level options).
Part of #885
Window > Arrange > Tile (window.arrange.tile) splits the document area
into one pane per open document, edge to edge, in a grid with as many
columns as the square root of the number of documents, rounded up. Each
document is fitted to its pane, and fitted again when its pane changes
size (a document opened or closed, the window resized) unless it was
zoomed or scrolled since. The current document's pane is the canvas;
the other panes draw their documents with their artboards, guides,
selected objects and Pixel Preview, and a press in one makes its
document the current one, as a click on its tab does. Window > Arrange >
Consolidate All Windows (window.arrange.consolidateAllWindows) goes back
to one document under its tab. It works the same on the desktop and the
web, and the arrangement is not saved.
Tile is enabled with two or more open documents, and Consolidate All
Windows while documents are tiled. ui.inspect reports tiled and the
panes.
#5057 opens ruler guides and guide objects from a .ai's editing data and
closed#779. The File formats row of docs/target-app-parity.md, the known
failures in docs/file-format-parity.md, G9 in docs/gaps.md and
docs/roadmap.md still listed guides as lost.
A menu under each of the five tiles in Pattern Brush Options lists None,
Original and the document's pattern swatches; the corner menus keep the
four automatic corners. A swatch choice copies the swatch's whole art and
its tile frame into the brush, so the brush keeps drawing after the swatch
is deleted, and the menu then shows that art as Original. Side cannot be
None: OK shows "You must choose a side pattern." and keeps the dialog open.
brush.options takes side_tile, outer_corner_tile, inner_corner_tile,
start_tile and end_tile (a pattern name, none, original, the corner auto
ids, or {"pattern": name} for a swatch named like one of those words), and
rejects side_tile "none" and unknown names before editing.
- A composite (Type 0) font is noted by its descendant CIDFont's BaseFont
(ISO 32000-1 9.7.4.1), not its own BaseFont, which ends in "-" and the
CMap name (9.7.6.1, e.g. Name-Identity-H); without one, the CMap name is
taken off the end of the BaseFont.
- A font name's family is the longest part before a hyphen (or the comma of
Arial,Bold) that names an available family, so Noto-Sans-Bold is Noto
Sans, Bold and Noto-Sans-CJK-JP is Noto Sans CJK JP; a shorter family
still matches when no longer one is available.
- A missing font is split only at a comma or before the first part that is a
style word (Helvetica-BoldOblique); otherwise the whole name is its family
(MS-PGothic is reported as MS PGothic, not MS).
Synthetic tests (no host fonts): Families::of builds the family list.
# Conflicts:
# docs/file-format-parity.md
# docs/gaps.md
# docs/type-parity.md
The grid options' public page lists a Preview option: the repeat on the canvas follows the fields while the dialog is open. Object > Repeat > Options had none, so a change showed only after OK. Now a Grid Repeat's dialog has the checkbox: Cancel puts the original back without a history step, OK keeps the previewed values as one undo step. Radial and Mirror Repeat Options are left as they are, since their public pages list no Preview.
A mesh read from a file can have any number of patches, and the view any zoom: a 150 x 150 mesh from a 3 MB file exported at 20x asked for about 23 million quads (2 GB), and the command's own cap of 50 rows and columns does not apply to a loaded one. One tessellation now makes at most 1,048,576 quads, the subdivision per patch giving way before that, and a mesh with more patches than that is malformed, like a point count that does not fit.
Alter Paths for Appearance wrote every stroke as its filled outline, so a
1 pt circle became two circles and a hatch, and an open arc became a
closed outline and a hatch: two cut lines where a CAM program expects
one. The option now outlines only the strokes a line can't draw: the ones
Preserve Appearance outlines, strokes wider than the widest lineweight
(2.11 mm, to the hundredth), and every stroke in R13 and R14, which have
no lineweights. Other strokes stay CIRCLE, ARC, SPLINE and LWPOLYLINE
entities with their lineweights; R12 keeps them as POLYLINEs with their
width.
A dashed stroke whose offset starts part way into its pattern is now
outlined with Preserve Appearance and with Alter Paths: a linetype always
starts at the start of its pattern, so the dashes were written in the
wrong place.
A closed path of four cubics that is the four-cubic circle under an
affine map (an ellipse, or a circle scaled out of proportion) is written
as a native ELLIPSE in R13 and later, with its center, major axis and
minor to major ratio. The check maps the path back onto the unit circle
and runs the existing circle recognition on it. R12 keeps the existing
fallback, and ellipses with a ratio under 1e-6 stay splines. The ratio
and end parameter are written in full so that a thin ellipse keeps its
size and a whole turn reads back closed.
Part of #1299.
#4757 gives a blend the Xy written after its record. When the blend is a
layer's last object, the layer's own state block (Xy, Xd, 7 () XW, LB)
went to the blend instead, and the layer stayed opaque. The blend's state
is now tentative like a mesh's (#4748): its own 6 () XW confirms it, the
layer's LB gives it to the layer and the blend keeps what it had.
Also records the .ai/EPS audit fixes under G9, the File formats row and M4.
People reuse the same gradients across documents and rebuilt them by
hand. The Gradient panel now has a Presets menu: the presets of the
app's built-in gradient libraries, grouped by library (theme), then
the saved ones. Choosing one gives the gradient behind the active
proxy (or a solid paint) the preset's type, stops, method and dither,
keeping its placement, as one undo step, through paint.editGradient.
The panel menu saves the current gradient as a preset (named in a
dialog) and deletes saved ones; Hide/Show Presets toggles the row.
Saved presets are kept with the preferences, next to the other user
preset lists (width profiles, flattener, perspective). Stops linked to
a document swatch keep their colour, unlinked, since a preset is used
in other documents. Built-in presets can't be deleted; names are
unique.
Commands: gradient.presets.list, .apply, .save and .delete. The menu's
rows are drawn only while in view, as its headings are.
Tests: engine tests for list, apply (fill and stroke, placement kept,
one undo step), save (unique names, unlinked swatch stops) and delete;
panel tests for applying from the menu and saving, deleting and
hiding presets from the panel menu, including scrolling the menu to
the saved presets. Docs: gaps G13, target-app and UI parity, ROADMAP.
Where arboard can't open (GNOME has no data-control protocol, and the Flatpak on a
Wayland session has no X11), Copy and Paste use the compositor's core data-device
protocol on the window's own connection through smithay-clipboard, already in the
build through egui-winit: text only, a copy's text or its SVG markup. A bitmap-only
copy reports that this clipboard carries text only and leaves it as it was.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every Preferences dialog option is read by shipped code, by its field or
its key, except five, each waiting on a subsystem of its own: Open
Documents As Tabs (documents in floating windows, G7), Units > East Asian
Type, Hyphenation > Default Language, Scratch Disks, and the legacy vector
clipboard. Highlight Alternate Glyphs, Real-time Drawing and the Devices
options, still listed as stored-only, act. Docs only.
The Flatpak on GNOME has no X11 (fallback-x11 on a Wayland session) and GNOME offers
no data-control protocol, so arboard::Clipboard::new() failed every time. The handle
was kept only on success, so the background Paste check reconnected to the compositor
and logged a warning every 250 ms: 267k warnings in 18 h, the 16 MB log full in 3 h.
- A clipboard that can't be opened is tried again after 1 s, doubling to a minute.
A Copy or Paste of the user's retries at most once a second.
- The outage is logged once (WARN) and its end once (INFO), across the UI's and the
background check's handles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merges kept prepending each side's whole summary as "previously: …", so
target-app-parity.md's status line had grown to 66 MB (839 copies of its
Target clause), gaps.md's and ui-parity.md's to over 1 MB. Each status
line now names its latest change only, as the progress-docs standard has
it; the revision histories keep the earlier ones.
CodeRabbit follow-up for #4293. Add 2026-10-26 revision-history rows and
status-line updates for EPS Save and Save As in file-format-parity, gaps
(G9), roadmap and target-app-parity; move the completed EPS Save note in
roadmap's M4 row out of "Left:". Save-back is recorded as done; G9 import
fidelity and Illustrator editing-data writing (#868) are unchanged. Scores
and estimates unchanged. Docs only.
A blend whose keys are blends draws each of its steps as a copy of a key,
and that copy makes its own steps when it is drawn, so step counts capped
per pair multiplied with nesting: five levels of 15 steps were 1.3 million
steps from a 23 KB file, 3.4 GB to export.
A blend now makes at most 100,000 steps in all (MAX_BLEND_STEPS), counting
the steps of the blends inside its keys and steps. Past the budget each pair
of keys keeps the same share of its steps; a copy of a blend spaced by
distance counts the most steps its spacing can make, since scaling it up
adds steps. Blends within the budget draw every step as before.
A document keeps up to 25 saved views, but the View menu listed only the
first 10, so views 11 to 25 could be saved and never chosen. All 25 are
listed now, by name, and choosing one changes the current window to its
zoom, centre and rotation (within the canvas's zoom range).
Edit Views… used to open the generic parameter dialog on the first view,
where the name of the view to change had to be typed. It now opens a list
of the document's views, like Edit Selection…: pick one to rename it or
delete it; OK applies every change as one undo step and Cancel leaves the
views as they were. view.saved.edit takes {edits: [{name, newName?,
delete?}…]} for that, the one-view form still works, and the command is
off while the document has no saved view.
A file's views are untrusted: on open at most 25 are kept, names are
trimmed, capped at 255 characters and unique, and views without a finite
centre and a positive zoom are dropped. New View… starts from the first
free "View N" and caps names the same way.
Behaviour from the public help page "Use multiple windows and views":
https://helpx.adobe.com/illustrator/using/viewing-artwork.html
docs/gaps.md (G7) and docs/target-app-parity.md take New View/Edit Views
off the missing list; docs/mcp.md documents the view.saved commands; the
catalogs get the Saved View 11-25 rows in the pattern of 1-10.