These commands read optional numbers and flags with f64_or and bool_or,
so a value such as "10" or "yes" ran the command with the default. They
now return an error and leave the document, the selection and the undo
history as they were. Omitting a key or giving null behaves as before.
- edit.duplicate, edit.paste, guide.move, object.slice.duplicate: dx, dy
- guide.move: copy
- document.node: summary
- edit.undoGroup.end: cancel
- guide.add: vertical
- guide.select, object.slice.select: toggle
- select.editSaved: delete; newName must be a string
- select.save: name must be a string
- file.close: index must be a non-negative integer
The Edit Colors commands read `fill`, `stroke`, `includeImages` and
`includePatterns` with a reader that fell back to the default for any
value that is not true or false, so `edit.colors.toGrayscale
{"fill": "false"}` converted the fills it was asked to leave alone.
These keys, and `convert` and `mode` in `edit.colors.adjustBalance`,
are now read with `switch_or` and `text_param`, and a wrong type is an
error that changes nothing. The same applies to `addon.color.filter`,
which shares the scope.
`edit.colors.overprintBlack` now refuses a `remove`, `fill`, `stroke`,
`includeCmyBlacks` or `includeSpotBlacks` that is not true or false
and a `percentage` that is not a number. `color.harmony` refuses a
`rule` or `variation` that is not a string; a `variation` such as 5
was dropped before.
A null still counts as a key left out. docs/mcp.md describes the
errors.
With the Artboard tool selecting several artboards, Paste in Place, Paste
in Front and Paste in Back put a copy of the copied art on each, where it
was on the artboard it was copied from, in one undo step. The three
commands take `artboards: [index...]`; the app passes the Artboard tool's
selection there, and without it the engine uses that selection while the
tool is chosen and records it in the journal. Art copied from no artboard
keeps its place on the first artboard and its offset to it on the others.
Copied artboards still paste once, where they were.
An `artboard` or `artboards` value that is not an artboard index of the
document, a repeated index or an empty list is now an error, and nothing
is pasted. An out-of-range `artboard` used to paste the art where it was.
A batch keeps the `artboards` a paste step names, and a paste step that
names neither pastes on the Artboard tool's selection while that tool is
chosen with several artboards selected.
Help page: https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/cut-copy-and-paste-artboards.html
select.object.notAlignedToPixelGrid selects the paths (not guides) and
compound paths whose pixel-aligned property is off, whatever their
coordinates, and returns {count}. It looks into groups but not into
compound paths, which take the property as one object, and leaves out
hidden, locked and template art and, in isolation mode, everything
outside the isolated object. It replaces the selection, clears it when
nothing matches, and records no undo step.
The menu item goes under Select › Object with rows in every complete
catalog, and plug-ins may call the command like the other Select ›
Object filters. Docs: docs/mcp.md, docs/target-app-parity.md.
The Symbols panel menu gains Sort by Name, which puts the document's
symbols in name order, ignoring case, as one undo step
(`symbol.sortByName`). Symbols whose names differ only in case keep their
order, the current symbol stays current, and no undo step is added when
the symbols are already in order.
The menu's Thumbnail View and List View become Thumbnail View, Small List
View (18-point rows, 14-point thumbnails) and Large List View (the former
List View's 26-point rows and 22-point thumbnails), as in the Graphic
Styles panel. Clicking, double-clicking and dragging a symbol work the
same in every view.
Docs: docs/mcp.md, docs/target-app-parity.md.
file.package, file.place, file.place.queue, file.place.info,
document.serialize, document.export, document.exportSelection and
document.exportForOffice read their switches, strings and numbers with
helpers that fell back to the default for a value of the wrong type.
file.package {"copyFonts": "false"} copied the fonts, and
document.exportForOffice {"path": 5} returned the bytes instead of
writing a file. Each such value is now an error naming the key, and
nothing is placed or written. Null still means the key was left out.
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.
A missing file below a folder link whose target does not exist got
"does not exist", while one below a link to an existing outside folder
was refused, so a script could tell whether the folder a link leads to
exists. A missing path is now refused when the nearest entry above it
that exists is a link that leads to nothing.
A fourth gradient kind, conic (also accepted as `angle` or `conical`),
sweeps the stops once around the center (the gradient's start),
clockwise on the page from the vector, with a seam where the last stop
meets the first. A negative aspect mirrors the sweep, and reflecting an
object turns its sweep the other way, also for a conic without a
vector or with a flat extent.
- Gradient panel: a Conic button in the Type row; the Aspect Ratio
field works for conic gradients and keeps a mirrored sweep mirrored.
- Gradient tool: the extent ellipse, aspect handle and center ring of a
radial gradient, without a focal point.
- Rendering, swatch chips, transforms, warps, plug-in round trips, and
Object > Expand into wedges or a mesh of columns around the center.
- Commands: a negative `aspect` is an error for linear and radial
gradients, and so is a `focal` point for a conic gradient.
- PDF export writes a function-based shading whose calculator function
turns the angle into the ramp, and PDF import reads a function-based
shading that turns around the origin of its space back as a conic
gradient, also when that origin lies outside the painted area. A
PDF/A file, or a conic with more than 97 stops, gets an image
instead.
- SVG export writes 360 solid wedges clipped to the shape for opaque
stops and an image otherwise, and a linear gradient on characters.
- CSS export writes `conic-gradient(from ...)`, with the stops mirrored
for a mirrored sweep.
- EPS and legacy .ai export write 360 solid wedges; the .ai editing data
holds a linear gradient along the vector with the same stops.
- .af files: conical gradients open as conic gradients.
- An export that writes the gradient in another form gives a warning.
Supersedes #2326 and includes switch87's code from it.
Co-authored-by: Gert Pellin <gert@pellin.be>
When plugin.remove could not remove the grant and then could not rename
the plug-in's settings folder back from <folder>.removed, the failure was
only logged: the plug-in got empty settings, and the next removal deleted
the .removed folder with the settings in it.
- The error now names the .removed folder, the plug-in folder and the
reason, and the plug-in gets no storage until the settings are back.
- The next load, install or plugin.remove of the plug-in renames a kept
folder back while the plug-in has no folder of its own. When it has one,
that folder is used and the kept one stays until plugin.remove deletes
both. A removal while a kept .removed folder exists renames the plug-in
folder to <folder>.removing.
- Before deleting a renamed folder, the store writes an empty
<folder>.removed.delete file and removes it once the folder is gone, so
what a failed deletion leaves is not moved back. When that file can't
be written, nothing is deleted.
- A .removed folder without a .delete file, including one an earlier
version left, counts as kept settings: a new install of the id needs
replace: true, and plugin.remove of the id deletes it.
object.vectorHalftone read frequency, angle, invert, clip and
keepOriginal with readers that fall back to the default for a value of
the wrong type, so {"frequency": "60"} drew a 20 lpi screen. shape and
mode given as a non-string also fell back to circle and mono. Each of
these is now an error and the document is left as it was. A null
value, color included, still stands for the default.
Closing a path by dragging on its first anchor made that anchor smooth
with symmetric handles, so the first segment bent along with the closing
one. The drag now sets the first anchor's incoming handle to the mirror
of the pointer. Its outgoing handle stays where it is on a corner, so
the first segment keeps its shape, and on a smooth anchor it turns to
stay in line at its own length. Alt makes the anchor a corner and leaves
the outgoing handle where it was when Alt went down. A click on the
first anchor closes the path with the incoming handle retracted and the
anchor a corner, so the closing segment comes in straight.
The Smart Pen's closing drag follows the same rule.
path.close's `in` now places the handle as path.setHandle does, and a
new `out` param places the outgoing handle and makes the anchor a
corner.
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.
The gradients category was titled Gradient Swatches and listed every
gradient swatch, used or not. It is now Gradient Objects: one row per
object filled or stroked with a gradient, named as the object is, with
the gradients it uses (a linked swatch by name, else Linear gradient,
Radial gradient or Freeform gradient). document.info adds
gradientObjects and gradientNames, and Selection Only, Save and the
Package report use the same rows.
Gradient objects follow the rules Pattern Objects uses for paint that
draws: hidden fills and strokes, strokes with no width, guides, compound
path members' own paint, image paint without a shape and type that draws
nothing are left out, and type whose characters carry a gradient is
included. objects.gradients now counts the listed objects, so it no
longer counts hidden or zero-width gradient paint and no longer misses
type whose characters use a gradient.
The duplicate Document Info Save line in docs/target-app-parity.md is
removed.
A `File` or `Folder` for a missing path got "does not exist" even when
the path led outside the script's folder, through a folder link or, on a
file system where case matters, through a folder whose name differs from
the script's folder only in case, while an existing file there was
refused. A missing path is now refused unless the nearest folder above
it that exists lies inside a granted folder.
New Color Group looked up and removed each named swatch with a scan of
every swatch list, so making a group from artwork linked to many global
swatches took time quadratic in their number. Delete Swatch, Move and
Merge did the same, and checked each paint's link against the name
list.
take_swatches now finds every named swatch in one pass and removes them
together, in the order and with the same choice of swatch as removing
them one at a time. The commands look names up in one name map and
check links against sets.
With 16,000 named swatches in a release build, New Color Group went from
175.9 ms to 16.3 ms; 4,000 went from 11.6 ms to 3.6 ms.
Type > Dynamic Text (type.dynamicText) now also works on area type as
Block. Each line as typed (each paragraph, and each forced line break in
one) is set to the frame's width, less the inset, the column gutters and
the paragraph's indents, by changing its font size only. Lines are not
broken again. The first character's size moves in 0.1 pt steps within
0.1 to 1,296 pt, and the line's other characters keep their proportions.
The sizes are written into the characters and fitted again after every
edit to the text or the frame, in the same undo step.
- Word Gap (0 to 1,000 % of a word space, default 100) sets every
paragraph's Desired word spacing; Line Gap (-1,296 to 1,296 pt,
default 0) is added to each character's size for its leading. Both are
VectorCraft's own ranges and defaults, saved in the area options only
when they differ from the defaults.
- Area Type Options gains a Block fit (text.areaOptions fit "block",
with wordGap and lineGap), and the Properties panel's Paragraph section
shows a Dynamic Text checkbox with Word Gap and Line Gap rows.
- Block replaces Auto Size and Shrink Text to Fit. Off keeps the sizes.
Threaded area type is refused, as for type on a path.
- Empty lines keep their size, a frame with no width changes nothing,
vertical type fits each line to the frame's height, and right-to-left
lines and list markers are fitted like other lines. A refit runs at
most four extra layouts.
Docs: docs/mcp.md, docs/target-app-parity.md.
With Envelope Options > Distort Appearance on, VectorCraft turned the strokes
of type in an envelope into filled outlines and bent them with the art, so a
stroke got thinner where the grid squeezes the type and thicker where it
stretches it. The drawn art of users' .ai files keeps those strokes as stroked
paths at the type's own weights, and the ring they paint is as wide wherever
the grid squeezes or stretches the type. An envelope of stroked type in such a
grid therefore didn't draw as its drawn art and kept it instead of opening live.
Type's strokes (its characters' and those on its own appearance) now paint
along the bent glyph outlines at their own weight, with Distort Appearance on
or off, also in an envelope inside an envelope:
- doc: `map_node` maps the groups that hold type's glyph outlines with
`Hooks::type_appearance` in place of `Hooks::appearance`, for type in the
envelope's contents and for type that a live object in them (an inner
envelope) outlined (`Warper::type_outlines`). Such a group's opacity mask
is mapped with the ordinary hooks, so it bends as other art does. Groups
marked as type's outlines in the contents themselves (art an earlier Expand
made) stay plain art.
- effects: `bake_type_appearance`, `bake_appearance` with strokes kept as
strokes: geometry effects on type's fills and strokes still bake in and bend.
- doc: `max_outset` counts type's character strokes as type's own visual
bounds do (`char_stroke_outset`). It left them out, though they already
painted past an envelope's grid where the glyphs reach its edge; this also
grows the visual bounds of blends and repeats of stroked type and the reach
of symbol art holding it.
Paths' strokes, geometry effects, opacity masks, linear gradients and pattern
fills bend as before. The canvas, hit testing, effect bounds, Expand, every
export and the .ai save take the evaluation from the same hooks. Expand of an
envelope of stroked type gives the bent outlines with the strokes on them.
This changes how VectorCraft's own envelopes of stroked type draw.
Tests: envelope_distort::type_strokes_paint_at_their_own_weight_in_a_squeezed_envelope,
a_stroke_on_types_appearance_paints_at_its_own_weight_in_a_squeezed_envelope,
type_strokes_paint_at_their_own_weight_in_an_envelope_inside_an_envelope,
a_paths_stroke_still_bends_in_a_squeezed_envelope,
an_opacity_mask_on_type_bends_as_other_art,
the_visual_bounds_of_an_envelope_hold_its_types_strokes,
the_visual_bounds_of_a_repeat_of_stroked_type_grow_by_half_the_stroke,
expanding_an_envelope_of_stroked_type_keeps_the_stroke_at_its_weight,
bake::tests::type_pieces_bake_their_geometry_effects_and_keep_their_strokes and
tests_native::an_envelope_of_stroked_type_whose_drawn_art_strokes_the_bent_outlines_at_the_weight_opens_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An error a finally block passed on unchanged was reported at the end of
that block, in script.run's report and in the Script Console, because the
block's end raised the error again at a new site. The interpreter now keeps
the site of the exception each finally block runs for, keyed by the stack
slot that holds it, and the block's end throws the exception on from that
site. This holds through nested finally blocks, called functions, resumed
runs and marked host errors. An error the finally block throws is reported
at that throw, and one its return, break or continue discards is not
reported.
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.
The Save PDF dialog's General section gets "Write pages longer than 200 in
at full size in points" (longPagesInPoints in document.exportPdf,
document.export, presets and document.pdfSettings), off by default. When it
is on, a page with a side longer than 14,400 pt is written with its media
box in points and no /UserUnit at every PDF version, and a PDF 1.5 file
stays PDF 1.5. The export's warning for such a page then says it is written
in points. With the option off, and for pages of 14,400 pt or less, the
file is the same as before.
Docs: docs/mcp.md, docs/target-app-parity.md. The new label has a row in
every catalog that lists the dialog's other options.
The 27 commands that act on `ids` or else the selection (Apply as
Filter, Print Ink Fixes, the Arrange, Cleanup and Path Tools plug-in
commands, Smooth, Text Wrap, variable.bind and variable.unbind among them)
refused `ids: null` with "ids must be an array of non-negative integer
object ids". They now read null as `ids` left out, as the other commands
that take `ids` do. Remove Breaks also reads null as left out when it
decides whether to act on the characters the Type tool selected.
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.