A Warp effect on an image, or on a group that holds one, bent the image's
frame but drew the pixels unbent, cut by the bent frame. The image now
bends with the frame as a raster mesh warp, as an envelope bends it: after
the last effect in the list, the image is cut into 10 x 10 cells of two
triangles, each a clipped piece of the image under its own affine map,
through every Warp it took and the Transform moves between them. Effects
after a Warp reshape only the frame, and a placed gradient on a group
that holds the image follows the group's bounds as the pieces draw them.
The image itself is unchanged, so editing or removing the warp redraws
it. The images stay unbent when their pieces would make the art pass 2
million objects, and an image stays unbent when its clipping frame keeps
its shape because the warp would pass the effects' anchor budget.
- SVG export writes the image once and draws the pieces with <use> when a
group holds pieces of one image clipped by triangles. PDF, EPS, .ai and
PNG exports write the bent pieces.
- Envelopes: a grayscale image's fill color now colorizes the pieces an
envelope cuts. Before, the pieces drew the gray image.
- Geometry effects: an image under any geometry effect keeps its fill
color and its smoothing setting. Before, it drew the gray image,
smoothed.
Docs: Warp params text, docs/mcp.md, docs/target-app-parity.md.
Under --automation-read-root, prefs.get, prefs.list, prefs.set and
prefs.reset returned the folder preferences (fontsFolder, pluginsFolder,
scratchPrimary, scratchSecondary, recoveryFolder, templatesFolder) and
the export presets' folder even when they were outside the read roots.
Such a folder preference is now null, as ui.inspect leaves it out, and
such a preset's folder is left out. An empty folder and the scratch disk
choices stay; without roots the replies are unchanged.
The Stroke panel menu's Edit Arrowheads… (stroke.arrowheads.edit) opens
Arrowheads.vectorcraft next to the preferences, creating it with one
example head when it is missing. Each symbol in the file is one head,
listed by name after the built-in heads in both arrowhead menus: it
points right, its registration point is the tip, and its height is drawn
as wide as the built-in heads. The file is read at startup and again
each time VectorCraft saves it; stroke.arrowheads.reload and
stroke.arrowheads.list read and list it.
Arrowhead gets a Custom variant, appended after the built-in names, that
carries the head's name and outline, so a document that uses one draws,
saves, reopens and exports it without the arrowheads file. stroke.set
takes {"custom": name} or the {"Custom": {name, path}} form that
document.inspect reports.
Part of #5421.
Clicks, the hover pointer and marquees with the selection tools already
leave out the area that raster effects paint beyond an object. New tests
in crates/engine/src/tests/select_effects.rs hold that in place for drop
shadows, Outer Glow and Gaussian Blur on paths, groups, point type,
symbol art and images, for Shift-click, Direct Selection, the hover
pointer, a drag from a selected object's shadow and a marquee. hit_leaf
gets a doc comment, and docs/target-app-parity.md gets a line citing
https://helpx.adobe.com/illustrator/using/selecting-objects.html.
swatch.library.list, brush.libraries, symbol.libraries and
graphicStyle.libraries returned every library's file path and the user
library folder to a client confined by --automation-read-root, including
paths outside its read roots. They now return null for those paths, and a
library loaded from a file outside the roots is listed as
loaded/<file name>, numbered (2), (3) when names repeat. That id finds the
library in the other library commands, and their replies show it too.
- The naming moves from control.rs into
swatchlib::concealed_library_ids, which numbers the session's loaded
libraries first, so ui.inspect, ui.docking and the library commands show
a library under the same id.
- swatch.library.copyToUser refuses a library whose file is outside the
read roots without naming the file; it and the library saves with
`user: true` leave out `path` when the user library folder is outside
the read roots.
Docs: docs/mcp.md (read roots), docs/target-app-parity.md.
every_command_survives_empty_params and
read_only_commands_read_the_instance_of_a_picked_object run every command
with {}. That includes plugin.clearCache, which without an id empties the
cache of every plug-in installed in the process. When either sweep ran
during plugin_storage_is_capped_and_survives_a_damaged_settings_file
(called from plugins_keep_settings_and_a_cache_of_their_own_in_the_store),
it deleted that test's cache entries Tile and tile. The test's
st.clear_cache() == MAX_CACHE_BYTES + 3 assertion after the
MAX_CACHE_SCAN folders then failed with 536870912 bytes freed.
Both sweeps now leave plugin.clearCache out.
tests/plugin_clear_cache.rs already runs it with {} in a test binary of
its own. A new test runs the other plug-in commands with {} in the
selection states that every_command_survives_empty_params uses, and checks
that an installed plug-in's cache keeps its 3 bytes.
malformed_ids_are_refused_by_commands_that_default_to_the_selection put back
the document, the selection and the undo history after a case that changed
them, but not isolation mode or the opacity mask view. If object.isolate
entered isolation mode while refusing a bad id, the test did not report it,
and the later transparency.editOpacityMask and transparency.viewOpacityMask
cases failed with "exit isolation mode first" instead.
Each case now also compares isolation mode and the shown mask before and
after the command, and puts them back when they changed, so a failure is
reported against the command that caused it.
At the Type tool's caret, kerning goes on the character before the caret.
At the start of the text there is none, so Alt+arrows, the Kerning field
and Reset Tracking and Kerning did nothing; at a later paragraph's start
the kerning landed on the invisible line break.
CharStyle::kerning_before (kerningBefore) is the space before a character
that starts a line by itself: the first character of the text, of a
paragraph, or after a forced line break. Shaping gives its cluster the
space and draws it that much later along the line (right to left the
space is at its right; in vertical type it moves down the column). The
caret at such a line's start sets it (edit::caret_kerning_target), in
type.step, type.resetTracking and the Character panel; text.setRangeStyle
takes it. SVG export starts the line where the character is drawn. The
Eyedropper and character styles leave it alone.
A script whose first lines name an engine with `#targetengine name` (or
`//@targetengine name`) runs in that engine, and a later script that names
it sees the globals and functions earlier runs left, until the engine is
dropped or the session ends. A script without the line, and
`#targetengine main`, still get a new engine for each run.
- A session keeps at most 8 named engines, each with a 64 MiB heap. A run
that goes past the heap ends with the heap limit, and the engine stays
usable. Names are 1 to 64 bytes. The line comes once, in the script
itself, before its code and its #include lines.
- An uncaught error, a limit or script.stop puts the documents back and
keeps the globals the run set. An internal error drops the engine. A run
stopped at a dialog or left paused ends where it was before the engine
is kept. An error in a function an earlier run defined is reported
without a line and column.
- `vc`, `$`, `alert`, `confirm` and `prompt` are made once for each engine
and act for the run that calls them, including copies an earlier run kept
in a global. `vc.args` gives a read-only copy of the calling run's
`args`, and what a run sets on `$` lasts for that run only.
- `script.engines` lists the named engines with their heap, and
`script.engine.drop {name}` drops one. Scripts cannot run either.
- The engines stay on the thread that made them. A session used on
another thread starts there with no named engines, and the old ones are
dropped on their own thread.
- The web build has no named engines: a run that names one other than
`main` ends with an error before any of it runs.
- `Realm::end` ends a run where it is, `Realm::error_source` counts from
the realm's last script, and `HostItem::FrozenGetter` gives a frozen
value each time it is read.
- The script checker lists `#targetengine` as a supported directive.
Docs: docs/mcp.md, docs/target-app-parity.md, docs/gaps.md.
Type › Dynamic Text (`type.dynamicText`, also on the Contextual Task Bar
for type on a path) scales the text by one factor so it fills the span
between its start and end brackets, and the layout fits it again after
each edit to the text, the path or a bracket. The first character's
size moves in 0.1 pt steps within 0.1 to 1,296 pt. Turning it off, or
threading the type, keeps the sizes it was drawn at.
- The model saves `dynamicText` only when on.
- SVG, the legacy .ai writer and the .ai editing data write the drawn
sizes through `vectorcraft_text::drawn`; the editing data warns that
the setting is not written.
- Shrink Text to Fit and Dynamic Text step the size the Character panel
shows when a size measures the cap height, x-height or ICF box.
Docs: docs/mcp.md, docs/type-parity.md, docs/target-app-parity.md.
Part of #4134.
Ruler guides were a list kept beside the layers. Each guide is now an
object of its layer or sublayer, always directly in it and above the
layer's art. These behave as on main:
- Guides draw on screen only. Exports (SVG, PDF, PNG, PSD), Fit All,
Select All, the art bounds, clicks and marquees, and a layer's
Pathfinder and Transform effects leave them out. The Layers panel
lists them as guide rows of their layer, the highest guide.list
index first.
- Deleting a layer deletes its guides, and merging layers keeps their
guides in the merged layer.
- The guide commands number guides as guide.list does, in the order
they were made.
New: each guide takes an id from the document's object ids. An object
command given a guide's id fails with an error, and node.move cannot
put a guide into a group or out of its layers. A guide made while an
opacity mask, a symbol or a pattern is edited moves to the top
visible, unlocked layer when editing ends.
File format: native files still save the guides in the document's
guides list, in every file version, so older builds read them. Each
entry may now also carry the guide's id, name, locked and hidden
flags; older builds ignore those fields. Opening a file puts each
saved guide on its layer (on the top visible, unlocked layer when the
file names none or a layer it does not have), and each saved guide
counts against the object limit for opening a file. A ruler guide node
that a file holds below a layer's art moves up to the layer's guides,
and one anywhere else (in a group or another object, beside the
layers, or in a symbol) is removed. Ruler guides read from an .ai file
become guide objects on the layer they were on, numbered in the order
the file lists them.
The params docs now state the direction (and the zero) of each angle or
rotation a command takes; behaviour is unchanged. Shape creation, text.create,
the grids, add-on shapes, saved views, Liquify, Puppet Warp pins, Mirror and
Radial Repeat and Vector Halftone turn clockwise; text character rotation,
gradients, the eraser tip and print screen angles turn counter-clockwise.
view.saved.new's rotation note changes its schema, so the API level goes to 3.
A test walks every command's params doc and fails when a param named like an
angle or rotation doesn't say clockwise or counter-clockwise.
Scripts get the File and Folder classes as built-ins of the script
engine. A script run from a file can test whether files exist, read
their properties, list folders with getFiles (a mask or a function),
and open files to read them with read, readln, readch, seek and tell.
- Reads stay inside the script's folder and, when automation read
roots are set, inside them too. A script given as text reads no
files. Network and device paths, and paths outside the folder by
their names, are refused before anything is looked up. Links and
junctions that lead out are refused, and getFiles leaves them out.
- The encoding comes from the byte order mark, then the encoding the
script set, then UTF-8 when the bytes are valid, then CP1252.
- Limits: 8 MiB a file, 32 MiB a run, 256 files open at once, 2,048
getFiles entries and 65,536 names looked at, and paths of at most
32,767 characters.
- Writing, the file dialogs and execute are not available yet: they
return false or null and set error.
The report's hostCallCounts gains fileCalls and fileExecutes.
Docs: docs/mcp.md, docs/target-app-parity.md.
A script's $ object now has the other members the Dollar object page
lists: about, appEncoding, bp, build, buildDate, colorPicker,
decimalPoint, engineName, error, flags, gc, getenv, setenv, global,
hiresTimer, level, line, locale, localize, memCache, os, screens, sleep,
stack, strict and version. Scripts also get the global
localize(object, ...values), toSource(value) and
Object.prototype.toSource().
Each run starts with the interface's locale and its own $ state;
$.setenv changes only the run's copy of LANG, LC_MESSAGES, LC_ALL and
OS, and $.getenv returns null for every other name. $.sleep waits from
0 to 1,000 ms between frames, and script.stop ends the wait. toSource
writes plain data only and throws on cycles, getters, host objects and
values past the data limits.
The script checker reports $.localize as a $ member instead of the
global localize function.
path.reshape refused focal with radius only when focal named an anchor.
Since an empty focal list chooses the focal-point rule, a call with
"focal": [] and a radius was accepted and used the radius falloff,
although the params text and docs/mcp.md say the two cannot be used
together. Any focal other than null now goes with radius as an error.
Before the Reshape tool had focal points it sent path.reshape without
focal or radius, and the command stretched an open path from the grabbed
point to its ends whatever the selection, and moved the anchors of a
closed path within half the diagonal of its bounding box with a
falloff. Such a step, in a saved action or a script, now moved a closed
path whole. A step without focal (or with focal null) and without
radius now plays by that earlier rule. The Reshape tool always sends
focal, an empty list included, so its drags keep the focal-point rule.
alert(message[, title, errorIcon]), confirm(message[, noAsDflt, title])
and prompt(message, preset[, title]) are globals in a script run. In the
app the run waits at the call for the person's answer, spends no
instructions while it waits, and every dialog has Stop, which ends the
run and puts every document back. A run over MCP or the command line
takes its answers in order from script.run's answers, a list of
{kind, value} records; a missing, mismatched or unused answer ends the
run. The report lists each dialog with its answer in dialogs, with
dialogCount and dialogsTruncated.
The interpreter gains a host call that waits at the call for its
result, from the script's own code only; a dialog called in a callback
of a built-in, a getter or a conversion throws in the app.
Docs: docs/mcp.md, docs/target-app-parity.md.
Before a script runs, `script.run` reads its preprocessor lines (`#` or
`//@` at the start of a line, outside strings and comments):
- `#include` and `//@include` put another file's text in place of the
line, looked for beside the including file, then in each
`#includepath` folder, then in each `JSINCLUDE` folder. A later
`#includepath` replaces the list.
- `#script` names the run unless `name` is given; `#target` takes any
value; `#strict off` is taken. `#strict on`, `#targetengine` and
`#engine` are refused as unsupported, and any other `#name` as
unknown.
`$.evalFile(path, timeout?)` runs a file in the same global environment
and returns its last value, `$.fileName` is the file being run and
`$.includePath` the include folders.
A script run from a file reads these files only in its folder and the
folders below it. Paths with a `..` name, links or junctions that lead
out, device names, streams and network paths are refused; a script
given as text reads none. A run reads at most 64 files 16 levels deep,
32 MiB of source in all and 1,024 paths looked up. An error in an
included or evaluated file is reported at that file's line and column,
and the report's new `file` field names it.
Docs: docs/mcp.md (script.run, Script Console), docs/target-app-parity.md.
A click with the Reshape tool on an anchor of a selected path makes it
the focal point, and a click on a segment adds an anchor there that
becomes the focal point. Shift-click adds an anchor or a point on a
segment to the focal points, or removes an anchor that is one already.
Each focal point shows a filled anchor square in a larger hollow one.
A drag from a focal point moves all of them; a drag from another point
moves that point alone, and Shift only keeps the drag's angle.
path.reshape takes the other focal points as focal: [[subpath,
anchor]...]. Anchors that are not direct-selected stay in place, and so
do the ends of an open path unless they are focal points. A selected
anchor between two focal points moves by the whole drag, and one
between a focal point and an anchor that stays takes u² (3 - 2u) of
the drag, as before. A closed subpath with every anchor selected moves
whole: a Reshape drag on a fully selected closed path, and path.reshape
without radius on a closed path selected whole or not selected, move
the whole path instead of a local area. With radius, only the grabbed
point's subpath changes, with the falloff as before.
- An anchor added on a segment of a partly selected path is selected,
and the selected anchors and segments after it keep their points.
- A focal pair that names no anchor is refused ("`focal` names [0, 7],
which is not an anchor of the path"), and so is focal with radius.
Docs: docs/mcp.md, docs/target-app-parity.md.