334 Commits
Author SHA1 Message Date
dotCooCoo 0c272519fb .ai import: symbols and their instances open live
The reader measured a symbol's definition only to find the page art an
instance covered, so every file with a placed symbol instance opened
with that instance as drawn art, or as its page.

- Each `%AI14_BeginSymbol` definition is read by a reader of its own into
  a symbol of the document, with its art (in the symbol's space, y down),
  the registration point its dictionary gives
  (`AI13PatternRegistrationType`, 1 to 9 from the top left) and the
  instances of other symbols in its art. A definition whose art isn't
  read whole isn't kept: type, placed files, non-native art, pattern
  fills, a gradient or an image the reader can't read or place, and
  anything else the reader doesn't read. Its instances come in as the
  page draws them, within the box of the art that is read. A definition
  that holds an instance of a symbol the file doesn't have isn't kept
  either, nor is a definition holding an instance of one of those, and
  their instances aren't read. A definition after the first layer isn't
  read.
- `%AI24_BeginSymbolList` orders the symbols as the Symbols panel lists
  them. A name read twice is numbered ("Dot 2"), and instances name the
  first symbol with the file's name.
- `/SymbolInstance` with `/SymbolRef` and `/RTransformMatrix` comes in as
  a live instance, with the hide and lock set before it and the opacity,
  blending mode, isolation and knockout of the `Xy` after it, as a
  group's. An instance with appearance changes of its own
  (`AI19SymbolAppearanceMap`), of a symbol that isn't read, or placed
  only by `/Matrix` or `/Transform`, comes in as the page draws it.
- Symbol art takes the gradients and art styles read before it, and an
  object in it linked to a listed graphic style keeps the link.
- In a symbol's art, the matrices of instances, gradients and images are
  read in the canvas's space, y up, about the canvas point of the ruler
  origin. Without `%AI3_TemplateBox` an image can't be placed, so the
  symbol isn't kept.
- At most 4,096 definitions are read, and a symbol list names at most
  16,384 symbols.

Tests: reader tests for each case above, and fuzz cases for damaged and
hostile symbol data.
2026-11-06 01:32:42 +07:00
dotCooCoo ddb64fceb6 .ai import: objects other than type keep some Distort & Transform effects live
An art style with a Distort & Transform effect opened every object but
type as its drawn look. Objects other than type now open as themselves
with these effects live:

- Any object takes a Tweak that moves nothing and Pucker & Bloat at 0,
  beside the effects it already took.
- A path, compound path or group takes Transform without copies, or a
  Free Distort whose corners make a parallelogram, when it is the only
  effect. Neither the style nor anything in the object may paint with a
  gradient or pattern. Everything in the object must be a path, compound
  path or group without a clip, an opacity mask or effects of its own.
  When Transform scales, rotates or reflects, nothing in the object may
  be stroked.
- An unstroked path or compound path whose segments are all straight
  takes Twist when it is the only effect.

Other objects keep the drawn look, as do Zig Zag, Pucker & Bloat of
another amount, a Free Distort with other corners and Transform copies.

Docs: docs/file-format-parity.md, docs/target-app-parity.md.
2026-11-06 01:20:04 +07:00
dotCooCoo 7d004de195 .ai import: graphic style libraries open with their styles
A .ai or EPS graphic style library holds graphic styles and their list
in its editing data but no layers, so it came in as its PDF part or
page with the default styles, and Other Library listed those. A file
read as its PDF part or page, because it has no layers or its layers
can't be used, now gets the styles of its list, in list order, as a
file read from its layers does: File > Open shows them in the Graphic
Styles panel and Other Library lists them in a library panel.

Style stroke weights and effects are scaled by the Large Canvas factor
of an EPS, and for a .ai whose editing data is in the units of its
pages by the pages' UserUnit or the declared Large Canvas scale.

Part of #3349
2026-11-06 00:55:18 +07:00
dotCooCoo f092b0b7a7 .ai import: legacy type keeps its tab stops
A tab definition in type of the legacy format (`numDots numTabs Xb`, a
`leader decimal tabType tabPosition Xe` for each stop, then `XB`) was
skipped, so every paragraph opened with only the default stops every
36 pt. The stops it describes now become the paragraph's tab stops
(left, center, right or decimal, at their positions), and they last
into the next paragraphs and text objects like the other paragraph
attributes. A definition with no stops, or whose descriptions were all
skipped, leaves the default ones. A description with an unknown type or
a position outside 0 to 100000 pt is skipped, and so is one past the
first 1024 of a definition. A text object's `To` ends a definition that
an earlier object left open.

Docs: docs/file-format-parity.md.
2026-11-05 22:26:49 +07:00
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 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
el-j 02a8928f8e M2.1: copy and cut direct-selected segments and anchors as independent paths (#2122)
Squashed from #3903 onto main.
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
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
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 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
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
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
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 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
ruchirguitarandClaude Opus 5.5 7e016a0bcc Select by Artboards: select objects overlapping, inside or outside several artboards (#5525)
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>
2026-11-04 22:57:57 +07:00
dotCooCoo 0990bfb244 .ai save: the editing copy keeps transparency, clipping and opacity masks
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.
2026-11-04 22:29:43 +07:00
dotCooCoo a7acf43cb1 .ai import: objects come in bound to their variables
Squashed from #5481 onto main.
2026-11-04 16:33:25 +07:00
dotCooCoo 322f1c24db .ai import: brushes come in from the editing data
Squashed from #5046 onto main.
2026-11-04 15:41:24 +07:00
dotCooCoo a8c6d078e5 .ai import: blends keep their Specified Distance and Smooth Color spacing
A blend's record in the editing data holds its Blend Options spacing.
Word 1 is the spacing: 0 is Smooth Color, 1 is Specified Steps and 2 is
Specified Distance. For Specified Distance, word 2 is the distance in
points as a 32-bit float. The reader took only records whose word 1 is
1, so every Smooth Color and Specified Distance blend came in as its
drawn steps.

A Smooth Color or Specified Distance blend now opens live with that
spacing where VectorCraft lays out as many steps between each pair of
keys as the file draws, in the same places and colors. Otherwise it
keeps its drawn steps with the existing note. The distance is read as
the shortest decimal that reads back as the stored float. The steps are
counted before anything is laid out, and a blend takes from the
import's budget only when its count matches. Specified Steps blends
are read as before, with the steps counted from the drawn ones.

Tests: import::ai::tests::a_blend_keeps_its_spacing,
a_blend_of_three_keys_keeps_its_distance_between_each_pair,
a_blend_whose_spacing_doesnt_lay_out_its_steps_keeps_them,
import::ai::blend::tests::records_say_their_spacing,
a_blend_takes_from_the_budget_only_when_it_is_laid_out,
a_distance_past_the_keys_lays_out_no_steps, and the fuzz case
ai_blend_spacing_never_panics.
2026-11-04 15:41:08 +07:00
bflatastic 96330fb54e SVG import: a style sheet too large to match opens with a warning; fuzz reaches the pass 2026-11-03 21:12:41 +07:00
ruchirguitarandClaude Opus 5.5 8850b923ce Fix #4924: Text Tweaker ramps or randomises one character attribute letter by letter
A built-in Type Tools add-on (Object › Plug-ins › Type Tools › Text Tweaker…,
`addon.type.textTweaker`): size, baseline shift, rotation, horizontal or vertical
scale, tracking, leading or stroke weight go from a start value to an end value
along a straight ramp, an ease, a bow, a spike or at random with a seed, set as
the Character panel sets them, while the text stays live. One undo step, a
dialog with Preview, catalog rows in all 26 languages, docs/mcp.md and the
parity docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 20:07:58 +07:00
dotCooCoo a870da6c7d .ai import: Effect › Pathfinder on a group opens as a live effect
Squashed from #5150 onto main.
2026-11-03 16:38:47 +07:00
dotCooCoo f62e502f1c .ai import: version 7 pattern definitions come in as pattern swatches
Squashed from #5189 onto main.
2026-11-03 16:38:42 +07:00
dotCooCoo b664216a2a .ai import: type keeps its Distort & Transform effects live
Squashed from #5139 onto main.
2026-11-02 16:19:59 +07:00
dotCooCoo 8ab68e4223 .ai import: open the variable library and data sets from the editing data
Squashed from #5156 onto main.
2026-11-02 16:19:19 +07:00
dotCooCoo 952446253f File formats: Pixar (.pxr) images open and place
Squashed from #5231 onto main.
2026-11-02 16:18:48 +07:00
FarmerGeorg 9c59a4dcb4 Appearance panel: UI parity for row editing
- Drag strokes, fills, characters and effects to move them, or drop them
  on the Duplicate or Delete button
- Select several rows with Shift/Cmd-click and move them as one block
- Delete and Backspace remove the highlighted rows, not the selected art
- Row links open their panel (Stroke, Character…) as a flyout
- Stroke weight spinner follows Units > Stroke
- Transparency panel edits the active fill's or stroke's opacity
2026-11-02 14:36:09 +07:00
dotCooCoo 2fafe25c27 Asset Export: Import Preset and Export Preset read and write preset files
Squashed from #5032 onto main.
2026-11-02 14:33:29 +07:00
dotCooCoo e8e09af782 Brushes: Calligraphic and Scatter values follow pen tilt, bearing and rotation
Squashed from #5031 onto main.
2026-11-02 14:33:02 +07:00
dotCooCoo f69f7976b7 .ai import: legacy files without layers open through the format reader
Squashed from #5209 onto main.
2026-11-02 14:32:58 +07:00
bflatastic 8913ba2440 Tests: the placed-files fuzz fixture goes in after the noted path 2026-11-02 14:32:57 +07:00
dotCooCoo 01bc69cc80 .ai import: placed EPS files in the version 7 form are read
Squashed from #5187 onto main.
2026-11-02 14:32:56 +07:00
dotCooCoo 405372555d .ai import: the document's graphic styles open in the panel with their objects linked
Squashed from #5155 onto main.
2026-11-02 14:32:53 +07:00
dotCooCoo 0e38796573 .ai import: placed files are found by the folder they keep relative to the document
Squashed from #5176 onto main.
2026-11-02 14:32:53 +07:00
dotCooCoo 56f6128f94 .ai import: a line width of 0 draws as a hairline
Squashed from #5174 onto main.
2026-11-02 14:32:53 +07:00
dotCooCoo dbb04905e0 .ai import: Offset Path, Outline Object and Outline Stroke open live
Squashed from #5148 onto main.
2026-11-02 14:32:52 +07:00
dotCooCoo ccc7e44078 .ai import: multi-layer masks open as a clipping group in each layer
Squashed from #5117 onto main.
2026-11-02 12:04:26 +07:00
dotCooCoo 795cbc060d .ai import: graphs open as their drawn art, with a note
A graph in a legacy .ai, or in a current file's editing data, is written
as `%_Gs`, its data and settings on `%_` lines, its drawn art in a group
with a `%_... Go` tag after each object, and `%_GS`. The reader stopped
on the graph operators as ones it doesn't know, so such a file did not
open through its layers.

The reader now leaves the graph's records out with their operands and
reads the drawn art as usual: the bars, lines and labels come in as the
groups, paths and type they are written as. Paint operators on `%_`
lines between `Gt` and `GT` set nothing. One note per file says the
graphs came in as plain art without their data or settings. A graph
that starts inside another, or doesn't end before its layer does, keeps
the art read so far with a second note. Graph operators outside a graph
stay unknown.
2026-11-02 12:04:24 +07:00
dotCooCoo 69e5eecde0 .ai import: artboard display options, raster effects settings and the header's author come in
Squashed from #5102 onto main.
2026-11-02 12:04:24 +07:00
dotCooCoo 13db3cd602 .ai import: anchors are smooth or corners by the case of their segment operators
In editing data and legacy .ai files, a lower-case l, c, v or y makes the
anchor the segment ends on smooth and an upper-case L, C, V or Y makes it a
corner. The reader used to guess each anchor's kind from whether its
handles are in line, so a lower-case l always read as a corner and an
upper-case C with handles in line read as smooth. A closed path's first
anchor takes the case of the segment back to it. Points and handles do not
move. Paths, compound paths, clipping paths, guide paths, area type frames
and type-on-a-path paths all keep the kinds.
2026-11-02 12:04:23 +07:00
dotCooCoo beb89ebec4 .ai import: vertical type keeps its orientation and Tate-chu-yoko runs
Squashed from #5086 onto main.
2026-11-02 12:04:08 +07:00
dotCooCoo f46427167c .ai import: area type keeps its rows, columns, gutters and first baseline
Area type made from the editing data's text document came in with one
row, one column, the 18 pt gutter and the Ascent first baseline,
whatever its frame kept. Each area frame now keeps its rows and columns,
the gutter between rows and the gutter between columns, and its first
baseline (Fixed, Cap Height, Leading or x Height) with its minimum,
scaled with the art's units. Each frame of a thread keeps its own, and a
key that is missing or can't be read leaves that option as it was. A
first baseline the frame keeps is no longer replaced by the fixed one
that matches the file's first line.

AreaOptions gets row_gutter (rowGutter, left out while unset), which
layout spaces rows by; text.areaOptions takes and reports rowGutter, a
gutter given alone sets both, and the Area Type Options dialog shows
Row Gutter.
2026-11-02 12:04:07 +07:00
dotCooCoo d4f5004580 .ai import: area type flows around its text wrap objects
Fixes #2763. An object whose editing-data dictionary holds the text wrap
options keeps them (offset in points, 6 pt by default, clamped to
±1000 pt; Invert Wrap), and the objects after the text in a version 7
wraparound group (`*w` … `*W`) are wrap objects at the default options.
Area type below a wrap object in its layer flows around it before the
layers are compared with the page, and threaded type flows on into its
next frames. A Large Canvas file's offset is scaled with its art.

The wrap resolver moves into `vectorcraft_doc::text::resolve_wrap` for
the import and the edit-time refresh, and builds each wrap object's
outline once per layer. A file whose wrap shapes would copy more than
2,000,000 anchors is not read from its editing data.

Docs: docs/file-format-parity.md.
2026-11-02 12:04:05 +07:00