A dashed stroke of a path with more than 1,024 path elements was outlined
whole in every frame after an edit. It is now outlined in and around the
view like a solid stroke. dash_kept runs the pattern along every segment
and keeps only the dashes and dots on the segments near the view, so they
sit where dashing the whole path puts them and the pixels are the same.
A pattern too fine to draw is still drawn solid, and that solid stroke is
cropped too.
At 1600 x 1200 the frame after one anchor moves takes 14 ms instead of
102 ms on a filled closed path of 50,000 anchors with 6 pt dashes and
4 pt gaps. anchor_bench now includes that document.
The link-watch worker now signals a condition variable when it ends,
whether it stored its stamps or unwound, and Session::wait_link_scan
blocks until then. The place tests' idle helper waits on it once per look
in place of a 5 ms sleep under a 20 s wall-clock bound, and the engine's
link-watch and link-path tests wait on it in place of spinning under a
10 s bound. The link-watch tests date a rewritten file 2 s after the one
it replaces instead of sleeping 30 ms before writing it.
text.setGlyphAlternate looked for the character's glyph only in a
cluster as long as the character. A character followed by a combining
mark is shaped in one cluster with the mark, so the lookup found
nothing: under a substituting feature such as salt, `gid: null`
reported the default glyph instead of the one drawn, and a request for
the default glyph changed nothing.
The lookup now also takes a cluster that holds the character's
grapheme, and in a cluster of several glyphs takes the character's
default glyph or one of its alternates. Text that overflows its area
is shaped with the whole grapheme.
The method help of paint.editGradient and docs/mcp.md said a Perceptual
gradient with spot stops becomes an image in PDF unless every other stop
is paper white, CMYK or gray. PDF export checks each stop's color as it is
written: a CMYK document, or an export that converts colors to a CMYK
destination profile, writes RGB and Lab stops in CMYK, so the gradient
becomes a DeviceN shading. Both texts now say so, and a test covers the
CMYK document and CMYK destination cases.
text.tabs.set and text.tabs.clear return without an edit when every
targeted paragraph already holds the stops. That path skipped the
settlement Session::edit does first, so a typing session in progress
stayed open. The command now ends it first, as the Variables commands
do: the typing is its own undo step whether or not the stops change.
A spot swatch defined in RGB keeps its spot flag and its own plate when
the document changes to CMYK, and its definition converts like any other
swatch: a fill that uses it and a 40% tint stroke take the converted
color. The Lab spot test's comment now says it covers a Lab definition.
Follows #5442.
- text.setGlyphAlternate keeps the default glyph as a choice when a
feature of the character's style (salt, a stylistic set) draws another
glyph for it, so the default glyph is drawn. A character that is not
laid out (overflowing text) is shaped alone in its style to decide.
gid: null reports the glyph the style draws.
- The Glyphs panel's alternates grid shows the default glyph first when
the font does not list it, so a chosen alternate can be put back.
- A test for a chosen character that starts a longer grapheme (f with a
combining acute), which is not split from its mark.
- The alternates tests in the engine hold the shared font lock while
they add fonts to the global font database.
The comment in add_item said a new fill or stroke is a copy of the top
one. The code takes only the top item's paint, and for a stroke its
weight. The comment now says so, as the params text does.
addon.select.byArtboards {artboards, relation} in a new Selection Tools
add-on. Overlapping is All on Active Artboard's test, now one shared
helper (select::on_artboard); Fully inside is within one artboard, edges
included; Fully outside is on none. Objects are those Select All takes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A nonzero tile pointer below the end of the tile pointer table made the reader decode header or table bytes as pixels. The table end is now computed with checked arithmetic, and such a pointer is refused as damaged image data.
The editing copy of the art that a PDF-compatible .ai carries now holds:
- Transparency: `mode opacity isolate knockout shape Xy` (the blending
mode's number, Knockout Group 0 off, 1 on, 2 neutral), written when it
changes: in a path's paint after its colors, before a guide, and after
a group's end followed by `0 0 Xd` and `6 () XW`. A group without
transparency of its own ends `9 () XW`, and a group's name comes
before its state block. A layer's transparency follows its art: `Xy`,
`0 0 Xd`, `7 () XW`, `LB`.
- Clipping groups as `q`, the art, the clipping path or compound path last
with `h` (or `H`) and `W` before its painting operator, `Q`, as section
5.6.2 of the version 7 specification shows them
(http://mercator.elte.hu/~saman/hu/okt/AI7FileFormat.pdf). A clipping
layer whose clipping object is a path or compound path sets the last
`Lb` flag and writes its clipping path last.
- Opacity masks as an `AI9_Mask` entry in the object's dictionary: the
mask's art between `X=` and `X+`, and Clipping, Inverted, Disabled and
Linked.
Before, clipping groups were written as their art cut to the clipping
path, and transparency and opacity masks were left out. Still left out,
with a warning: the opacity and blending mode of single fills and
strokes, opacity masks on layers (disabled ones too), and opacity masks
nested more than 8 deep. A clipping group or layer whose clipping object
isn't a path or compound path is written as a group of the art cut to
that object, in a layer that doesn't clip, and so are the clipping
groups inside that art. A clipping path's paint that a plain fill and
line can't draw is written as paths inside the clipping, fills under the
art and strokes over it, each side in a group of its own when it has
several, with the clipping path's transparency, lock and hidden state;
the clipping path's opacity mask stays on the clipping path, with a
warning. A clipping group without members is an empty group, and an
empty clipping layer is an empty layer.
Each of these is written in the form VectorCraft's .ai reader reads, and
the tests read the written data back through that reader.
Reading .ai editing data: an `Xy` after a layer's last group or compound
path, followed by the layer's `7 () XW`, is the layer's (a following
`6 () XW` keeps it the group's); groups, compound paths and layers keep
Opacity & Mask Define Knockout Shape.
The `.ai` option `editingData` and docs/file-format-parity.md describe
what the copy now holds.
A `color` parameter shows a color button that opens the color picker in plug-in
dialogs and the Appearance panel's effect editor; its default and values may be
a color object or a hex string, and the plug-in gets a color object. A filter's
returned objects may set `guide` and `locked`; objects it locked or made guides
are left out of the selection after the run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Select > Object > All on Same Layers took the layer of every selected
object, including one that select.set had kept while it is hidden or
locked, on a template layer, on a locked artboard, or outside the
isolated group. That object's layer then decided which art was
selected. The command now takes the layers only of the selected
objects that the Select commands could select. When the selection
holds no such object, it clears the selection.
The PDF writer writes a spot gradient as a Separation or DeviceN shading
only when it is linear or radial; freeform gradients and dithered fills
are written as images in process colors.
- `paint.editGradient`'s method help and the Method paragraph in
docs/mcp.md limit the Perceptual exception to linear and radial
gradients.
- The Linked gradient stops paragraph in docs/mcp.md no longer says the
writer has no DeviceN: a gradient mixing spot inks with other spot,
CMYK or gray inks exports as a DeviceN shading, and only a mix with RGB
or Lab colors is written in process colors, with a warning.
text.tabs.set and text.tabs.clear return without an edit when every
targeted paragraph already holds the stops. A Ctrl-drag that the 0 pt
bound keeps from moving, or a drag released where it started, no longer
adds an undo step that changes nothing. When one targeted paragraph
holds other stops, the command still sets them all.
appearance.addFill copies the active fill row only when the call has no
`ids`, no `id` and no `target: "contents"`; the params text now names
`id` too. appearance.addStroke gives the new stroke at least 1 pt, so a
0.5 pt top stroke gives a 1 pt stroke; the params text now says so.
Tests pin both: `id` alone leaves the active row alone and puts the new
fill on top, and Add New Stroke on a 0.5 pt stroke adds a 1 pt one.
A freeform gradient whose field has no points of its own takes its
colors from the stops, so spot-linked stops in it are written in
process colors too. The export now warns in that case, as it does for
the other gradients drawn as images.
Batch Trace Folder replaces a script, so it is now a Document Tools
add-on command, addon.document.batchTraceFolder, under Object ›
Plug-ins › Document Tools (as Export All Open Documents is), and no
longer in Object › Image Trace. Behaviour, caps and the refusal of a
folder too big to list in full are unchanged; the tests move with it.
The status messages have @msgplural rows in every catalog, as does the
menu label. When some images trace and some don't, the status is one
counted message ("Traced {n} images; not traced: {failed}") so it
translates as a whole.
list_dir cuts its listing before sorting, so a cut listing could skip any image. The batch now
stops with an error instead of tracing part of the folder.
imageTrace.batchFolder opens each image in a folder, traces it with a
preset (optionally expanded), saves it into a destination folder under
its own name (vectorcraft, svg, pdf, ai or eps; taken names numbered so
nothing is replaced) and closes it again whether it traced or not; the
active document stays active. Object > Image Trace > Batch Trace Folder
asks for both folders. Not on the web.
A Copy of ruler guides alone left the system clipboard as it was, so the
next Paste took whatever another app had copied before instead of the
guides. The guides-only Copy now empties the system clipboard (on the
web, empty text through egui with Copy As SVG on), as any Copy replaces
what the clipboard held, and drops an earlier Copy's formats not written
yet. What another app copies afterwards still pastes.
Copying only ruler guides published an empty SVG and text to the system clipboard, replacing
what other apps put there. The clipboard now offers other apps nothing without art. Tests cover
two pastes adding guides of their own on the current layer (following its visibility) and the
SVG carrying no guides.
With anchors and ruler guides selected, Cut deletes the anchors and the
selected guides in one undo step (edit.clear's guides: true reaches the
anchor-deleting path).
Cut with objects and ruler guides selected now removes both, in one
undo step: edit.clear takes guides: true beside ids to delete the
selected guides with the objects it names.
Edit > Copy and Cut put the selected ruler guides on the clipboard,
with the selected objects or on their own. Pasting puts them on the
layer new art goes to, selected, in the same undo step: with objects
they move as the objects do; alone they keep their places, while Paste
in Place / in Front / in Back put them on the active artboard as they
lay on theirs, and Paste on All Artboards on every artboard.
text.areaOptions given for several area type frames (ids, or a selection of
several) copied the first frame's options onto all of them, so setting one
option (say the vertical alignment) also replaced the others' columns, gutter,
inset and fit. Each frame now takes only the options given over its own, and
fit resolves against each frame's own setting, so fitMinPercent alone changes
only frames that already shrink text to fit. Options that change nothing add
no undo step and keep redo.
A PDF-compatible .ai (or a PDF saved with the editing data) now opens
from its layers without its PDF part being imported, and without being
compared with its page, when:
- its text document makes the type of every text object, hidden ones
included, at least one text object shows, and none is drawn by the
appearance of the layer or group it is in;
- its layers hold no unread object, no placed file with a second
placement and no live envelope with drawn art to fall back on, and
no image too large to read;
- it declares no large canvas, and its artboards are its pages' trim
boxes one for one, in points or in the first page's UserUnit.
A file whose layers draw differently from its page then opens from its
layers instead of its PDF part. The bleed comes from the pages' bleed
boxes. The check runs only when no pages are picked, layers and the
editing data are on, the crop box is asked for and type comes in as
text. Files saved without PDF compatibility, files whose PDF part
draws only type or nothing, and files whose PDF part's content can't
be read take the same path when they qualify: their bleed comes from
the pages' bleed boxes, pages in units larger than a point draw them
at their size in points, and the note that the PDF part couldn't be
read is not given, since its content is not read.
Files that don't qualify open as before, and the editing data decoded
and read for the check is reused after the PDF part is imported
(layered_ai_from).
When a file whose PDF part can't be read is scaled up to its size in
points, its text wraps are now laid out again at that size, and the
default graphic styles of a file without graphic styles of its own are
no longer scaled. The same applies on the new path.
#5139 (in 0.21.0) made a positive Twist angle turn clockwise, but
documents saved by earlier releases stored their angles with the other
sign and reopened twisted the other way.
The document version (FORMAT_VERSION) goes from 1 to 2. Opening a
version 1 document negates its Twist angles (in layers, opacity-mask
art, Shaper edits, symbols, graph designs, pattern swatches, graphic
styles and brush definitions), unless the file is of the current file
version and its generator is VectorCraft 0.21.0 or later. The document
opens and saves as version 2. A save for an older file version, which
only apps before 0.21.0 read, writes a version 1 document with the
angles negated back. Graphic style libraries (.vcstyles) go to version
2: a version 1 library's Twist angles are negated when it is read.
Readers do not check either version, so older builds open the new files
as before.
Add New Fill again copies only the active fill row's paint; Duplicate
Item copies a whole row. The params text of appearance.addFill and
appearance.addStroke now says the new fill or stroke takes the paint
(and for a stroke, the weight) of the row it starts from, and a test
pins the paint-only copy.
With a fill row active, Add New Fill copied only the row's paint and
reset its opacity, blend mode, visibility, overprint and effects. The
new row is now a copy of the whole active row, as the command's
description says.
The `method` text of `paint.editGradient` and the Method paragraph in
docs/mcp.md said SVG and PDF exports draw a Perceptual gradient as an
image. PDF writes an eligible one natively: tints of one spot ink as a
Separation shading in that ink, and spot inks mixed with other spot,
CMYK or gray inks as a DeviceN shading. Gradients without spot stops,
with RGB or Lab stops, dithered fills and curves with too many stops
still become images there. Both texts now say so.
text.setStyle and text.setRangeStyle collected the fonts for their
warning after Missing Glyph Protection had run. A character the new font
has no glyph for keeps its earlier font, and when that font was not
available the warning named it, although the command did not set it.
protect_missing_glyphs now returns the byte ranges that kept their font,
and both commands leave those ranges out of the warning's fonts.
Reduce to Basic Appearance and Add New Fill matched the Appearance
panel's active row only when every selected object had an equal row.
With three objects whose third had a different lower fill, the second
object lost the match too: Reduce kept its top fill instead of the
active one, and Add New Fill put the copy on top instead of above the
active row.
items_per_target now matches each object against the first one
separately, so an object keeps the match whenever it has an equal row.
layer.setProps, layer.new, layer.newSublayer and livePaint.gapOptions read
an 8-digit hex color such as #ff000080 as #ff0000 and set it without its
alpha. They now refuse it with the error a bad color already gets, and
change nothing. #rgba was already refused. #rrggbb, #rgb, a preset index
and a preset name are read as before.
The params text of select.same.symbolInstance, docs/mcp.md and
docs/target-app-parity.md now say that the object compared with can be
of any kind (a group member selected on its own, or an object that is
not a symbol instance, which leaves the selection empty), and that new
art on a locked artboard is not passed over while it stays selected.
A test covers the three cases.
Done (pattern editing), Stop Editing Opacity Mask and Exit Isolation
Mode end the Type tool's edit first, so point type a click placed and
left empty is discarded. While a Cmd/Ctrl press had lent a selection
tool, the Type tool was not the active tool, its edit was not ended,
and the empty point type was saved into the pattern tile or the mask.
The lent tool is now given back first. A drag the lent tool started is
kept as its own step, with its last position applied when Real-time
Drawing and Editing is off.
A layer nested in a group whose art was all locked counted for nothing,
so the group was hidden or locked with the layer inside it. The selected
layer now counts as selected itself.
While Lock Guides was on, a guide path that was locked itself, or sat in
a locked object, was not kept, so the group around it was hidden. Guide
paths inside locked objects are now kept too.
The renderer outlined the whole stroke of an edited path on every frame,
so dragging one anchor of a path with many anchors re-outlined every
segment. A stroke line of more than 1,024 elements without dashes or a
width profile is now outlined only for the segments within the stroke's
reach of the view, inflated by the view's own size on each side. The
cached outline keeps that region, and a frame outside it outlines the
stroke again. Closed subpaths that a wide stroke crosses over stay whole,
and a cut closed subpath is walked from a removed segment so no cut lands
where it starts and ends. The pixels are the same.
At 1600 x 1200, the frame after one anchor moves takes 28 to 30 ms
instead of 103 ms on an open path of 200,000 anchors, and 10 ms instead
of 30 ms on a closed path of 50,000 anchors (the new anchor_bench
example: cargo run --release -p vectorcraft-engine --example anchor_bench).
Remove Text Tweaker's own start and end check: range_of already refuses
a bad offset given with an id, and like the other type commands Text
Tweaker now ignores start and end without one. The test's "2" case
gets an id so it still reaches the check, and docs/mcp.md lists
addon.type.textTweaker with the commands that refuse such offsets.
text.startList read a negative or fractional `at` as the end of the
text, so it could remove a typed list marker there and start a list.
The `start` and `end` of text.editRange, text.getRange,
text.setRangeStyle and the type shortcut commands given an `id` read
such values as the start or end of the text.
These now return "`at` must be a whole number" (naming the key) and
change nothing. Omitted or null keys keep their defaults, and 2.0 is
read as 2.