Before drawing a shape, the renderer checks that its control points stay
within the rasterizer's limits. For a long path it measured every point
of the path for this check in each frame. The kept path of a long path
now keeps the box of the points of each run of segments next to the run's
bounds, updates it with the runs an edit changes, and answers the check
from those boxes.
With a closed path of 200,000 anchors and no paint (release build,
single-threaded, medians of 21 frames), the part of the frame redrawn
after one anchor moves took 1.93 ms instead of 2.69 ms, and drawing the
same part again with nothing changed took 0.015 ms instead of 0.343 ms.
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.
graph.setType read `axisMin: "10"` as a cleared bound, so the axis went
back to the calculated range, and it ignored a string or number given for
a true/false option, a string for a number and a number for a text option.
graph.setData and graph.create ignored a `csv` that is not a string and
`series`, `categories` or `rows` that are not lists, and read a string in
`rows` as a blank cell.
The three commands now refuse such a value with an error naming the key,
and leave the graph and the undo history as they were. A null is still
read as a key left out, and a null axis bound still goes back to the
calculated value. A negative tick count is refused too.
The Control bar's panel menu lists the bar's groups of options above
Dock to Top and Dock to Bottom, each checked while it is on. A click
turns that group off or on, and a group that is off stays out of the
bar even while its tool or object is selected. A group that is off
takes no room, so the groups after it fit a narrower window. The menu
scrolls in a window too short for all its items.
- window.control.options {group, visible?} sets or toggles a group and
returns {group, visible}; an unknown group or a non-boolean visible
is an error and changes nothing.
- The groups are off only by choice: every group starts on. The choice
is kept with the preferences and with each workspace; files saved
without it show every group, and unknown ids are dropped on read.
- The Artboard tool's bar follows the Artboard Options and Transform
groups; with Image Options off, an image shows neither its controls
nor Fill and Stroke.
- New labels have rows in every complete catalog.
Docs: docs/mcp.md, docs/target-app-parity.md.
tests::pen::a_drag_from_the_first_anchor_closes_the_path expected the
closing drag to give the first anchor an outgoing handle mirroring the
incoming one, which is the behavior #6634 reports. The first anchor is
placed by a click, so after the close it stays a corner with its
outgoing handle at the anchor. The test now checks that.
Double-clicking a paragraph style, or Paragraph Style Options... in the
Paragraph Styles panel menu, opens Paragraph Style Options
(`ui.paraStyleOptions`) instead of the rename dialog. Its sections are
General (name and stored attributes), Basic Paragraph Formats (Alignment,
indents, space before and after), Composition (composer, Roman Hanging
Punctuation), Hyphenation and Justification. OK runs the new
`paraStyle.edit {name, to, attrs}`, which replaces the name and attributes
in one undo step; paragraphs that use the style keep their local
overrides. Fields left as they were keep the style's values, empty fields
leave an attribute unset, Preview can be canceled, and each section's
Reset Panel (`ui.paraStyleOptions.resetPanel`) clears only its own
attributes. New Paragraph Style shows the same sections, filled in from
the selected paragraph or the interface language's paragraph defaults.
`paraStyle.new`, `paraStyle.setAttrs` and `paraStyle.edit` now reject a
changed indent or space outside -1296 to 1296 pt, Hyphenation counts and
zone outside their ranges, Justification values outside their ranges or
out of Minimum/Desired/Maximum order, and tab positions outside 0 to
100000 pt. A value the style already holds is kept.
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.
Under an `/NChannel` color space a mesh corner gives one tint for each
ink. The reader now pairs the tints with the inks in the opposite order
to the listing, for process and spot inks alike: under
`[[Yellow] [Magenta] [Cyan] 3 /NChannel]`, `0 0 1` is full yellow. No
public document describes these records. This is VectorCraft's reading
of them, and under it the meshes match the flattened page that the same
file carries.
The reader gave the first tint to the first ink listed, so mesh areas
came in with the wrong inks, yellow as cyan. Where enough of the art
changed, the layers no longer matched the file's page, and the file
opened as its page instead of its objects.
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.
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.
The word gap is now the Desired word spacing the layout uses for area type
with Block on (TextObject::layout_justification), with Minimum and Maximum
moved out to it when it lies outside them. The paragraphs keep their own
Justification values, so a wide gap no longer leaves a 1,000 % Maximum
behind, and turning Block off brings back the paragraph's own Desired
value. The SVG writer reads the same values.
- The Contextual Task Bar offers Dynamic Text for area type outside a
thread, beside Convert To Point Type.
- type.dynamicText with wordGap or lineGap and no area type in the
selection is an error instead of being ignored.
- Docs: docs/mcp.md, params text, docs/target-app-parity.md.
With the Selection tool on several frames of one threaded story, the
Tabs panel listed the paragraphs to change in the order the frames were
selected, and listed each frame's paragraphs separately. A marker drag
over mixed indents then ran its per-paragraph steps out of story order,
so a step that made text reflow could move a later step's paragraph,
and a paragraph that runs on from one frame into the next got two
steps. After Type tool Select All the split paragraph also got two.
The panel now lists each paragraph of a story once, in story order,
by its last piece with text in a selected frame. With the Selection
tool only the paragraphs with text in a selected frame change, so a
frame selected alone that starts or ends inside a paragraph sets it.
Docs: docs/target-app-parity.md.
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.
release.yml also runs nightly on main with craft-common-actions' dry-run: every release build
except macOS, with nothing signed, pushed or drafted. Breakage that only the release build hits
(riscv64, Flatpak, FreeBSD, packaging) then shows up the next morning instead of on release
day, as it did on 11 Nov 2026.
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.
* fix(svg): first element of an id, shared linked file reads, bounded blank lines
* address review: cache failed linked-image reads but give each href its own placeholder and link
* address review: charge only blank lines to the line-break budget
* address review: a collapsed line break keeps its measured leading in tiny type
* address review: blank lines use the same scaled leading tolerance
Each third-party action that StepSecurity publishes a secure drop-in
replacement for now uses that replacement, pinned to a commit with its
version as a comment. Versions follow what we used: the newest
StepSecurity release in the same major, or the closest major they
publish. dtolnay/rust-toolchain refs that named a toolchain (stable,
nightly, 1.76) become the replacement's `toolchain` input.
random_seed passes the OS random source's result to random_seed_from, so a test can
give it a known value and check the seed it makes, instead of only reading the source text.
Follows #6614. When a closed subpath's refit in two and in three parts is
still lines that enclose no area, for example an S curve closed by a
straight edge whose cut points all lie on that edge, Simplify now splits
each segment in 2, 4, 8 and so on (up to 1,024 pieces, 65,536 in all)
until every piece is within the tolerance of the line between its ends
and those lines enclose an area. With Convert to Straight Lines those
lines are the result; without it the subpath stays as it is. A subpath
whose points are all in line keeps the refit's lines, so its extra
anchors are still removed. Lines count as enclosing no area when every
point is within 1e-6 pt of one line.