Commit Graph
2228 Commits
Author SHA1 Message Date
dotCooCoo 6e2a32c2a0 .ai import: legacy type used as a mask clips its clipping group
In the legacy format, `Tr` sets the rendering mode of the type that
follows. Section 5.7 of the version 7 specification says that modes 4 to
7 make the text object a mask: 4, 5 and 6 also fill, stroke, or fill and
stroke it, and 7 paints nothing. Sections 15.3.2 and 15.4.5.1 add that
these modes are used in pattern definitions, and the reader does not
read type in a pattern. The reader painted 4 to 6 like 0 to 2 and never
clipped, so the art a text mask clips showed whole.

A text object with characters in modes 4 to 7 is now a clipping path of
its clipping group (`q` ... `Q`, or the layer), as a path painted with
`W` is, and the whole object is the mask. Beside other clipping paths it
nests as they do, and the mode lasts into later text objects. Area type
with linked areas (threaded frames, also when the reader's limits keep
one frame), and type in wraparound text or in a compound path, a group
inside them included, doesn't clip, with a warning.

A `Tr` operand that is not a whole number from 0 to 9 now leaves the
mode as it was; before, 4.5 read as 4 and -4 hid the type.

The new warning and the reader's warning about linked type areas left
out get rows in every catalog.
2026-11-05 21:18:44 +07:00
dotCooCoo e55c6d9d12 .ai import: gradients and patterns are named up to the newline in their names
The Adobe Illustrator File Format Specification 7.0, section 4 (p. 51),
says the name shown for a global object is the part of its name in the
file before the newline
(https://13thmonkey.org/documentation/fonts/AI7FileFormat.pdf). The
legacy reader kept the whole name, so swatches and patterns came in
named "Sky\n1". The reader now applies this rule to the files of every
version it reads.

- Gradient swatches, gradient fills, pattern definitions, pattern fills,
  palette cells and art-style pattern fills use the part before the
  newline. Lookups inside the reader still use the whole name.
- When that name is taken, the first free "Sky 2", "Sky 3" is used.
  Taken names are None and Registration, the palette's own swatch and
  group names, the default swatches when no palette replaces them,
  named colors (color operators, gradient stops, version 7 pattern
  strings, the header's and an EPS program's custom colors), the names
  of gradients and patterns that have no newline, and the names given
  to earlier gradients and patterns. Only content the reader reads
  counts: not the alternates of versioned content, the prolog, skipped
  sections or what follows the trailer.
- A name with nothing before the newline comes in as "Gradient" or
  "Pattern". Names without a newline do not change.
- docs/file-format-parity.md lists the rule.
2026-11-05 21:06:27 +07:00
dotCooCoo 99169d5404 Reject malformed Batch Trace Folder options 2026-11-05 20:18:52 +07:00
FractalEncrypt e0ad7ccf16 Preserve raster coverage of thin curved strokes
Squashed from #3273 onto main.
2026-11-05 20:00:21 +07:00
FractalEncrypt 884a330fae Keep Group Selection drags on the selected outlined component
Squashed from #3272 onto main.
2026-11-05 20:00:12 +07:00
dotCooCoo 7973439cc2 .ai import: a re-encoded font's own encoding pairs decode its type
In the legacy format, `TZ` makes a re-encoded font from an existing one
and can give it encoding pairs of its own, which change only that font's
encoding. Its `useDefault` operand says whether the file's `TE` encoding
also applies to the font (Adobe Illustrator File Format Specification
7.0, section 3.3.1,
https://13thmonkey.org/documentation/fonts/AI7FileFormat.pdf). The
reader skipped the body of `%AI3_BeginEncoding` sections and ignored
`TZ`, so every string was decoded with `TE`'s encoding.

Strings in a re-encoded font are now decoded with its own pairs, then
with `TE`'s encoding when `useDefault` is 1, and as Windows-1252 for the
codes left. Strings in other fonts keep `TE`'s encoding. The reader
reads `TZ` in the body of an `%AI3_BeginEncoding` section and wherever
else it appears in the script, with or without the direction operand
and the Multiple Master weight vector. The font names are the last two
names, and only numbers and the weight vector may follow them; the
reader does not use a `TZ` of another shape. The first `TZ` for a font
is used.

`TZ`'s font names also map the re-encoded font to the font it was made
from, as the `%AI3_BeginEncoding` comment does. Type in a re-encoded
font that no such comment names now opens in the font `TZ` names; when
both name the font, the first one read is used.
2026-11-05 19:40:58 +07:00
dotCooCoo ab86a62ea0 .ai import: type with the older TS, Te and TM operators opens
The version 7 and version 3 file format specifications list three text
operators the editing-data reader did not dispatch, so a file whose type
used any of them on a visible layer did not open from its editing data:

- `(text) justified TS` (special characters): the reader adds its
  characters to the story, as it does for `Tx` and `Tj`.
- `Te` (end render) is read and ignored. The reader accepts it with any
  number of operands.
- `TM` (pop text matrix) is read and ignored.

Tests cover each operator in a text object, `TS` without a string and
outside a text object, and `Te` inside a tab definition. The import fuzz
tests add the operators to the legacy type tokens.
2026-11-05 19:28:59 +07:00
KnottyDyesandClaude Opus 5.5 65b8192b7e Docs: a document at another resolution exports at it, not 72 ppi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 19:02:23 +07:00
KnottyDyes c9588457b9 Export: raster output defaults to the document resolution 2026-11-05 19:02:23 +07:00
Alex 82251c8e8b Layers: a layer that does not print shows its name italic (#5739)
Layer Options › Print off means the layer's art does not print. The Layers panel now paints
that layer's name italic, as Illustrator shows non-printing layers, and the ghost of a row
being dragged keeps the italic. A printing layer's name stays upright, and ticking Print
again restores it.

Row names are painted rather than widgets, so the italics come from a layout job's text
format (egui slants the glyphs itself; no italic face is needed).

Tests: panels::layers::tests::a_layer_that_does_not_print_has_its_name_italic covers Print
off, Print on again, and a plain printing layer; the layers suite (64 tests) passes.
Docs: roadmap, target-app-parity and ui-parity carry the change.
2026-11-05 19:02:23 +07:00
dotCooCoo d5473acb09 .ai import: 1-bit image masks open colorized by their fill color
The version 7 file format specification gives `XI` and `XF` an
ImageMask operand: 0 is an opaque image, 1 is an image that is
transparent and can be colorized (sections 8.1, 8.2 and the 1-bit
examples in 8.4,
https://13thmonkey.org/documentation/fonts/AI7FileFormat.pdf). The
reader ignored the operand, so a 1-bit image mask opened as an opaque
black-and-white image.

A 1-bit gray image with ImageMask 1 now opens with the fill color set
before it as the color that colorizes it. A spot or global color keeps
its swatch and tint. When the fill is a pattern, a gradient or none,
the image is colorized black. VectorCraft's own rule is that the pixels
that open black with ImageMask 0 take the color and the others are
transparent. A linked image (`XF`) keeps the same color for when its
file is relinked. 8-bit gray, RGB and CMYK images, and images with
ImageMask 0, open as before.
2026-11-05 18:50:05 +07:00
dotCooCoo 68f6f8755b .ai import: several clipping paths clip to their intersection
In `.ai` and EPS editing-data import, a clipping group, layer or opacity
mask's art that holds more than one clipping path (`W`) now clips to the
intersection of the paths, as each `W` intersects the current clip (AI7
spec section 5.6.2,
https://13thmonkey.org/documentation/fonts/AI7FileFormat.pdf). Before,
the paths were joined into one compound path, which clipped to their
union, and kept a stroke only when every path had the same one.

Each clipping path after the first clips a clipping group nested inside
the one before it, and keeps its own stroke, opacity and blend mode. The
innermost clipping group holds the art in the order written; as before,
every object in the group is clipped by all of its clipping paths. A
clipping path's painted fill stays at its path's level. A compound path
of clipping paths (`*u` ... `*U`) is still one clipping path. Clipping
paths that would nest the art past the document's 32 levels stop the
editing data from being read.
2026-11-05 18:37:23 +07:00
Alex 347b7e46bc Panels: a floating palette keeps out of the way of dialogs and menus
Floating panels are OS windows of their own, and craft-ui keeps a palette above the
app's other windows while it is active. A modal dialog or an open menu is drawn in
the main window, so a floating panel covered it: the Colour Picker's OK button and
fields, Recolor Artwork, and the menu bar's drop-downs (#5238, defect 1).

While a modal dialog or a menu of the in-window menu bar is open the docking renderer
hides each palette window, and when neither is open each palette shows again at its
palette level: topmost while the app is active, behind other applications' windows
while it is not, as craft-ui leaves a palette it creates. Hiding rather than lowering
covers both: a lowered window can be clicked back to the front, and craft-ui varies
the level through the viewport builder, which egui applies when the window opens and
not to one already on screen, so the commands go explicitly to the windows there are.

The menu bar tells the docking renderer whether one of its top-level menus is open,
the same state its hover switching uses; egui's own "any popup open" misses the
in-window menu bar.

Measured on Windows with the app focused and panels floating: before, a palette was at
the front of the z-order and topmost over the main window with the Colour Picker open;
after, the main window is in front and the palette is hidden until the dialog or menu
closes.

Tests: a_modal_lowers_the_native_palette_windows and
a_menu_of_the_menu_bar_lowers_the_palette_windows check the commands sent, and
a_palette_window_keeps_out_of_the_way_of_the_main_windows_dialog_and_menus the level
and visibility table. The wheel test
`wheel_over_a_floating_character_font_field_changes_the_font` fails on this machine
without this change too (font-order dependent), so it is not part of it.
2026-11-05 18:11:00 +07:00
dotCooCoo 653e6ff7fb .ai save: the editing copy keeps images and linked files
The editing copy of a PDF-compatible .ai wrote every image as its outline
filled with its average color. Embedded images are now written with their
pixels, and linked images as placed files with a preview:

- Embedded images are rasters in the form of the version 7 specification
  (section 8, https://13thmonkey.org/documentation/fonts/AI7FileFormat.pdf):
  %AI5_BeginRaster, the color space, the image matrix and the XI operands
  of current files, then the samples in binary in a %%BeginData section.
  Gray, RGB and CMYK images keep their samples (CMYK images their ink
  amounts), and see-through images their alpha channel after the color
  samples. The matrix is written to ten decimals.
- Linked images are placed files (section 16): the matrix, the box of the
  file's pixels and its path, %%IncludeFile, the folder relative to the
  document's (when the relative path ends in the path's file name), and a
  preview of at most 256 pixels a side.
- Images that can't be decoded or have more than 33,554,432 pixels (the
  most the reader reads) are written as their outlines filled mid gray,
  images of 16 bits a channel (CMYK TIFFs included) at 8 bits, and linked
  images in opacity masks and pattern tiles as embedded images, each with
  a warning.

Each of these is written in the form VectorCraft's .ai reader reads, and
the tests read the written data back through that reader.

The reader now gives an image its name and opacity mask when the
operators that end its raster (XH, Xr and the N after %AI5_EndRaster) come
before its dictionary, reads an empty relative folder as the document's
own folder, and looks for the %%EndData that ends an image's samples
after the alpha channel its XI operands give it, preferring one whose next
line is XH, so samples that hold the marker's bytes are read whole.

Tests: the round trip of every kind of image and its state, a second write
of what is read back giving the same bytes, the raster and placed-file
forms, previews of large linked images, and the engine's editing copy with
every save option; a fuzz case for damaged images and placed files.

Docs: docs/file-format-parity.md.
2026-11-05 17:38:55 +07:00
dotCooCoo 48f0347351 .ai import: area type with linked areas opens as threaded frames
In the legacy format, area type whose story flows through several linked
areas has one text path (`Tp` ... `TP`) for each area, each with its own
matrix and frame (version 7 specification, sections 15 and 15.5.3). The
reader kept only the first frame, placed it with the text object's last
valid matrix, and put the whole story in that one frame.

Each linked area with a frame now opens as its own area type object,
placed with its own matrix, and the frames are threaded in file order.
The first frame holds the story: the characters after every `TP`, and
the overflow text. When the file opens, the story is laid out again
through the thread (section 15.1 lists the flow from area to area as
final-form information). A linked area without a frame is left out.
Area type with one area opens as before.

The dictionary that follows a text object (its opacity mask, text wrap,
name and other keys) went to its one object. Its opacity mask and text
wrap now go on every frame, and its other keys on the first frame.

Point type is read with its first text path's matrix, and type on a
path with the matrix and start of the first text path that has a path.
Area type with no frame at all opens as point type with its first text
path's matrix. Before, these used the last valid matrix and, for type
on a path, the last start.

One text object reads at most 4,096 text paths. The number of frames
times the story's character count is kept to 4,194,304, so a longer
story keeps fewer frames. Frames past either limit are left out, with a
warning.
2026-11-05 17:17:36 +07:00
dotCooCoo c5269bd096 .ai import: version 7 linked images open as linked images with a placeholder
A legacy .ai in the version 7 form names a linked image's file with
`(path) modified XG` and draws the image with `XF`, which takes the
operands `XI` takes and has no samples after it (Adobe Illustrator File
Format Specification 7.0, sections 8.2 and 8.3). The reader ignored both
operators, so the image was dropped without a warning.

- `XF` after `XG` becomes a linked image of that file, its pixels placed
  as an `XI` image of the same operands is placed. The file keeps no
  pixels of it, so a placeholder stands in until the file is relinked in
  the Links panel, with a note in the open result. No pixels are read,
  so the image is not held to the pixel limit of `XI`'s samples.
- An `XF` without a file name before it, or with operands `XI` couldn't
  read (no matrix, too few numbers, no pixels, or samples of other than
  1 or 8 bits), is left out with the existing "an image VectorCraft
  can't read was left out" warning. An `XG` applies only to the image
  after it in the same raster object; `() 1 XG` before an embedded `XI`
  keeps that image embedded.
- A PostScript page draws nothing for `XF`, so there the placeholder's
  area is left out of the comparison of the layers with the page, and
  the file keeps its layers. A PDF part draws the image's pixels, so a
  file with one is compared with it there as before, and its layers are
  not used without the PDF part.
- Linked images of different files get image keys of their own even
  when they show the same pixels (the placeholder, or two identical
  placed-file previews), so updating or exporting one file's image
  reads that file only.
- `vectorcraft_doc::links::missing_placeholder` draws the placeholder: a
  light tile, crossed out, as SVG import shows for a missing file.

Tests: reader tests for the placement, the image keys and the cases left
out, an end-to-end test of a legacy file with a linked image, a test of
a file with a PDF part, and a fuzz case for damaged files with `XG` and
`XF`. The new note has a row in each catalog that translates the
placed-file note.
2026-11-05 17:02:57 +07:00
dotCooCoo a90ffe2a5f .ai import: object tags open as each object's URL and data
`.ai` and EPS editing-data import now reads object tags
(`/identifier (string) XT`) and applies each one to the object before it,
whatever kind of object it is. The `AdobeURL` tag sets the object's URL.
Every other tag is added to the object's data in order, up to 64 per
object. A tag with no object right before it, with an empty identifier,
or with an identifier or string longer than 64 KiB is ignored, and so
are tags in a pattern's or brush's art. A clipping path keeps its own
tags; the copy that paints its fill gets none.

Before, a tag on art that shows was an operator the reader did not know,
so it stopped the editing data from being read, and brush art holding a
tag was not read, so the brushes using it were left out.

An image's tags follow the `XH` that ends its data, and the state written
after a group or a gradient mesh is still theirs when their tags come
first. When a drawn look stands for an object whose style is not found,
the look keeps the object's note, URL and data, and a live blend keeps
those of its drawn steps.
2026-11-05 16:04:32 +07:00
dotCooCoo cefb44c033 MCP: pointer_gesture fails when the input sent before it is dropped
Squashed from #5772 onto main.
2026-11-05 15:38:38 +07:00
dotCooCoo 1f68248bc5 .ai save: the editing copy keeps gradients and patterns
The editing copy for other apps now writes linear and radial gradients and
grid patterns as themselves instead of as average colors and cut tiles.

- Gradients the art uses are defined in the setup (`n Bn`, then
  `%AI5_BeginGradient` … `Bd`, the stops as `Bs` with their opacity and
  midpoints, `BD`), and gradient swatches nothing uses in a non-printing
  block. Stops keep gray, CMYK, RGB and spot colors at a tint. A
  gradient that isn't a swatch gets a name that no swatch or pattern of
  the document has.
- Each fill or stroke with a gradient is an instance around its painting
  operator (`Bb`, `Bh`, `Bg`, `Xm`, `Bm`, `Bc`, `BB`), on a compound
  path's last subpath and on clipping paths too. `Bg` places the gradient
  relative to the path: a linear one by its angle and its start and length
  as fractions of the path's extent along it, a radial one by the matrix
  that takes the unit circle onto its extent ellipse, its center as
  fractions of the path's bounds in that circle's space and its radius as
  a multiple of the one the center's distances to the right side and to
  the bottom give. In a pattern's tile the placement is mirrored top to
  bottom.
- Patterns whose tiles make a grid and hold their art are defined with
  `%AI3_BeginPattern` … `E`, the tile's box first, and fills and strokes
  paint with `p` and `P`; gradient and pattern swatches are in the palette.
- Freeform gradients, gradients along or across a stroke and gradients
  without stops, with more than 256 stops or without extent are written
  as their average color, and other patterns as their tiles cut to the
  shape, each with a warning. Gradients that dither or mix perceptually
  are written as classic ones, stops linked to global process colors as
  their colors, and pattern swatches without the placement that moves,
  turns or scales their pattern, with warnings.
- Reading: an unpainted box that a pattern's definition marks as its
  tile's box (`AIPattern_Editor_Backing_Tile_Rect`) is no longer read as
  art, and a linear gradient whose stops all lie at one place keeps each
  stop on its side.

The definitions, instances and patterns follow sections 3.4, 3.5 and 14
of the version 7 specification
(http://mercator.elte.hu/~saman/hu/okt/AI7FileFormat.pdf). The stop
opacity, `Bg`'s placement and the marked tile box follow the newer form
that current .ai files carry, which that specification predates.

Each of these is written in the form VectorCraft's .ai reader reads, and
the tests read the written data back through that reader.

Docs: docs/file-format-parity.md.
2026-11-05 15:38:38 +07:00
dotCooCoo bf7ca194bb Type: Convert to Text leaves a paragraph that starts in a locked frame of the selected story
While Select All holds a threaded story, a paragraph whose text starts in one of the
story's locked or hidden frames is no longer converted, and the empty parts of a converted
paragraph in such a frame are no longer restyled. Before, a locked frame whose first
paragraph took in the previous frame's empty last part was converted, and so was a
paragraph that ran on from a locked first frame into an editable one.
2026-11-05 14:34:27 +07:00
dotCooCoo 2c1f3d7605 Type: text cleanup and Convert to Text act on every frame of a selected story
After Select All in a frame of threaded text, Remove Line Breaks and
Remove Paragraph Breaks changed only the edited frame, and Convert to
Text over MCP converted only the selected frame. Both now act on the
text of every frame of the story as one undo step.

- addon.cleanup.removeBreaks reads the frames of the story as one text,
  so a break at the end of a frame before a word in the next one becomes
  a space. The same applies when every frame of a story is selected as
  objects.
- text.convertListToText without ids, id or a range converts every
  paragraph of the story. Locked and hidden frames of the selected story
  are not converted, even when ids names them. When the call converts
  the whole story, the story stays selected; before, the call reset the
  Type tool's selection to the edited frame.
- A selection inside one frame, and unthreaded type, behave as before.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-05 14:34:27 +07:00
dotCooCoo 2038dc7849 SVG export: numbers are written from integers, not through the exact float formatter
push_num formatted every number with write!("{:.*}"), which runs the
exact decimal float formatter, then scanned and trimmed the text. That
formatter was about 95% of the time to export a path of 200,000
anchors.

At 9 decimals or fewer, push_num now scales the value by 10^decimals
and writes the digits of the rounded integer when that rounding is
certain: the scaled value is below 2^53 and further from a half than
the scaling's rounding error. Ties and larger values still go through
the formatter, so the output is the same byte for byte.

Exporting one path of 200,000 anchors takes a median of 43.4 ms
instead of 167.0 ms, and 20,000 rectangles 81.4 ms instead of 141.4 ms
(Windows 11, release build).
2026-11-05 14:12:19 +07:00
dotCooCoo e9be911c9b MCP: pointer_gesture rejects a backquote that is not a boolean
A `backquote` given as a string or a number, on the request or on an
event, was read as absent, so `"true"` moved the object's geometry
instead of its pattern. `pointer_gesture` and `ui.pointer`, headless
and desktop, now answer with an error before any event runs.
2026-11-05 14:12:18 +07:00
dotCooCoo 50128e6025 Render: adding or deleting an anchor of a long path splices the kept path
When anchors of a kept path (a path or compound path of 1,024 anchors or
more) are added or deleted, or a subpath is closed or opened, the next
frame writes again only the path elements from the first anchor that
changed to the last, and the closing segment and ClosePath when an end
anchor changed or the subpath was closed or opened. The elements after
the change move with it. Before, such an edit made the whole path again
and measured it whole.

- The runs of segments now carry their own ends. The runs that held the
  segments written become one run, split into runs of 128 when it grows
  past 256, and only those runs are measured again, with the miter tips
  of the run of the segment with length before each span. When a
  subpath is closed or opened and the stroke reaches farther from one
  than from the other, all of that subpath's tips are measured again.
- On a subpath that is open before and after the edit, the handle before
  the first anchor and the one after the last are not compared, so
  Delete Anchor Point, which clears them, writes again only the segments
  around the anchor deleted.
- A subpath that has fewer than two anchors before or after the edit is
  written again whole. A compound path member that gains or loses
  subpaths is made again. When members are added or removed, those
  members and every member between the first and the last of them are
  made again, and the members before and after them stay.
- An edit that adds or deletes anchors in, closes or opens more than 16
  subpaths makes the whole path again, as before.
- An edit that leaves every anchor as it was, such as a new stroke,
  leaves the kept path as it is.

The pixels and the cull bounds are the same as with a path made again.

Docs: docs/target-app-parity.md.
2026-11-05 14:12:17 +07:00
ruchirguitarandClaude Opus 5.5 7dc66d660a Fix #5526: select images by link status
Selection Tools add-on: addon.select.byLinkStatus selects the image
objects and placed documents whose link is linked, embedded, missing or
modified (one status or several), as links.list reports it. Optional ids
limit the search, add adds to the selection, and zoom runs the existing
Zoom to Selection in the desktop app. Objects that are hidden, locked, on
a template layer or outside the isolated group are left out and returned
with the reason. Only the selection changes.

Select Linked / Embedded / Missing / Modified are in Object > Plug-ins >
Selection Tools and the Links panel menu. Each is off when no image has
that status, and Shift adds to the selection. Select by Link Status...
lists the images with name, status and format, with a name search, a
status filter, checkboxes with All / None and Zoom to Selection. The
status bar says what was selected and how many were left out, with
plural rows in every catalog.

Tests: engine (statuses, ids, add, zoom, skip reasons, bad params),
command sweep, and UI (message, list filtering, menu items, Links panel
menu, headless dialog frame). Docs: mcp, target-app-parity, ui-parity,
roadmap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:12:10 +07:00
dotCooCoo ec13f54f3c Layers: the shown document's thumbnails are not held up by another document's queue
The Layers and Links panels share one thumbnail thread across documents,
and it ran its jobs in the order they came. Rows of a document left with
a long queue held up the thumbnails of the next document shown.

The thumbnail thread can now drop the jobs it has not started for some
slots. Each frame the Layers and Links panels name the document they
show; when it changes, the queued jobs of every other document are
dropped, and when no document is open, all of them are. Those rows ask
for their thumbnails again when their document shows again, and the
thumbnails already made are kept. The Navigator and the arrange panes do
not use the new call.

With 150 rows of 20,000-anchor paths queued for one document, the 8 rows
of such paths of the next document shown get their thumbnails in 153 ms
instead of 3,265 ms (debug test build, median of three runs).
2026-11-05 13:58:16 +07:00
Kindmark 1b8d96433e Compound shapes default
Squashed from #5757 onto main.
2026-11-05 13:58:16 +07:00
dotCooCoo d88f5eeed9 Render: a moved path reuses its gradient stroke slices
A path whose stroke has a gradient along or across it kept its slices under
the node's address, so every nudge made the slices again for the whole path,
although its stroke outline was already reused. The renderer now keeps the key
of a stroke's latest slices under the object's id, the stroke's place and the
tolerance level, and when the object has only moved (same stroke and gradient,
same fill rule, anchors moved by one offset) it draws those slices moved by the
offset, translating only the slices that reach the piece being clipped. Scale,
rotate, an anchor edit or a change to the stroke or its gradient makes the
slices again. The pixels are the same.
2026-11-05 13:58:16 +07:00
dotCooCoo 624c82941e MCP: remote press_key does not run single-key shortcuts
docs/mcp.md said that with nothing being dragged, remote press_key c
chose the tool bound to C and f ran the shortcut bound to F. Remote
press_key sends the key event without the letter it types, and the app
runs single-key shortcuts only from the typed letter, so neither runs.
The Arc tool, Selection marquee and grid paragraphs now say what
headless press_key does with these letters and point remote callers to
select_tool or invoke_menu.
2026-11-05 13:33:58 +07:00
dotCooCoo 94424bfa0c MCP: pointer_gesture waits for a key sent before it
In remote mode, press_key queues its key for the next UI pass, while
pointer_gesture drives the tool as soon as the app reads the request. A
release sent right after a key could commit the drag before the key
reached the tool, so C pressed while dragging out an arc left it open.

A ui.pointer request now waits, ahead of the requests after it, until
the synthetic input sent before it has been applied.
2026-11-05 13:26:22 +07:00
KindmarkandClaude Sonnet 5.5 db1ef1bfed Contour: make it a built-in add-on and translate its labels
Review of #5694 asked for two changes.

Add-on, not a core tool, as Enclosing Lasso is:
- The command is addon.contour.set, in an add-on module of its own
  (crates/engine/src/cmd/addons/contour.rs) with its tests beside it.
- The tool is listed under Object > Plug-ins > Contour and under its own
  "Contour" heading in All Tools. It has no Basic or Advanced toolbar
  slot and no default shortcut. The Contour bar is unchanged.
- The add-on drawer test lists Contour with the other add-on tools.
- Docs (mcp.md, the parity documents, ROADMAP) say add-on and use the
  new command name; mcp.md gains a Contour add-on section.

Translations: rows for "Contour", "Contour Tool", "Sharp", "Bake" and
"Turn the Contour into plain paths" in all 26 catalogs ("Round" and
"Square" already had them). Drafted from each catalog's own vocabulary
(for Bake, the verb it uses for Expand Appearance; a word other than the
catalog's Stroke or Path word for the tool's name where they collide);
they are not native-speaker reviewed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 12:57:51 +07:00
KindmarkandClaude Sonnet 5.5 8584bf1ab6 Contour: live offset tool with a floating bar and its own icon
Add a Contour tool (Modify group). Dragging away from a selected path or
shape's edge makes a copy of it that carries a live Offset Path effect,
grown by the drag; dragging toward the inside shrinks it. The result is
one undo step and stays editable until baked with Expand Appearance. A
second drag on a selected Contour changes its distance instead of
nesting another effect.

- object.contour: the command behind it (offset, sharp/round/square
  corners, miter limit), usable over MCP and the control channel too.
- Contour tool: live preview, Shift rounds to whole points, Escape
  cancels, a dashed guide with a white halo shows the outline the shape
  had, and a pill shows the distance.
- Contour bar: a floating bar above the selection with the offset box,
  Sharp / Round / Square and Bake (Expand Appearance).
- Toolbar slot under Modify (Basic and Advanced) and dc-contour.svg, an
  original icon with its ASSETS.md row.
- Tests for the command, the tool's pointer gestures and the bar; the
  border-with-a-gap recipe (two Contours, bake, Minus Front) is covered
  end to end.
- Docs: beyond-Illustrator row, revision histories and the ROADMAP log.

Beyond Illustrator: a VectorCraft design built on Offset Path. Scores and
estimates unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 12:57:50 +07:00
dotCooCoo 30a2d85658 Global Edit: the enabled check decides without normalizing every anchor
The Control bar asks whether Select > Start Global Edit can run every
frame it shows a one-object selection. For a path, compound path or
symbol instance that check built the object's form: a copy of every
anchor and handle, normalized into a unit box. On a 200,000-anchor path
it took about 2.2 to 3.0 ms a frame.

The check now walks the object the same way and measures only the box
of those points, then applies the same size test, so it gives the same
answer for every object without allocating. Starting Global Edit still
builds the forms it compares.
2026-11-05 12:36:41 +07:00
jo-quast bddb834231 Part of #394: each run hyphenates in its own language; Default Language sets new type's
Squashed from #2818 onto main.
2026-11-05 12:17:33 +07:00
ruchirguitarandClaude Opus 5.5 e0645341a4 Document tabs: drag a tab along the strip to reorder the open documents (#5663)
A document tab dragged sideways along the tab strip moves its document to the
place an insertion line marks; the current document stays current. Dragged
above or below the strip, the tab still tears off into a floating window. The
views Window > New Window opened move with their document, and each document
keeps its own zoom, centre and artboard.

The move is the engine command `document.move {from?, to}`, so the control
channel, MCP and recordings reach it, and a replay moves the document before
the steps recorded after it. `sync_views` keeps the views with their documents
by uid when the documents change order.

Behaviour: https://helpx.adobe.com/illustrator/using/customizing-workspace.html

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:02:46 +07:00
NetterSilverandClaude Opus 5.5 1db874610f Keep Pencil and Paintbrush Fidelity in screen pixels at any zoom
Fixes #5729

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 11:52:34 +07:00
dotCooCoo 21680cb53a Docs: Additional Plug-ins Folder's Choose… cites the preferences help
The progress line for the folder preferences now cites the preferences
help for Plug-ins & Scratch Disks as the place where additional plug-ins
folders are set, and names the plug-ins Choose… button and the check of
a typed folder on OK as VectorCraft's own rules.
2026-11-05 11:17:09 +07:00
dotCooCoo 045030f444 Preferences: Additional Plug-ins Folder gets Choose…, and OK checks typed folders
Additional Plug-ins Folder (Plug-ins & Scratch Disks) gets the Choose…
button that Data Recovery's Folder has, on hosts with a folder picker.
A picked plug-ins folder must be a folder whose contents can be listed;
otherwise the status bar says why and the field keeps its value.

OK now gives a folder typed into either field the same check as a
picked one. When it fails, the dialog stays open, the status bar says
why and no preference is set. An empty field and the folder already
saved are not checked. Under automation roots, a folder outside them is
refused before it is looked at.

Additional Fonts Folder and Templates Folder stay typed only.
2026-11-05 11:17:09 +07:00
ruchirguitarandClaude Opus 5.5 528faa8e0a Fix #5532: Release Type on a Path splits type on a path into point type and its path
A Type Tools add-on command (Object › Plug-ins › Type Tools › Release Type on a
Path…, `addon.type.releasePath`): each selected type on a path becomes point
type with the same runs, character and paragraph attributes, on one straight
line whose baseline starts where its first character sat, and the plain path
it was set on, with its exact shape and transform, in its layer and stacking
place, with no fill or stroke. `mode` keeps both, the path only or the text
only; `keepOriginal` leaves the type on a path and adds the released objects
above it. Other objects are left alone, nothing to release makes no undo step,
the run is one undo step and the released objects are selected. The command
dialog previews it as Type on a Path Options does (#5360): Cancel leaves no
history, OK is one step. Tests, command_sweep cases, catalog rows in all 26
languages, docs/mcp.md and the parity docs (beyond Illustrator: scores
unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 11:17:09 +07:00
Don Omondi 62c6d42dff Set Corners: round only the corners in an angle range (#5545)
Squashed from #5737 onto main.
2026-11-05 11:04:52 +07:00
Don Omondi 077ef54d70 Gradient panel: Delete Stop sits beside the ramp, so a short panel no longer hides it (#5403)
The Delete Stop button sat at the end of the Opacity/Location row, below
the selected stop's Color row. In the default docked panel height that
row is cut off as soon as a stop is selected, so the button looked
missing. It now sits at the right end of the ramp, level with it, and
shows whenever the ramp does (with options hidden too). It deletes the
selected stop, never below two, as before.

Adds a headless panel test for deleting a stop by dragging it off the
ramp and with the button beside it, one undo step each, and for a
two-stop gradient keeping both.
2026-11-05 11:04:41 +07:00
dotCooCoo 8a5ec6a25f Tests: the task bar follows an edit and Undo
The task bar drops Join once the selected open path is closed and offers it
again after Undo, and Shape Properties shows a rectangle's corner radius as
it is after object.setLiveShape and after Undo. The progress line names the
new test.
2026-11-05 10:54:45 +07:00
dotCooCoo c236129702 Docs: name the task bar test in the Control bar and task bar line 2026-11-05 10:54:45 +07:00
dotCooCoo 3f12cfdbbf Control bar: idle frames read the selected object without copying it
The Control bar and the contextual task bar copied the first selected
object every frame (the task bar twice), which on a path of 200,000
anchors costs about 1.4 ms per copy. Both bars now borrow it; the task
bar copies it only when it is a basic shape, for Shape Properties.

With one selected open path of 200,000 anchors, an idle frame with both
bars shown takes 3.8 ms instead of 8.0 ms in a release build.
2026-11-05 10:54:45 +07:00
Don Omondi 7aa759d4de Set Corners: round all, odd-numbered or even-numbered anchors (#5546)
addon.pathtools.cornerSet takes `every` ("all", "odd" or "even"; default
"all"), which keeps, of the anchors already chosen (named, direct-selected
or all), only the odd- or even-numbered ones, counted along each path from
its first anchor as number 1. Anchors that take no corner still count, so
the pattern follows the path's anchor order on open and closed paths; when
nothing is left the command says so. Unknown values are refused.

Object › Plug-ins › Path Precision Tools › Set Corners… (also a button in
the Anchor Inspector's Corners section) opens a dialog with Radius, Corner
kind and Anchors (All / Odd-Numbered / Even-Numbered) that previews on the
canvas and commits as one undo step.
2026-11-05 10:42:37 +07:00
dotCooCoo 1c0fcd707b Docs: stroke outline reuse timings measured against current main 2026-11-05 10:26:08 +07:00
dotCooCoo e6331e559a Render: a moved path reuses its stroke outline
A path object that moves without changing shape drew its stroke by
outlining the whole line again, because the stroke outline cache is
keyed by the node. The renderer now keeps, per object, stroke and
outline width, the latest outline with the line it was made from. When
the new line is that line moved, it draws the kept outline moved by the
same offset, and moves the region a long path was outlined in with it.

Scaling, rotating or editing the path, changing the stroke's width,
join, cap, dashes or profile, a zoom that changes the outline's
tolerance, and a move that takes the view past the outlined region
still outline the stroke again. Only the latest outline of each stroke
keeps its line.
2026-11-05 10:26:08 +07:00
Don Omondi 26c27b8b85 Artboards: Shift- and Ctrl/Cmd-click in the Artboards panel select several artboards with any tool, and Fill applies to all of them (#5698) 2026-11-05 10:26:07 +07:00
dotCooCoo 5baf83f1de Layers: counting a row's art for its first thumbnail stops after 20,000 steps
A row drawn before was counted with no limit on its objects, so symbols
that each hold many instances of the next made the count grow with the
product of the instances, and the Layers panel could stall on such a file.

- Counting stops after 20,000 steps: each object, subpath, text run and
  symbol looked at is one step, and every 64 bytes of a symbol or placed
  file name looked up is one more. A row that needs more gets its
  thumbnail on the thumbnail thread, and the painted thumbnail draws
  nothing for it.
- A row drawn before counts at most 20,000 objects.
2026-11-05 10:26:07 +07:00
dotCooCoo 0f9839f7f6 Layers: heavy type, symbols, meshes and images get their first thumbnail off the UI thread
The Layers panel decided whether to make a row's first thumbnail on the
UI thread by counting path anchors and type frames only, so point type,
symbol instances, gradient meshes, images and placed documents counted
as one object with no anchors, and a heavy one was rendered on the UI
thread.

- small_node takes the row's document. Each glyph of type counts as 4
  anchors, each mesh point as 1 and every 100 image pixels as 1, against
  the same 2,000-anchor limit; glyphs are counted only up to the limit.
- A symbol instance and an inline graphic count their symbol's art, and
  a placed document counts its file's art once the file has been read;
  one not read yet goes to the thumbnail thread.
- Symbols and placed documents nested more than 8 deep go to the
  thumbnail thread, so a symbol holding an instance of itself is counted
  to an end.
- The painted fallback thumbnail counts as before.
2026-11-05 10:26:07 +07:00