The continuation cursor (a short rising line) shows over an open end of a
selected path before any press, freehand too, and while a continuing stroke
nears an open end of another selected path it connects to. The straight-line
cursor becomes a short level line. A stroke continuing a path, a straight line
too, shows the closing ring within Close Paths When Ends Are Within of the
path's other end, and the release closes it; path.freehand takes the tool's
closed for extend instead of its own four-Fidelity distance.
Dynamic Text, Dynamic Text Presets, Center on Top and Center on
Bottom, and Area Type Options with Block on checked each object for a
thread with its own search of the document's threads. A selection of
many objects in a document with many threads repeated that search for
each object, in the commands' enabled checks as well as when they ran.
The checks now build one lookup from frame to thread for the whole
selection. An object counts as threaded by the first thread that lists
it, as before, so the commands refuse the same frames, list them in the
same order and leave the document and the undo history as they were.
Select > Start Global Edit was enabled for any plain group, so a group
holding type, an image or nothing was offered in the Select menu and the
Properties panel's Quick Actions, the Control bar showed its Global Edit
Options, and starting then refused it. The enabled check now applies the
same support check as starting to every selected object, a group's
contents included.
The answer is kept per document revision and selected object, so the
Control bar, which asks every frame, walks a group once per change.
Docs: docs/mcp.md, docs/target-app-parity.md (a new line; the existing
Global Edit line no longer says the menu item checks only for a group).
A document's composite fonts were lent by writing them into the font
database's shared map, and a second lend of another definition of the same
name was skipped while the first was held. A document drawn in a tiled or
floating window while the front document drew, a document placed in one
that defines the name differently, and an export made while another
document is in front could draw their type in the other document's
definition. Registering the front document's fonts during a render could
also change the definition mid-render.
Lending now applies on the lending thread only: each guard pushes the
document's definitions onto a per-thread stack that lookups read before the
database's own fonts, latest loan first, and removes them when it drops,
also when the thread unwinds. The guard is neither Send nor Sync. Lending no
longer changes the database or its generation; while a thread lends
definitions other than the database's, FontDb::generation gives a number of
its own for them, so kept layouts and render caches made in one document's
definitions are not used for another's.
Delete Artboards, Delete Empty Artboards and Cut Artboard removed the
chosen artboards one at a time, walking every ruler guide and the layer
tree once per artboard. They now share one helper that removes the
artboards in one walk and their guides in one more, leaving the same
artboards, guides and selected guides as before.
Empty Artboards in Artwork Cleanup had its own copy of the one-pass
removal. It now calls the same helper, and its test reads that helper's
walk counter.
264 Hindi rows kept English words in a Hindi sentence ("or", "give",
"see"), dropped the word that names what is wrong ("unknown align" read
as "unknown `left`"), reversed a condition ("not editing an opacity
mask" read as "do not edit an opacity mask"), translated enum values the
command reads in English, broke a list of accepted values
("(vertical|horizontal|free)" read as "(vertical Chhorizontal
Chlorfree)"), or repeated a parameter as a "size: आकार" prefix. Each row
now says what its English says, with parameter names, enum values,
placeholders and value lists unchanged, and "positive" reads धनात्मक.
Two tests check every row: no English connecting word outside
backticks, quotes, braces and brackets, and every `a|b|c` list of an
`@msg` row kept as written.
The help page says a wrap object's Offset may be positive or negative
(https://help.adobe.com/archive/en_US/illustrator/cs4/illustrator_cs4_help.pdf#page=307).
Text Wrap Options, the commands, the MCP tool and .ai import already
kept a negative value, but layout set it to 0.
- Layout keeps the signed offset, within -1000 to 1000 pt like the
commands and .ai import (a value that is not a number counts as 0).
- Around a wrap object, a negative offset shrinks the object by that
distance from each edge, holes included, so lines beside, above and
below it run that far into it. A shape narrower than twice the
distance no longer moves text aside. A line may get less room than
the exact shrunk shape leaves, for example where several edges come
close to one piece of the band, or where a band holds more than 64
anchor heights. It never gets less room than an offset of 0 leaves.
- With Invert Wrap, a negative offset keeps the text that far inside
the object.
- Zero and positive offsets up to 1000 pt lay out as before. A larger
positive offset lays out as 1000 pt.
- The params text of object.textWrap.make and object.textWrap.options
and the MCP text_wrap offset description give the range and what a
negative value does.
Docs: docs/target-app-parity.md.
An object in an opacity mask's art is written with its art style, as on
the layers, so a mask whose art has Offset Path opens from the editing
data with the effect live and draws the masked object as it did. This
covers a hidden art root and masks inside another mask's art.
Geometry effects on mask art that no art style holds, such as Roughen,
are applied to the art written, in the masks of layers, symbols and
pattern tiles. Before, the mask's art was written without them.
With the Live Paint Bucket, a double-click on a face fills it and every
face reached across edges that are hidden or draw no stroke; a stroked
edge, a hidden face and a Gap Detection gap stop it. On an edge, a
double-click paints the edges joined to it end to end with the same
stroke paint. A triple-click fills every face of the group with the
clicked face's fill, or paints every edge with the clicked edge's
stroke. Shift and the Paint Fills and Paint Strokes options choose a
face or an edge as for a click; Alt held paints nothing.
- livePaint.fill and livePaint.strokeEdge take scope ("one", the
default, "connected" or "samePaint") and match, the paint compared,
and reply with count. A bad scope or match is an error, and scope
with a drag's points is refused.
- Each click of a double-click or triple-click paints as it arrives,
as its own undo step. For a triple-click, and for a double-click on
an edge, the tool sends as match the paint the face or edge had
before the first of those clicks, including a first click that makes
the Live Paint group from the selected paths. A double-click on a
face sends no match.
- PointerKind::TripleClick is new: the canvas sends it on egui's
triple-click unless a modal dialog is open, as for a double-click,
and ui.pointer and pointer_gesture take "tripleclick".
Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md.
A step whose command id is not a built-in command, a command of an
installed plug-in, a View menu step or a stop now loads as it is and is
reported when the set loads: the ui.actionSet.load answer lists the
action, step and id (unknownCommands, at most 1,000, and unknownCount),
and the status bar names the first and the count. Commands inside a
command.batch step are checked one by one. Control characters in the
status text are replaced and long names are cut at 80 characters.
An action with such a step included no longer plays: the error names
the step and its id before any step runs, at every speed. An excluded
step does not stop it.
On a canvas narrower than one row of the bar, the action buttons ran
past its right edge: only the font controls moved to a second row, and
keep_in moved the bar without shrinking it.
- The actions now wrap, in order, onto rows under the row with the
handle, the chips and More Options. Their widths are measured with
the button's font, and the bar's place on the canvas uses the same
layout from the first frame and after a resize.
- An action wider than a row, or one that would need a row past the
canvas's height, is listed in More Options.
- When the handle, the chips and More Options don't fit one row, the
chips take rows after the actions. Without a row to spare, the font
controls are left out. A canvas too small for the handle and More
Options shows no bar.
Docs: docs/target-app-parity.md.
The test's second and third wheel turns step to whichever families the
process has loaded at that moment. When another test has loaded a test
font without Latin glyphs, such as Alias Jointest Sans from the Glyphs
panel tests, the turn applies it, missing glyph protection keeps "Hi" in
Source Serif 4, and the assert_ne at the second turn fails. The test now
turns the preference off, as the font menu tests in tests/font_menu.rs
do, so each turn changes the text's font whatever family it reaches.
With downsampling on, each piece of an image bent by a Warp effect was
resampled for its own placed size, so a warp wrote one image XObject for
each size a piece was placed at (four for a 4 x 4 warp that stretches
across, eight for a colorized one with its soft masks). The pieces are
now resampled for the widest and the highest size any piece places the
image at, and all paint that one image. Pieces off the page count too,
and another placement of the same image keeps its own size.
Without downsampling, a colorized image and a CMYK image with a grid
mask no longer key their cache by placed size, so each is decoded and
encoded once for all the sizes it is placed at, as plain images are.
When a recovery copy could not be written, the status bar said only
"Couldn't save recovery data: <error>", and a copy that failed on the
background worker showed as "Saving recovery data for <title> failed:
<error>". Neither said which folder was in use or how to pick another.
The message keeps the error text (and the document's title when one
copy fails), then names the folder copies go to, says whether it is
the default recovery folder or the folder set in Preferences, and
names the setting that picks another folder: Preferences → File
Handling → Data Recovery → Folder (recoveryFolder). The recovery code
writes the same line to the log. The folder is the one in use when the
round started, so a preference changed while a copy runs does not
change the folder the message names.
A copy that fails now reports its own message, so the job's result is
Ok rather than an error, and the job no longer adds "<label> failed:
<error>" over it. A store the host installed (browser storage) has no
folder to name: its failed copy shows "Saving recovery data for
<title> failed: <error>", as a copy on the worker did before, also
when the copy was written without the worker.
- engine: recovery::folder returns the folder in use and whether the
recoveryFolder preference names it; recovery::store uses it.
- Four @msg rows in each complete-message catalog, and in the bg, bs,
da and id catalogs, which translate the old message.
- Control replies to a client confined to read roots show a recovery
folder outside them, and the paths under it, by the folder's name, as
they already show other paths outside the roots. That covers the
folder in use and the last eight folders a failure message named.
- docs/target-app-parity.md: a line next to the Data Recovery Folder
preference.
Object › Plug-ins › Cutting Tools › Banner Hems and Grommets…
(`addon.cutting.banner`) marks where a printed banner is hemmed and where
its grommets go. The banner is the bounds of the selected art, or each
artboard when nothing is selected. On each chosen side the command adds a
hem line a set depth inside the edge (1 in by default) and grommet marks a
set inset inside it (0.5 in): one at each end of the side and the rest
spread evenly, as few as keep them at most a set spacing apart (24 in). A
corner that two chosen sides share gets one grommet. Each grommet mark is a
circle (27 pt by default) with a cross through its center. The marks are
stroked in a chosen swatch ([Registration] by default) with no fill, in one
group named Banner Marks per banner on top of the current layer, selected,
as one undo step. While a clip group is edited in isolation, the group goes
outside the outermost clip group, as Weed Box and Lines does.
The command refuses every side off, no hem and no grommets, and bad params
with a message. It also refuses a hem or inset that reaches the far edge of
the banner when one of two opposite sides is chosen, or that meets the marks
of the opposite side when both are chosen, an inset of more than half a
chosen side's length, and more than 100000 grommets. The dialog has Preview.
The grommets' distance from the edge is `grommetInset`, so the dialog shows
it as Grommet Inset with its own catalog rows. The new Diameter catalog
rows also translate the Diameter field of the `shape.flare` command dialog,
which had no rows.
Docs: docs/mcp.md (Cutting Tools add-on), docs/target-app-parity.md and
ROADMAP.
object.setLiveShape was in params::UNCHECKED, so a key it does not read,
such as `path`, ran the command with the key ignored. Its description
names every key it reads, so it now goes through check_keys like other
commands, for MCP, scripts and command.batch steps. Each of `items` takes
`id` and `corners` alone; another key is refused as
`items[0].corner`, with the nearest accepted key. Saved steps still
replay leniently.
A `sides` that is not a whole number from 3 to 1000 (2.5, -1, 2, 1001)
and a negative `radius` are now errors naming the key, and leave the
document and its undo history as they were; a radius of 0 still
removes rounding. The Transform panel's Sides field takes 3 to 1000,
the Corners dialog's radius field stops at 0, and the panels' corner
radius fields send 0 for a negative value typed in.
Break Text checked every thread of the document for each selected type
object to skip threaded frames, and Remove Breaks searched every thread
for each selected object to find its story, so the work grew with the
number of selected objects times the number of threaded frames.
Both commands now index the document's threads once per command: Break
Text tests membership in that index, and Remove Breaks takes the story
from the indexed thread through threads::story_in, the second half of
story_of. Split Text at Cursor, which takes one object, checks it once
as before. The results are unchanged.
Tests: Break Text on two threaded frames and six point type objects
skips the frames unchanged, breaks the rest in place as one undo step
and goes through the threads once; Remove Breaks reads the selected
story as one text and the other objects on their own, going through
the threads once; Split Text at Cursor refuses a threaded frame.
Offset Path took each selected path in turn, so offsetting many paths, or a
group of many paths, used one CPU. It now reads the selected paths first,
works out their offsets on as many threads as the computer has logical CPUs
(one thread on the web and for a single path), and then makes the new
objects one after another in the selection's order on the editing thread.
The new objects, their ids, their stacking, the selection, the messages and
the undo step are the same as before. When several paths fail, the error is
the one for the first of them in the selection's order, and the paths after
a failure that have not started yet are skipped.
A right-to-left line draws the text before a tab after it, but its width
was measured in the order of its text. A right-aligned line such as
"אב גד<tab>הו זח" with a stop at 150 pt in a 300 pt frame ended 5.59 pt
past the frame's right edge, and right, center and decimal stops aligned
the text before the tab instead of the text drawn after it.
- The line's width is measured in drawing order, each tab up to its stop
from where the glyphs drawn before it end. Right, Auto and centered
lines are aligned by that width. Stops are still measured from the
left edge.
- Right, center and decimal stops align the text drawn after the tab.
- Area type breaks a line with a tab where it fits as drawn, and wraps a
line that fits only in the order of its text. Once the line holds a
tab, it reads on while the least it can be drawn at fits, then ends at
the latest break that fits as drawn: up to 256 breaks are measured
from the latest back, and earlier breaks without a hyphen are
bisected. Hyphenation points are measured as drawn the same way, so
the line hyphenates at a point that fits.
- Only lines of horizontal paragraphs with right-to-left text keep a
list of their break candidates.
- A tab that ends a wrapped right-to-left line is drawn before the line
at its own width instead of pushing the line to the right.
Docs: docs/target-app-parity.md.
Type on a path whose first paragraph is aligned Justify All places its line
from the start bracket, the same as Left, so a threaded story is now measured
with the paragraphs it needs rather than the whole rest. A 1,500-paragraph
story through 40 paths aligned Justify All was laid out over 2,135,202 bytes
while measuring and is now laid out over 20,583. The frames take the same
text as before.
Center, Right, Auto right to left, hanging punctuation, Dynamic Text and
horizontal right-to-left text are still measured with the whole rest: a
centered or right-aligned line starts where the width of all its glyphs puts
it, and negative tracking can make an advance negative.
Tests compare every frame's share with the whole-rest share for Justify All
with spacing, on closed paths, with negative tracking and with punctuation
ending paragraphs.
Add Points and Divide into Dashes read each subpath's selected segments
with a range lookup in the path's ordered segment set instead of
filtering the whole set once per subpath. Add Points at Crossings and
Split at Crossings sort a path's crossing places into a list per
subpath once, in the order they come, instead of filtering the whole
list for every subpath. Places on a subpath that no longer exists are
still left out. The results are unchanged.
Tests cover paths with several subpaths, an empty subpath, an
untouched subpath and selection on the last subpath, for all four
commands, plus the anchor fallback and the grouping of out-of-order
crossing places.
An SVG shown as an image that could not be read (text that isn't XML,
an SVG without a size) still took one of the 256 nested SVG images an
SVG may show, so an image after 256 broken ones was left out. Only
images that decode now count toward that limit; a failed image gives
its place back.
Every nested SVG image read, decoded or not, counts toward new limits
of 1,024 reads and 512 MB of text per import, so a broken image shown
many times through <use> is still read a limited number of times. Past
them, nested SVG images are left out with a warning.
A nested SVG image with text that usvg then refuses no longer warns
that its text isn't imported.
Under a light that is not white, an opaque Inflate fill or stroke in a
solid color was drawn as one composite of all its facets, so the
object's solids were drawn whole, one after another: the stroke's faces
covered every face of the fill, nearer or not.
When an object's solids share one order (all opaque with Normal
blending, or one member's under its opacity mask), each facet of such a
solid is now drawn on its own, with the composite's paint, channel
light ramps and seam width, and takes its place in that order as the
faces of a gradient already do. A lone solid, and the solids of an
object with a translucent or blended fill or stroke, keep the one
composite.
A face drawn on its own is about 16 nodes, against about 6 per facet
in the composite of a whole surface. These faces go in one order while
they number at most 30,720 together, counted again after the cuts.
Past that, each such solid is drawn as one composite.
Docs: docs/three-d.md, docs/target-app-parity.md.
With a path selected whole by the Direct Selection tool, the bar's
"Smooth" button converted every anchor point to smooth at once. It was
the anchor convert, labeled like the path Smooth that the overview lists
beside Simplify as adjusting the path's curvature, and the bar had no
button for Object › Path › Smooth….
- A path selected whole with Direct Selection now offers Smooth after
Simplify. It runs object.path.smooth the way the menu item does, so
it opens the Smooth slider and changes nothing until it is confirmed.
- The two anchor convert buttons, with a whole path or with anchors
selected, show their icons alone, named by the Control bar's tool
tips "Convert Selected Anchor Points to Corner" and "to Smooth".
The keyboard shortcut part of #6784 is fixed on main by #6810.
Docs: docs/mcp.md, docs/target-app-parity.md, the ui.smoothAuto params.
Fixes#6784