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.
Spreading two keys along a spine whose length overflows splits nothing, so
it returned the end anchors, and a closed spine was then opened at its start.
spread_anchors now rejects a length that is not finite before it makes any
target, so two keys get no anchors and the path stays as it was, as for
three or more keys.
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.
Several threaded-text paths in threads.rs searched a list once for each
frame, so their work grew with the product of the frame count and the
list length:
- leave_threads (Create Outlines, Expand Appearance, Rasterize, Flatten
Transparency, symbol replacement, opening with text as outlines)
searched the threads for each frame that stayed and ran continued over
that frame's whole thread again, about n^2 frame visits for a thread of
n frames. It now marks each changed thread with one continued call,
and keeps the frames that left in a map instead of a list.
- has_thread_selection (the availability of Release Selection and Remove
Threading), with_threads, keep_shown_text and cut_to_shown, release and
remove test ids against a hash set instead of a list.
- create finds the thread of each selected frame from a table built
once, and each frame still takes in the first thread not yet merged
that lists it.
- release writes each thread back at its own index instead of searching
the thread list for an equal thread.
Marks, thread membership, frame order, text and overflow are unchanged.
A 256-frame thread that loses one frame visited 65,025 frames before
this change and at most 256 now. Six tests on many threads count the set
lookups: with a list in place of the set they make 67,650 to 258,226.
Another test compares the marks with a scan of the frames before each
one.
A prefs.set step, recorded inside a command.batch or loaded from a
.vcactions file, can hold a folder preference such as scratchPrimary.
- ui.inspect under automation read roots now checks such a step's
value for {key, value} and each entry of values, also in a batch
step's commands at any depth, as written and trimmed, including bare
names, subfolders and file: URLs. An empty value and the scratch disk
choices Startup and None stay.
- Any other step text written as an absolute path or a file: URL
outside the read root is left out also when whitespace surrounds it.
- Save Actions refuses a set with a prefs.set step that holds a folder
value and names the action and the preference instead of writing the
file. An empty folder and the scratch disk choices save.
- prefs.export leaves out scratchPrimary and scratchSecondary, like the
other folder preferences. prefs.import refuses a scratch disk set to a
folder and still reads Startup, None and an empty value.
The folder list, the scratch disk choices and the folder check now live
in one place in the engine. Docs: docs/mcp.md, params text and a
progress line in docs/target-app-parity.md.
A fill or stroke row that the Appearance commands gave a graph label, its
object opacity, and the characters the Eyedropper gave it lasted only
until the graph was drawn again: the graph stored nothing for a label's
rows, and the Eyedropper's character attributes were not stored.
- The graph keeps, for value axis numbers, category labels and legend
labels, the paint, weight, opacity and blend mode of a label's topmost
visible fill and stroke row, their order and place relative to the
Characters row, and the label's object opacity and blend mode
(labelStyles valuesRows, categoriesRows, legendRows). Every label of
the kind gets those rows when the graph is drawn again, with a placed
gradient fitted to each label.
- Every appearance.* command stores them, and so do graphicStyle.apply
and redefine and the paint, stroke, gradient and transparency edits
aimed at a row, even when the label already showed them.
transparency.set without a row and object.setProps store only the
object opacity and blend mode. With several labels of a kind, the last
one edited wins.
- appearance.copyFrom onto all of a label's text stores the character
attributes it applied and, from type, the paragraph attributes; a
transparency pick stores the object opacity and blend mode, and
append stores the rows.
- paint.setFill and paint.setStroke store a character fill or stroke
only when the characters take the paint (the whole label, or all of
its text highlighted with the Type tool); aimed at a row, they store
the row.
- Document Color Mode and swatch edits reach the kept rows.
Docs: docs/mcp.md, the commands' params text, docs/target-app-parity.md.
tool.setOption for the Live Paint Bucket, the Live Paint Selection tool
and the Shape Builder used to keep the old value when a switch was not
true or false, a color could not be read or a width or gap length was
not a number, and it set the other values sent with it. It also took
the RGB of a color with an alpha part, and the Shape Builder took any
`pickColorFrom` other than `swatches` as `artwork` and any `selection`
other than `straightLine` as `freeform`.
The command now checks every value before it sets any, whether the tool
is chosen or not: a switch must be true or false, a highlight color
must be an RGB color or a layer color's name without an alpha part, a
width or gap length must be a number, `pickColorFrom` and `selection`
must be one of their names, and an option the tool does not have is an
error. The error names the option, and nothing is set or saved. A width
outside 1 to 20 and a gap length outside 0 to 72 are still clamped, and
saved settings with an invalid value still load.
Docs: docs/mcp.md, docs/target-app-parity.md.
With anchors selected and Transform Objects off (objects: false, or the
backquote key during a Scale or Reflect drag), the transform moves the
whole objects' pattern tiles, so it now acts along the turned bounding
box instead of the page axes that apply to the anchors alone.
With repeated names numbered when a brush library is read, a stroke that
names a repeated brush draws the first brush of that name, and a later
brush of the name is read as "Name 2". Three tests built a library with a
repeated name and expected the later brush:
- Document Info lists a name two stored brushes share with the first
brush's type (art), not the last one's.
- The two render memory tests add their own `Dots` scatter brush beside
the default library's `Dots`. They now remove the default `Dots` first,
so the stroke draws their brush.
- The .ai writer test that leaves out a Pattern brush "X" gets the
Calligraphic brush after it back as "X 2", the name it had in the
document.
Giving the keys of a blend whose spine came from Replace Spine anchors of
their own flattened and measured the whole spine again for every key,
then walked its segments one by one. 1,000 keys on a spine of 4,096
segments flattened it 999 times.
The spine is now flattened once into one buffer. A split flattens only
its two halves and measures the segments after it again, in order, only
as far as the next key, so the arc lengths are summed as before and the
keys get the same anchors. A binary search finds each key's segment.
A spine so large that its length overflows no longer gets an anchor
split at a parameter that is not a number: spreading keys along it
returns nothing and leaves it as it was.
A path or compound path with straight segments and nothing stroked was
read with its Twist live at any angle. It is now read live up to 180°
either way, a limit VectorCraft sets, and one twisted further keeps the
file's drawn look, as curves and groups already do past 90°.
The .ai writer keeps a graphic-style link only where the reader reads the
style live, so a straight object twisted past 180° is now written without
the link, with the existing style-link warning.
Docs: docs/file-format-parity.md, docs/target-app-parity.md.
With the selection as its scope, a fix that removes or unwraps objects
(Needless Groups, Stray Points, Empty Text and the others) kept the list
of selected objects current by searching it and splicing it once for each
object it changed. Removing many selected objects above a long run of
others took time that grew with the square of their number.
The list is now rebuilt in one walk after each pass: each changed object
gives way to what replaces it, in its place, and the others keep their
order. The objects found, the counts, the document and the undo step are
the same as before.
Empty Artboards deleted each empty artboard with its own Vec::remove and
walked every ruler guide again for each one. It now drops the empty
artboards in one walk and deletes their guides in one more. The same
artboards and guides are deleted as before.
Tests cover the order after unwrapping nested and empty groups, Fix All
reaching an unwrapped member on a later round, the rebuild itself, the
number of objects looked at for 150 and 300 removals, and the number of
walks over the artboards and guides when 40 empty artboards are deleted.
A pair with manual kerning of 0 was saved with no Tk and opened with automatic kerning. It is now written as 0 0 Tk and reads back as manual kerning of 0.
A line breaking value (TG) or Japanese layout rules (Xu, Tu) with missing or non-numeric operands no longer mark the setting as set, so the next visible text object gives no warning about a setting the file did not specify.
`span_at` read the end of a closed subpath's closing segment as past
every stretch, so a click that landed there could leave the path
unchanged. It now reads that place as the first anchor.