A document tab dragged sideways along the tab strip moves its document to the
place an insertion line marks; the current document stays current. Dragged
above or below the strip, the tab still tears off into a floating window. The
views Window > New Window opened move with their document, and each document
keeps its own zoom, centre and artboard.
The move is the engine command `document.move {from?, to}`, so the control
channel, MCP and recordings reach it, and a replay moves the document before
the steps recorded after it. `sync_views` keeps the views with their documents
by uid when the documents change order.
Behaviour: https://helpx.adobe.com/illustrator/using/customizing-workspace.html
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check that reuses a moved path's stroke outline allowed each
coordinate to differ from the moved one by 4 x f64::EPSILON times the
sum of the compared coordinates and the largest coordinate of the first
point, about 2.7 pt near 1e15, so a 0.5 pt anchor or handle edit there
drew the outline made before the edit.
offset_between and anchors_moved now measure the offset on each axis at
the pair of points with the smallest coordinates on that axis, then
check every pair against that offset within 3 units in the last place
of the pair's own coordinates. A first anchor far from the origin no
longer sets the tolerance for points near it.
Tests: 0.5 pt anchor and handle edits at 1e9 and 1e15 (and 0.0005 pt
at 1e9); an edit near the origin of a path that starts at 1e16; a path
whose coordinates shrink with each point, moved by growing amounts.
Additional Plug-ins Folder (Plug-ins & Scratch Disks) gets the Choose…
button that Data Recovery's Folder has, on hosts with a folder picker.
A picked plug-ins folder must be a folder whose contents can be listed;
otherwise the status bar says why and the field keeps its value.
OK now gives a folder typed into either field the same check as a
picked one. When it fails, the dialog stays open, the status bar says
why and no preference is set. An empty field and the folder already
saved are not checked. Under automation roots, a folder outside them is
refused before it is looked at.
Additional Fonts Folder and Templates Folder stay typed only.
A Type Tools add-on command (Object › Plug-ins › Type Tools › Release Type on a
Path…, `addon.type.releasePath`): each selected type on a path becomes point
type with the same runs, character and paragraph attributes, on one straight
line whose baseline starts where its first character sat, and the plain path
it was set on, with its exact shape and transform, in its layer and stacking
place, with no fill or stroke. `mode` keeps both, the path only or the text
only; `keepOriginal` leaves the type on a path and adds the released objects
above it. Other objects are left alone, nothing to release makes no undo step,
the run is one undo step and the released objects are selected. The command
dialog previews it as Type on a Path Options does (#5360): Cancel leaves no
history, OK is one step. Tests, command_sweep cases, catalog rows in all 26
languages, docs/mcp.md and the parity docs (beyond Illustrator: scores
unchanged).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Delete Stop button sat at the end of the Opacity/Location row, below
the selected stop's Color row. In the default docked panel height that
row is cut off as soon as a stop is selected, so the button looked
missing. It now sits at the right end of the ramp, level with it, and
shows whenever the ramp does (with options hidden too). It deletes the
selected stop, never below two, as before.
Adds a headless panel test for deleting a stop by dragging it off the
ramp and with the button beside it, one undo step each, and for a
two-stop gradient keeping both.
The task bar drops Join once the selected open path is closed and offers it
again after Undo, and Shape Properties shows a rectangle's corner radius as
it is after object.setLiveShape and after Undo. The progress line names the
new test.
A test selects an open path in one document and a rectangle in a new
one, then switches back and forth with set_active: the task bar offers
Join only for the open path and Shape Properties only for the rectangle.
The Control bar and the contextual task bar copied the first selected
object every frame (the task bar twice), which on a path of 200,000
anchors costs about 1.4 ms per copy. Both bars now borrow it; the task
bar copies it only when it is a basic shape, for Shape Properties.
With one selected open path of 200,000 anchors, an idle frame with both
bars shown takes 3.8 ms instead of 8.0 ms in a release build.
addon.pathtools.cornerSet takes `every` ("all", "odd" or "even"; default
"all"), which keeps, of the anchors already chosen (named, direct-selected
or all), only the odd- or even-numbered ones, counted along each path from
its first anchor as number 1. Anchors that take no corner still count, so
the pattern follows the path's anchor order on open and closed paths; when
nothing is left the command says so. Unknown values are refused.
Object › Plug-ins › Path Precision Tools › Set Corners… (also a button in
the Anchor Inspector's Corners section) opens a dialog with Radius, Corner
kind and Anchors (All / Odd-Numbered / Even-Numbered) that previews on the
canvas and commits as one undo step.
The outline cache kept a copy of each stroke's line to recognize a moved
path, and made that copy on every miss, so an anchor edit of a long path
paid for copying its whole line. The entry now records whether its outline
was made along a line of the object's own path. On a miss, the anchors of
the earlier object are compared with the new one's, stopping at the first
that differs, and the earlier line is made again from the object and its
stroke only when they match.
Strokes with arrowheads keep the reuse: the check uses the untrimmed path.
A shear, a wider stroke, another cap, another dash pattern and another
width profile each make one new outline and draw the pixels a new
renderer draws, for open and closed, short and long, plain, dashed,
profiled and gradient-filled paths.
The moved-outline key is imported where it is used, and the outline is
stored through one helper, so the change touches fewer lines that other
work on the renderer also changes.
A path object that moves without changing shape drew its stroke by
outlining the whole line again, because the stroke outline cache is
keyed by the node. The renderer now keeps, per object, stroke and
outline width, the latest outline with the line it was made from. When
the new line is that line moved, it draws the kept outline moved by the
same offset, and moves the region a long path was outlined in with it.
Scaling, rotating or editing the path, changing the stroke's width,
join, cap, dashes or profile, a zoom that changes the outline's
tolerance, and a move that takes the view past the outlined region
still outline the stroke again. Only the latest outline of each stroke
keeps its line.
A row drawn before was counted with no limit on its objects, so symbols
that each hold many instances of the next made the count grow with the
product of the instances, and the Layers panel could stall on such a file.
- Counting stops after 20,000 steps: each object, subpath, text run and
symbol looked at is one step, and every 64 bytes of a symbol or placed
file name looked up is one more. A row that needs more gets its
thumbnail on the thumbnail thread, and the painted thumbnail draws
nothing for it.
- A row drawn before counts at most 20,000 objects.
The Layers panel decided whether to make a row's first thumbnail on the
UI thread by counting path anchors and type frames only, so point type,
symbol instances, gradient meshes, images and placed documents counted
as one object with no anchors, and a heavy one was rendered on the UI
thread.
- small_node takes the row's document. Each glyph of type counts as 4
anchors, each mesh point as 1 and every 100 image pixels as 1, against
the same 2,000-anchor limit; glyphs are counted only up to the limit.
- A symbol instance and an inline graphic count their symbol's art, and
a placed document counts its file's art once the file has been read;
one not read yet goes to the thumbnail thread.
- Symbols and placed documents nested more than 8 deep go to the
thumbnail thread, so a symbol holding an instance of itself is counted
to an end.
- The painted fallback thumbnail counts as before.
A path or compound path whose Transform effect made copies expanded to
one compound path of every copy. It now expands to a group of one object
per copy, each painted as the path, as gradient copies already did; a
second Transform effect's copies nest in a group per copy of the first.
Drawing is unchanged.
# Conflicts:
# docs/effects-parity.md
A numeric field read a comma as a decimal point only when the text had no
point at all. A field shows its value with a point (100.5 pt), so typing
-20,5 after it gave 100.5 pt-20,5, which kept its comma and was refused.
Each number now reads its own separator: commas become points unless the
text reads as thousands (1,000, as before). A number written with both
separators (1.234,5, 1,000.5) ends up with two points and is still refused
rather than guessed at.
Locking or hiding the layer (or a group) around the isolated object left
its art selectable with the Selection tool: the isolation branch of the
hit test only checked for a locked artboard, and the marquee's checked
nothing. Both now pass over isolated art that isn't editable.
type_font_menu_lists_every_available_family read Type › Font while the
scan the app starts could still be running. The scan bumps the font
generation a moment before it counts as done, so a read in that gap lists
only the loaded fonts under the new generation, and the test's
"generation held still" check let the comparison with the full list fail.
The test now waits for the scan after making the app, as the native menu
tests do since #5653.
On a platform without a clipboard change count (macOS, Linux), a Type
copy that the system clipboard refused is kept only when the clipboard
held text when the write failed. A clipboard with no text, one that
couldn't be read, or one holding a picture can't show that another app
copied something later, so Paste no longer inserts the earlier copy
there.
When a fallback face draws Ka with the vowel sign I, the vowel sign's
glyph comes first in Ka's cluster, and text.glyphAlternates reported it
for Ka, laid out or overflowing. The query now finds the face that draws
the character and takes that face's glyph for it by the same rule as
for the requested face.
To find the characters that justification or Mojikumi moves, SVG export
without Fewer <tspan> Elements laid out every live type object a second
time, as point type with
Mojikumi None and default Justification options, whenever its paragraph
had Mojikumi on. New type gets Line-end Punctuation Half Width, so every
new type object was laid out twice. Mojikumi only spaces Japanese
punctuation and Japanese letters, so type without them lays out the same
either way.
- TextLayout::mojikumi_applies says whether the text or a list marker
holds a character Mojikumi spaces.
- reader_advances keeps such a paragraph's Mojikumi, and reads the glyphs
from the layout it was given when the object it would lay out is the
same; otherwise it lays the object out again as before.
The SVG is unchanged. Exporting 5,000 point type objects takes a median
of 109.4 ms instead of 171.5 ms on Windows 11.
When another app copies while VectorCraft's write is trying to open the
clipboard and the write then fails, Paste takes the other app's copy. A
change during the failed write counts as the write's own only when the
clipboard then holds this copy's text, or no text and either nothing to
paste or one of this copy's formats.
When the requested font has no glyph for a character, the text draws it
from a fallback face, and text.glyphAlternates reports that glyph. For
a character that overflows its area, the query shaped the character
alone but kept only a glyph of the requested face, so it reported the
requested face's default glyph (0). It now takes the first glyph drawn
for the character when the requested face draws none, as it does for
laid-out text.
A symbol instance whose fill has an opacity of 0 no longer counts as
stained. Expand Appearance and the other places that build an
instance's art used to rebuild every solid paint as an unlinked color
even though the tint changed nothing, so the art lost its global and
spot swatch links.
Ka, the halant, Ka again and the vowel sign I are shaped as one cluster:
the vowel sign, the first Ka's half form, then the second Ka's default
glyph. The glyph lookup looked for the character's default glyph across
the whole cluster before its alternates, so for the first Ka it found
the second Ka's default glyph instead of the half form. It again goes
through the cluster in order, and takes the first glyph that is the
character's default glyph or one of its alternates and is not the
default glyph of another character of the grapheme, so a vowel sign's
glyph that the font also lists as an alternate of the consonant is not
reported for the consonant.
When a Copy in the Type tool cannot be written to the system clipboard
(another app holds it open, or a format fails after the clipboard was
emptied), Paste into type or a text field now inserts that copy, with
its style in type, instead of the text the clipboard kept. This covers
Edit > Paste, the paste key, and a paste that arrives while the copy is
still being written. When another app copies something, before or after
the failed write, Paste takes that.
The new SystemClipboard::change_count reports the clipboard's change
count (Windows' sequence number). Where a platform has none, a change
of the clipboard's text marks a new copy.
The enabled check of artboard.deleteEmpty built the full list of empty
artboards, measuring the visual bounds of the art against every
artboard, each time the Artboards panel menu was drawn. It now returns
false at once in a document with one artboard (that artboard is never
deleted). With two or more, it asks the new
Document::any_artboard_without_art whether any unlocked artboard has no
art, which is the same answer as a non-empty list.
Document::any_artboard_without_art builds the symbol boxes once for all
the artboards and first tests each path and compound path by a quick
box: its control points' box grown by the farthest its strokes reach
(Appearance::outset), with a margin for rounding. An artboard that no
quick box meets is reported empty without measuring along any path.
Only when every artboard may hold art is each one measured.
Document::artboard_has_art also skips measuring an object whose quick
box misses the artboard; its answers are unchanged.
The check of gradient.presets.save found the proxy paints through
Node::proxy_paint, which measures the object's geometric bounds for a
gradient box the check does not use. Node::proxy_paint_only picks the
same paint without measuring, and the engine's proxy_paint uses it, so
the Fill and Stroke proxies and the mixed-paint test no longer measure
each object either.
With an open stroked path of 200,000 anchors selected, the two checks
take under 0.01 ms instead of 21.9 ms and 17.2 ms in a release build, and
Delete Empty Artboards' check takes 0.85 ms instead of 42.1 ms with a
second, empty artboard.
Tests compare each check with what its command does (one artboard, all
empty, one empty, locked empty, nested art; with and without a
gradient), compare any_empty_artboards with the previous check and with
measuring every object on 120 seeded documents, compare the quick boxes
with the visual bounds on seeded strokes, and compare proxy_paint_only
with proxy_paint on seeded objects.
Docs: docs/target-app-parity.md, one line under Delete Empty Artboards.
text.glyphAlternates reported the default glyph as `chosen` for any
character that overflows its area. A character whose style substitutes
another glyph (`salt`, or `vert` in vertical type) was reported with a
glyph it is not drawn with. The query now shapes such a character alone,
with the rest of its grapheme in their own styles and in the text's
writing direction, by the same helper text.setGlyphAlternate uses.
The helper also shapes the character in the direction its paragraph
sets it in, from its bidi level, so a parenthesis set right to left is
reported with its mirrored glyph in both commands.
The renderer keeps the path of a path or compound path of 1,024 anchors
or more by object id, with the bounds of its segments and of its miter
spikes in runs of 128 segments. When anchors move and the subpaths and
anchor counts stay the same, the next frame rewrites only the path
elements of the segments that start or end at a moved anchor, and
measures again only the runs that hold them and the runs of the
segments with length before them. Other edits make the path again as
before. The pixels and the cull bounds are the same.
- StrokeLayer::bounds_on is unchanged. The miter tip of one joint
(miter_tip) and the stroke box with given tips (bounds_with_tips) are
new functions beside it, and Appearance::stroked_bounds_by takes the
box of each shaped stroke, so the cull bounds of a kept path use the
kept tips.
- geom::bez::has_length is the predicate reach.rs used for segments
that draw, made public.
Docs: docs/target-app-parity.md.
The anchor count that sends a row's thumbnail to the thumbnail thread now
includes the art of opacity masks that are not disabled, since the
thumbnail draws that art. A row drawn before was rendered on the UI thread
whenever its last thumbnail had been quick, even when its object had since
become a long path; it now goes to the thumbnail thread when the object
has more than 2,000 anchors.
Ka with a nukta and the vowel sign I is shaped as one cluster: the vowel
sign's glyph, then the ligature of Ka and the nukta. The ligature is
neither Ka's default glyph nor an alternate, so the lookup found no
glyph for Ka: text.glyphAlternates reported the vowel sign's glyph and
text.setGlyphAlternate reported Ka's default glyph. When the cluster
holds neither, the lookup now takes the first glyph that is not drawn
for another character of the grapheme.
The first thumbnail of a row goes to the thumbnail thread when its paths hold
more than 2,000 anchors in all. The count now also takes in the frame of area
type and the path of type on a path, so a long frame or path no longer renders
on the UI thread, and the painted fallback thumbnail leaves such a row out.
The Layers panel chose between the UI thread and the thumbnail thread
for a row's first thumbnail by counting objects only, so one path of
200,000 anchors was rendered on the UI thread. A row whose paths hold
more than 2,000 anchors in all now goes to the thumbnail thread, as a
row of more than 200 objects already did.
While such a row waits, the painted fallback thumbnail draws nothing
for it instead of measuring the path's bounds on every frame.
Opening the panel on one path of 200,000 anchors: the first frame takes
2.4 ms instead of 393 ms (release build, median of three runs).