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.
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.
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.
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
A knockout group was written as a chain of soft masks, one inside the
next for each element, so a group of about 200 elements made a PDF that
VectorCraft's own PDF import refused (form nesting past 50) and an SVG
that the web build refused as too deep.
- PDF writes a knockout group as one transparency group with /K true,
with /I true when the group isolates, and PDF import reads it back as
a knockout group. An element with several paints is drawn as one
group inside it, so its own fills and strokes do not knock each other
out.
- Where an element knocks out less than the area it paints (its opacity
or mask defines its knockout shape, or it paints translucently), each
element is drawn through one soft mask of the overlapping elements
above it. Past 65,536 objects in a group's masks, or 2,048 elements,
the group is a PDF knockout group, with a warning.
- SVG draws each element through one mask of the overlapping elements
above it, built from one shared copy of each element's shape, so the
elements stay siblings. Past 65,536 shapes in a group's masks, or
2,048 elements, the group is written as a plain group, with a warning.
Docs: docs/mcp.md, docs/target-app-parity.md.
With Invert Wrap, each piece of a line's band intersected the line's
spans so far with the spans the wrap shape holds over that piece by
comparing every pair of them. A wrap shape of many parts (a comb or a
zigzag) gives a line many spans, and with an offset the band is cut
into up to 128 pieces, so a dense shape made layout quadratic in the
number of parts for every line.
intersect_intervals now walks the two sorted lists once, stepping past
whichever interval ends first, and gives the same spans in the same
order. Tests compare it with the pairwise intersection on every list of
non-overlapping intervals with ends among seven points, check that two
interleaved lists of 3,000 and 2,000 intervals take at most 5,000
steps, and lay out a line inside a 500-tooth Invert Wrap comb with an
offset.
Spot Color Objects listed every spot color linked from a gradient's stops
once any stop was above opacity 0. A spot stop at opacity 0 between other
clear stops, at the end of the ramp after a clear stop, or meeting an
opaque stop only at one offset was listed though it prints no ink, and so
was an opaque stop out of order whose pairs have no width.
A stop now counts when it bounds a stretch of some width that the
gradient sampler mixes and either stop of that stretch is above opacity
0, or when it is the first or last stop and above opacity 0, since those
color the offsets before and after the others. Stops in any order are
judged by the stretches the sampler uses. A freeform gradient keeps the
old rule, since it draws from its points. Type runs follow the same rule.
The document.info params text, docs/mcp.md and the progress line
describe it.
The pieces of a Warp effect, an envelope or their expanded art each
held a copy of the whole image in the .ai editing data and in legacy
.ai files: 200 copies for a Warp's 10 x 10 mesh.
- An embedded image cut to a clipping path (in the editing data, also
an image under an opacity mask that clips) is written whole the
first time; later copies of the same pixels are written as the
rectangle the path or mask shows, 2 pixels more on each side, with
the matrix moved to keep their place. A copy that the path shows
none of is written as one pixel.
- An opacity mask's window covers everything the mask's art paints,
its effects and symbol art included.
- An image's editable original and an image in an opacity mask's art
are written whole. A live Warp's drawn look is not cut to the
clipping groups around the object, so a clipped image with a live
Warp reads back with its Warp live.
- When art that held an image's whole copy is left out (an opacity
mask's art, a live envelope or a definition past the object limit),
the next copy is written whole again. A whole copy written on hidden
lines after an earlier one leaves the earlier one as the copy that
counts.
- The writers read an image's pixels once for the pieces that follow
one another.
Docs: docs/target-app-parity.md.
Live Paint Bucket Options gets a Cursor Swatch Preview checkbox (on at
first, saved with the other options). With the Bucket selected, Left and
Right set the active fill, or the stroke when the Stroke proxy is in
front, to the color swatch before or after it in the Swatches panel,
wrapping around at the ends, with the preview on or off. The selected art
keeps its paint. The preview shows the active paint above the pointer
between the swatches before and after it; a color that is no swatch shows
alone until Right picks the first swatch or Left the last, and a paint
that is not a solid color shows no chip. The chips hide during a fill
drag and while Alt samples with the eyedropper. The fill and the stroke
each remember the swatch picked for them, so swatches of the same color
are each reached in turn.
The Bucket and the Shape Builder now take the arrows for swatches only
without Shift, Alt or Cmd/Ctrl, judged by the modifiers held when the key
went down, so modified arrows keep their shortcuts
(Tool::claims_key_with, Session::tool_claims_key_with).
Docs: docs/mcp.md, docs/target-app-parity.md, tool.options params text.
In a PDF without a standard, the warning for a page longer than 14,400 pt
written with a UserUnit above 1 now ends with "turn on Write pages longer
than 200 in at full size in points". The warning already said that apps
ignoring UserUnit show such a page smaller; it now names the Save PDF
option that writes the page at its size in points instead. PDF/A files,
which keep the UserUnit with the option on, and .ai saves, whose options
do not have it, keep the warning as it was.
The longer message has a row in every complete-message catalog. The
params text of document.exportPdf, docs/mcp.md and the evidence line in
docs/target-app-parity.md describe it.
object.distort warped the anchors of paths, groups, compound paths and
clip groups but left their bounding-box angle as it was, so a rotated
path kept its old box after a warp and an upright one stayed square to
the page however far the warp tilted it. Text, images and symbol
instances already took the angle of the warp's affine approximation.
- Each path, group, compound path and clip group now takes the angle
the warp turns its x axis to at its own center before the warp, the
members of a warped group and the art of a linked opacity mask each
at their own. An unlinked mask's art keeps its angle. Mirroring keeps
the nearer angle and a collapsing warp keeps the old one. Reset
Bounding Box squares the boxes again.
- Each subtree's bounds are measured once while it is warped.
- Corners that equal the corners of the box being warped change
nothing and add no undo step.
Docs: object.distort params text, docs/mcp.md, docs/target-app-parity.md.
When a Smooth tool drag refits the stretch at the first or last anchor
of an open path, the curve there now leaves or arrives along the
smoothed path within the tool's reach instead of along the first or
last segment, so a jagged end no longer keeps its jag. The end point
stays where it is. An end where the smoothed path turns back by 90
degrees or more keeps its segment's direction, and a stretch that ends
inside the path keeps the direction of the part outside the drag.
- pathops: SimplifyOptions gains free_start and free_end, off by
default; closed subpaths and simplify_part ignore them, so Simplify
and Object > Path > Smooth give the same output as before.
- engine: path.smoothRegion sets them for the ends of an open path
that a run's refit reaches.
Docs: docs/mcp.md, docs/target-app-parity.md.
When a drag reached the first anchor of a closed path, the smoothed path
started at the first anchor past the drag, and a drag over the whole
path could start it at a corner the refit kept. Dashes and type on the
path then started somewhere else.
The smoothed path now starts at its point nearest where the first anchor
was: at an anchor there, or at a new anchor split into the segment
there, which leaves the outline as it is. Of points equally near, the
one earliest along the path before smoothing, from its first anchor,
is used. A drag that does not reach the first anchor leaves it first,
as before.
Part of #2224.
An SVG or SVGZ writes each linked JavaScript file of SVG Interactivity
into a <script xlink:href> element as stored, so a caller with
automation read roots could read a path outside them from the exported
text. With read roots in force, an SVG or SVGZ of a document that links
such a file is now refused with "a linked JavaScript file is outside the
read roots", an error that names no file or folder. No file or folder is
written, and the document keeps the link.
- The check uses the rule document.json already applies to
svg_scripts: a relative path is taken from the document's folder, a
file: URL is a file, and another URL is not checked.
- It covers document.serialize, document.export, document.exportSelection,
document.exportForScreens and assets.export (before the folder is made
or any row is written), the SVG and SVGZ saves (also a save without a
path of a document saved as SVG), file.exportAs and the SVG Options
dialog's Show Code.
- Callers without roots, and the person at the keyboard, export the
links as stored.
Docs: docs/mcp.md, docs/target-app-parity.md.
The Smooth tool showed its own cursor with nothing selected, though a
drag then changes nothing. It now shows Not Allowed until a path it can
act on is selected (an editable path, not a guide, with three or more
anchors on one of its subpaths); a selected path anywhere on the canvas
gives the Smooth cursor wherever the pointer is.
- The Pencil with Alt key toggles to Smooth Tool on and the Blob Brush
show Not Allowed the same way when Alt gives them the Smooth tool. The
Paintbrush keeps its closing cursor, since its release with Alt closes
the stroke.
- A Smooth release, from the Smooth tool or from the Pencil, Paintbrush
or Blob Brush with Alt, sends path.smoothRegion only while such a path
is selected. With only type or other objects selected it sends no
command.
- Use Precise Cursors leaves Not Allowed as it is.
- The cursor catalog and docs/target-app-parity.md describe the rule.
The panel showed the values Document Info makes in English in every
language. It now shows the Units, the Raster Effects resolution, each
font's file, substitution and embedding permission, each image's size
and linked file, each Repeat object's kind and counts (in the
language's plural forms) and gradients named by their kind in the
interface language. Font, face, file and swatch names stay as they
are, a gradient swatch named like a kind included.
document.info keeps its English rows and adds the values the panel
builds them from: document.unitId, fontDetails, imageObjects and a
kinds list on each gradientObjects entry. Save… and Package's report
stay in English.
The outline of the area a Blob Brush or Eraser drag covers was outlined
again for the whole stroke on every frame, so a long stroke slowed the
canvas as it grew.
- The drag is outlined in pieces of up to 32 segments (one segment each
with Size set to Pressure), and a frame outlines again only the pieces
it added segments to. A grid of squares lists the segments near each
square, and each piece's outline drops the parts inside the rest of
the area, so the pieces show as one edge.
- One frame takes at most 256 new points and a fixed amount of work;
Tool::overlays_pending makes the canvas draw again until the rest is
done with the pointer still. Points not outlined yet show as a thin
line, and the tip shows at the pointer.
- The settled outline is kept in joined paths of 64 pieces, and a frame
joins again only the paths that changed. The grid's size is capped.
- The Blob Brush's length label and the Size-by-pressure check keep
running values instead of rescanning the stroke each frame. With Size
set to Pressure the outline shows only the edge of the whole area.
The release sends the same path.blob and path.eraseRegion parameters.