Commit Graph
275 Commits
Author SHA1 Message Date
Don Omondi 499a78b0bf Merge remote-tracking branch 'upstream/main' into fix/ai-import-brushes
# Conflicts:
#	docs/file-format-parity.md
#	docs/target-app-parity.md
2026-11-08 19:21:57 -05:00
BFlat 7724463e00 Merge pull request #5991 from mockydevs/fix/eps01-editing-overlay
EPS import: gradients drawn as abutting strips come in as one gradient, without seams
2026-11-09 07:57:02 +08:00
BFlat 22f5ede66f Merge pull request #6161 from dotCooCoo/feat/ai-editing-data-s6b2
.ai save: the editing copy keeps graphic styles and links to them
2026-11-09 07:52:12 +08:00
Don Omondi abeb7e3167 Merge remote-tracking branch 'upstream/main' into fix/ai-import-brushes
# Conflicts:
#	docs/file-format-parity.md
#	docs/target-app-parity.md
2026-11-08 18:18:16 -05:00
dotCooCoo 230fbb96a3 Merge main into feat/ai-editing-data-s6b2 2026-11-08 15:00:10 -08:00
dotCooCoo b8ab981058 Merge main into feat/ai-symbol-libraries 2026-11-08 14:56:51 -08:00
Don Omondi 8383b2af6d Merge remote-tracking branch 'upstream/main' into fix/eps01-editing-overlay
# Conflicts:
#	docs/file-format-parity.md
#	docs/gaps.md
2026-11-08 17:39:34 -05:00
dotCooCoo c4de0035b2 .ai import: the editing-copy paragraph in the file-format doc appears once again
docs/file-format-parity.md held 35 copies of the paragraph under
"Illustrator's private editing data" after #6202 left one, each copy
carrying a different set of the changes merged since. The section now
holds one copy with every clause from the others: placed PDF and .ai
pages as placed documents, Bas Relief among the single-effect art
styles, Bas Relief's settings block in type's art style, the fill a
clipping path keeps with its exceptions, paths with an Art brush, the
spot colors in brush art, symbols defined before the art styles
section, the Type on a Path options, and the artboards the type of a
PDF's later pages goes to.
2026-11-09 03:14:41 +07:00
dotCooCoo 11f1d228de Type: Align to Path moves each character by its own font's metrics
Align to Path moved the whole line by the largest ascent and descent
among its characters. Each character now moves by its own font, size
and vertical scale. Ascender runs the top of the font's em box along
the path: the ideographic em box that the font's BASE table gives, or
in a Chinese, Japanese or Korean font the one that its typographic or
hhea box, a one-em box-drawing cross or its ideographs give; else an
em box centered on the cap height. Descender runs the bottom of the font's
bounding box along the path, and Center runs a line at half the height
of the top of the font's H along it. In vertical type, Ascender and
Descender move the characters half an em, and Center leaves them on
the path.

Existing documents with Align to Path set to Ascender, Descender or
Center lay their type out at a new distance from the path.
2026-11-09 03:14:41 +07:00
Don Omondi 3bb43b2fe6 EPS import: composite fonts draw their Type 42 descendants' glyphs
A composite (Type 0) font with the 8/8 mapping picks a descendant font
and a code in it with each pair of bytes (PLRM 3rd ed., 5.10). The reader
set its strings as type in a font of the composite font's name, the
bytes read as Latin-1 and at a thousand times the size, so EPS from
pdftops of a PDF with embedded TrueType fonts showed nothing, or one
black shape, where its type was.

Characters now go to their descendants: a Type 42 font's glyphs are drawn
from the TrueType outlines its sfnts strings carry (Adobe Technical Note
5012), by the glyph index CharStrings gives their names, spaced by their
own widths or the show operator's numbers; charpath and stringwidth read
them too. A Type 3 descendant runs its procedures, and a descendant found
by name sets its character as type at the composite font's size.
2026-11-09 02:24:54 +07:00
M Balazs PC 500ff6ed4c .ai import: opacity mask art without its own paint takes the paint before its dictionary
A dictionary's state starts afresh, with a black fill. Mask art is written
going on from the paint before the mask's dictionary, which the writer leaves
out when it doesn't change, so a mask rectangle with no colour of its own
came in black and hid the white letters it masks. Mask art now starts from
the paint (fill, stroke, line style, fill rule, overprint) before the
dictionaries it is in; lock, visibility and transparency still start afresh.
2026-11-09 02:03:02 +07:00
Don Omondi d4f6fa0744 EPS/.ai editing data: art-style strokes aligned inside or outside keep their alignment
Squashed from #6010 onto main.
2026-11-09 02:03:01 +07:00
dotCooCoo fb848d28ac .ai import: objects in pattern and brush art take the file's art styles
A pattern's definition (in either form) and a brush's art were read
without the file's art styles, so an object there with a style came in
as the look it draws, with a "drawn look" note, while the same object on
a layer or in a symbol came in as itself with its effects live. The
three definition readers now take the art styles as the symbol reader
does, and an object in a pattern's art with a style in the file's style
list keeps that graphic style's id.

An art styles section inside a symbol's, a pattern's or a brush's art
is now skipped, so it no longer replaces the file's own art styles.
2026-11-09 00:36:09 +07:00
dotCooCoo 7b06cc070b .ai save: the editing copy keeps Standard Vertical Roman Alignment
The editing-data writer wrote a run's key 35 only for Tate-chu-yoko (3).
Type with Standard Vertical Roman Alignment, on or off, was saved with no
key, so the setting was lost from the editing data. Every run now carries
key 35 in the form the reader reads it (crates/eps/src/import/ate.rs,
Texts::style, lines 669-670): 1 when the setting is on, 2 when it is off
and 3 for Tate-chu-yoko. The document's defaults and the normal character
style carry 1.

Key 35 holds one value, so a run with both Tate-chu-yoko and the setting
is written as Tate-chu-yoko, and the save reports the type attributes
warning for it.

- writer/texts.rs: baseline_direction() gives each run's value;
  normal_char() writes 1; loses() and TYPE_ATTRIBUTES name the setting.
- tests.rs: a vertical and a horizontal object with the setting on, off
  and Tate-chu-yoko come back the same through the reader, lay out as
  before, and carry the expected keys; a run with both is reported.
- docs/file-format-parity.md: the .ai save section lists the written
  values.
2026-11-09 00:35:51 +07:00
dotCooCoo a37ad90e6c .ai import: type keeps Standard Vertical Roman Alignment from its editing data
The modern type reader read key 35 of a character style only for
Tate-chu-yoko (3). A run whose key 35 is 1 (Standard), in the run, its
character style or the document's defaults, now opens with Standard
Vertical Roman Alignment on, so its half-width characters stand upright
in vertical type. Horizontal type keeps the mark with no change to its
layout. A value of 2 (rotated), 3, any other value and no key at all
leave it off, VectorCraft's default.

- ate.rs: Style reads the flag and passes it to CharStyle, with a unit
  test over defaults, character styles, run overrides and bad values.
- tests_native.rs: a vertical and a horizontal story with Standard,
  rotated and unset runs; the test of values the reader ignores also
  checks the flag.
- import_fuzz.rs: the vertical fixture also checks the flag.
- docs/file-format-parity.md: one line on the mapping.
2026-11-09 00:35:02 +07:00
dotCooCoo f8977285cc .ai import: the editing-copy paragraph appears once
docs/file-format-parity.md held 17 copies of the paragraph under
"Illustrator's private editing data", and they disagreed about what
import reads. The section now holds one copy with every clause from
the others: a filled clipping path keeps its fill, with the exceptions
for multi-layer masks and compound clipping paths whose subpaths paint
differently; paths with an Art brush; the spot colors in brush art and
their limits; symbols defined before the art styles section; and the
Type on a Path options of type on a path. Each clause was checked
against crates/eps/src/import.
2026-11-09 00:34:29 +07:00
dotCooCoo b568977af4 Asset Export: 3D objects export as USDA, USDZ, glTF or OBJ meshes
Squashed from #6168 onto main.
2026-11-09 00:29:05 +07:00
dotCooCoo a409f34ebe .ai import: placed PDF and .ai pages open as placed documents
Squashed from #6083 onto main.
2026-11-09 00:26:34 +07:00
dotCooCoo f5be571d47 .ai import: Bas Relief opens with its settings
Squashed from #5227 onto main.
2026-11-09 00:25:27 +07:00
dotCooCoo d5e1c00470 File formats: HEIC and HEIF images open and place
File › Open and File › Place read HEIC and HEIF still images (.heic,
.heif) through heic-rs 0.1.4, the pure-Rust decoder photocraft-heif
uses, and store the pixels as PNG. Single pictures and grids of tiles,
8 to 10 bits, with or without an alpha image, are read with the
container's rotation, mirror and crop applied. A grid without a color
box of its own takes its tiles'. When neither the image nor its tiles
have an nclx box, the HEVC stream's video signal type gives the
samples' matrix and range. An RGB ICC profile, or Display P3 with the
sRGB curve named by an nclx box or, without a color box, by the
stream, is converted to sRGB. The EXIF resolution tags size the image;
without them it is 72 ppi. When the container's rotations turn the
image an odd number of quarter turns, the resolution's axes swap with
the pixels'.

Image sequences, images that aren't HEVC-coded, grids of cropped tiles
and images past the pixel allowance are refused with a message before
the pixels are decoded. The allowance counts each coded picture at the
size its sequence parameter sets give it.

The decoder is in the engine's `heif` feature, which is on by default
and listed by the desktop, CLI and web apps. A build without it still
recognizes the files and refuses them with a message.
2026-11-08 23:51:41 +07:00
FredApps 65adb7b211 Merge pull request #2480 from vangel76/fix/2414-svg-classes
SVG: keep CSS classes of imported elements on export (#2414)
2026-11-08 17:01:22 +01:00
Don Omondi d87844bba6 Merge remote-tracking branch 'upstream/main' into fix/eps01-editing-overlay
# Conflicts:
#	docs/gaps.md
2026-11-08 06:36:42 -05:00
Don Omondi 6abe10583a Merge remote-tracking branch 'upstream/main' into fix/ai-import-brushes
# Conflicts:
#	docs/file-format-parity.md
2026-11-08 03:35:43 -05:00
dotCooCoo 272a34bee5 Merge main into feat/ai-editing-data-s6b2 2026-11-07 23:24:31 -08:00
dotCooCoo c85b286994 .ai import: Scatter brushes in Tints open in Tints, and brush art drops its frame
A Scatter brush in a .ai or EPS file's editing data always came into
the Brushes panel keeping its art's colors. A Scatter brush whose last
15 numbers start with 1 now opens in Tints, and one whose numbers start
with 0 keeps its art's colors. Other values are not read and keep the
art's colors. The field before the art's name does not decide it: an
object whose first field is 1 is read by its 15 numbers, and a Scatter
object with 8 numbers is still left out.

A brush's art usually comes in a group after its frame, an unpainted
rectangle around the art. The reader kept that rectangle as part of
the brush's art. It now leaves out a frame at the art's bounds (within
1/1000 pt): an unnamed, unpainted rectangle with its four corners in
order, closed, or open with its first corner again at the end, and no
settings of its own (shown, unlocked, at full opacity in Normal, with
no effects, note or other attributes). A wider frame stays in the art,
and so do a path that crosses the corners, an open path along three
sides and a rectangle with settings of its own. A group left with one
object becomes that object, unless it has a state or look of its own
(hidden, locked, isolated, knockout, an opacity, a blend mode, a name,
a mask, a note or another setting, or an appearance); then it stays a
group without the frame. The rule applies to every brush art section.

Existing files with such brushes now open with Tints and without the
extra frame path in the brush's art. With an exact frame, the pieces
that a Scatter or Art brush draws along a path stay where they were.

The Scatter, Art and Pattern brush colorization options, Tints among
them, are described on pages 281-282 of
https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf#page=281

Tests cover Scatter brushes whose numbers start with 1 and 0, with a
first field of 1 and 2, and an 8-number object. They cover frames kept
or left out (exact, wider, open, filled, named, crossing the corners,
along three sides, with a note, Show Center or hidden), a group's own
state and note kept, and the pieces a Scatter and an Art brush draw
along a path with and without the frame. The import fuzz test reads
brush art after frames of any corners and paint.
2026-11-08 13:23:23 +07:00
dotCooCoo c8701a18cd .ai import: legacy binding punctuation opens as Burasagari's Force setting
The legacy reader skipped `bindrepeat Tg` (section 15.3.4 of the version 7
file format specification). It now reads the binding punctuation bit:
1 and 3 set the paragraph's Burasagari to Force, and 0 and 2 set it to
none, which is VectorCraft's own mapping. The value is modal, lasts into
the next text object, and an operand other than 0 to 3 leaves it as it
was. The repeat character, `TG`, `Xu` and `Tu` are still skipped.

docs/mcp.md and the `ParaStyle::burasagari` doc comment name the legacy
`.ai` case as the exception to imported text keeping `none`.
2026-11-08 13:21:08 +07:00
vangel76andClaude Opus 5.5 5f4982f66a Merge upstream/main into fix/2414-svg-classes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 07:18:04 +01:00
dotCooCoo 78f95c3ae9 Merge main into feat/ai-symbol-libraries 2026-11-07 22:03:00 -08:00
dotCooCoo 79dcf3b1ff .ai save: the editing copy keeps live effects
Squashed from #6150 onto main.
2026-11-08 12:59:34 +07:00
dotCooCoo 7036d44603 .ai import: warped type bound to a text variable stays type
Warped type that a text variable binds stays type with the effect live,
so it keeps its binding. Before, a failed comparison replaced it with
its drawn art and the binding was skipped.

When drawn art replaces warped type with a wrap setting, the wrap shapes
are checked against the file's limit on wrap work again, so a file whose
drawn art would go past it opens from its page, as one with too many
text objects does.
2026-11-08 12:59:33 +07:00
dotCooCoo d54d4657b9 .ai import: warped type that draws differently from the file opens as its drawn art
A Warp on type, on the object or on one of its fills or strokes, opened
as a live effect with no check. Where the live warp draws a different
shape from the art saved in the file, the type moved and changed size.

Warped type is now compared with its saved art once it is laid out,
with the comparison and the per-file budget that envelopes use. Type
that draws as its saved art stays editable with the effect live. Other
warped type opens as that art, not as editable type, with one note that
counts them, and saving over the file asks first. The art's box goes
back into the comparison with the page. Type stays live without a
comparison when its saved art paints nothing or draws type of its own,
and in frames of threaded type.
2026-11-08 12:59:32 +07:00
dotCooCoo 81a7a68388 .ai import: legacy type in CJK fonts decodes by the font's CMap
Legacy type in a composite font such as HeiseiMin-W3-83pv-RKSJ-H was
decoded one byte to a character, so Japanese, Chinese and Korean type
opened as unrelated single-byte characters, and the font name kept the
CMap.

Tf now finds a predefined CMap at the end of the font name, unless a TZ
gives the font encoding pairs, and the font is the name without it.
Strings in that font are read by the CMap's code space, mapped to CIDs
with the predefined CMaps craft-pdf carries, then to Unicode by the
character collection's CID-to-Unicode CMap. Codes the CMap doesn't map
are dropped. A -V CMap is read with its -H form's codes. Codes in the
Unicode CMaps (UCS2, UTF8, UTF16, UTF32) are read as Unicode; those
helpers follow mockydevs' #6067. A 0x0D byte ends a paragraph and a 0x09
byte is a tab in these fonts too.

The predefined CMaps craft-pdf doesn't carry (RKSJ, 78-RKSJ, 78ms-RKSJ,
78-EUC, 78, Add, Ext, NWP, GB, CNS1, KSC, B5 and the Hong Kong B5 sets
other than HKscs-B5) are read with the nearest
carried CMap, which can give a different character for some codes.
GBT, GBT-EUC, GBTpc-EUC, CNS2 and KSC-Johab type still decodes byte by
byte, with the CMap removed from the font name.

Docs: docs/file-format-parity.md.
2026-11-08 12:47:52 +07:00
dotCooCoo 0f39b54a05 .ai import: type on a later page of a PDF of some artboards goes to its artboard
A PDF saved with some of its artboards has a page for each of them, while
the editing data keeps them all. The importer took the pages after the
first to show the artboards right after the first page's artboard. In a
PDF of artboards 2 and 4 of 4, the second page's type went to artboard 3.
In a PDF of artboards 1 and 3 of 4, the second page's type went to the
left edge of artboard 2.

Each page after the first now goes to an artboard of its size that comes
after the previous page's artboard and leaves an artboard for each later
page. The artboard chosen for the first page also leaves one for each
later page. When several artboards fit a later page, the importer
compares their art with the page and takes the closest one, or the first
of them on a tie. It compares at most 16 artboards for all the later
pages together, the same limit it uses for the first page. A later page
with no comparisons left takes the first artboard that fits it.

When the PDF has fewer pages than artboards and its first page shows the
first artboard, the first page's type and page art still go to the art's
box or to the editing data's artboard.
2026-11-08 12:21:54 +07:00
vangel76andClaude Opus 5.5 6945bc1877 Merge upstream/main into fix branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 06:18:04 +01:00
dotCooCoo db137418c0 Merge main into feat/ai-editing-data-s6b2 2026-11-07 20:55:44 -08:00
dotCooCoo bbf4834f12 .ai import: a hidden clipping path's fill leaves with it
The fill read apart from a clipping path that was read on %_ lines, and
the copies of its subpaths, are recorded as read on those lines too, so
the copy of a look's art that is removed takes them along. The file
format notes list a compound clipping path whose subpaths paint
differently among the exceptions to a clipping path keeping its fill.
2026-11-08 11:22:37 +07:00
dotCooCoo 72959e6e80 .ai import: a multi-layer mask's fill paints once
Each layer a multi-layer mask covers gets a copy of the mask's path, so
a fill kept on that path painted in every layer, over the art the mask
covers in the layers before. The mask's fill is read as a path of its
own again, where the mask's path is, and the copies have no fill. A
fill read apart from a compound clipping path is a compound path whose
subpaths do not clip and have ids of their own. A fill read apart, its
subpaths and their points count toward the importer's object limit.

Docs: docs/file-format-parity.md.
2026-11-08 11:22:36 +07:00
dotCooCoo 6ab82ca71e .ai import: a filled clipping path keeps its fill
The reader took the fill off a clipping path in a file's editing data and
added a copy of the path, holding only that fill, under the art it clips.
A clipping path paints its own fill under the clipped art and its stroke
over it, so the reader now keeps the fill on the clipping path. A filled
clipping path opens as one path instead of two, in a clipping group, a
clipping layer and each level of nested clipping paths. Its note, tags and
gradient stroke (`1 BB`) are on that path as before.

A compound clipping path whose subpaths all paint alike keeps their fill.
When its subpaths paint differently, the fills of the filled subpaths are
still a compound path of their own under the clipped art, so a subpath
the file does not fill stays unfilled. Clipping paths in a compound path
that holds its drawn look still have their fills read as paths of their
own under the art, each with its clipping path's opacity mask.

Docs: docs/file-format-parity.md.
2026-11-08 11:22:36 +07:00
dotCooCoo fe0f3d919c .ai save: the editing copy keeps variables, data sets and bindings
Squashed from #6024 onto main.
2026-11-08 11:22:34 +07:00
dotCooCoo 57dbbbb41b Docs: .ai brush art spot color limits
The file-format entry states the 20,000-name limit across brush art and per section, the first-definition rule, and that colors past the limit still count toward the color mode.
2026-11-08 11:22:33 +07:00
dotCooCoo 8a34c89d7c .ai import: remove the duplicated editing-copy paragraph 2026-11-08 11:22:33 +07:00
dotCooCoo cbfca36c85 .ai import: spot colors in a kept brush's art become swatches
The reader kept a brush's art but dropped the colors it read from it, so
a spot color that only a Scatter or Art brush's art used made no swatch,
and the art's process colors did not count toward the document's color
mode.

The reader now keeps each brush art section's colors with its art. When
the document is finished, the colors of the art that a kept brush uses
are added after the rest of the file's, in the order the art was read:
its spot colors make swatches and its colors count toward the color
mode. Art that no kept brush uses (a pattern brush's tiles, a section
that a later one of the same name replaced, a section inside a symbol's
definition) brings none of its colors, and a spot color that the page's
art, a pattern or a symbol also defines keeps the value defined there.

Part of #637.
2026-11-08 11:22:32 +07:00
dotCooCoo 2faec2fa11 .ai import: Skew, Stair Step and Gravity measure the path's turn square to it
The spacing fitted from a type-on-a-path layout measured each turn against
the glyph's own foot. Skew's feet stay upright, so on a tilted or leftward
path the turn picked up a false angle and the fitted spacing was wrong (a
straight path at 30 degrees gave -9.7 instead of 0). Effects other than
Rainbow and 3D Ribbon now measure the turn against the baseline turned a
right angle.

The file-format doc states that a frame whose options set key 6 to true
keeps the default effect, alignment and Flip.
2026-11-08 11:22:31 +07:00
dotCooCoo bc5eb7aa47 Docs: state the limits of .ai type on a path import
Align to Path takes its distance from the path from VectorCraft's font metrics, and the fit under Gravity does not reproduce the file's layout.
2026-11-08 11:22:31 +07:00
dotCooCoo 53f6aafe87 .ai import: type on a path keeps its effect, alignment and flip
Type on a path from a file's text document opened as Rainbow on its
baseline, not flipped, whatever Type on a Path Options the frame kept.
Each frame now keeps its effect (Rainbow, Skew, 3D Ribbon, Stair Step,
Gravity), Align to Path (Ascender, Descender, Center, Baseline) and Flip.
A code or flag that can't be read leaves that option at its default.

Flipped type opens with the frame's path reversed once, after a copied
object takes its own art path, and an open path's end bracket stays so
the type starts where the file's range ends.

Spacing is still fitted from the file's layout. The fit now measures along
the path when Align to Path moves the baselines off it, and it follows the
glyphs' feet under 3D Ribbon, whose baselines stay level.
2026-11-08 11:22:30 +07:00
dotCooCoo b8233f7c19 .ai save: a reopened file opens on the artboard active when it was saved
Squashed from #5975 onto main.
2026-11-08 11:22:28 +07:00
dotCooCoo 9c0e7ccb80 .ai save: the editing copy keeps graphic styles and links to them
The editing copy of a PDF-compatible .ai left out the Graphic Styles
panel's styles and objects' links to them, with a warning. They are now
written as art styles:

- The panel's styles come first in %AI9_BeginArtStyles and are listed in
  its order in %AI9_BeginArtStyleList after the palette, with their
  fills, strokes, transparency and effects. Fills and strokes may have
  transparency and effects of their own, and a style may also hold Warp,
  Transform, Free Distort, Pucker & Bloat, Twist, Zig Zag or Gaussian
  Blur. A style of an object's own holds the same effects as before.
- A path, compound path or group linked to a graphic style names it when
  it has the style's appearance, the style is written, and the reader
  reads that style on that object. Objects linked
  to a new document's styles keep their link: the panel is written for
  them too.
- The writer reads each style back with the reader before it writes
  it. A style with gradient or pattern fills, with anything the
  reader does not read back, with a name another style has, with a name
  over 1,024 bytes or past 4,096 styles is left out with a warning. Other
  links are left out with a warning, and a panel with no style written
  warns that it opens with a new document's styles. No graphic style is
  written on a large canvas, with a warning.

Graphic styles:
https://helpx.adobe.com/illustrator/desktop/special-effects-styles/apply-graphic-styles/apply-graphic-styles.html

Tests: the round trip of the panel's styles and of links; each effect a
style can hold; the forms written; the list's limits; duplicate names; an
empty panel; links left out; a style's opacity at the written precision;
a large canvas; Twist and Transform on an object with no link; a link
to a style whose Transform moves the outline, with and without an
opacity mask; the
reader's reading of warp numbers; fuzz cases for damaged written styles.

Docs: docs/file-format-parity.md.
2026-11-07 12:02:18 -08:00
dotCooCoo 1f6be41d8a Docs: the .ai editing copy keeps live effects on simple objects only
The fallback for objects that can't use an art style keeps their vector
art with geometry effects applied and leaves out their other effects,
such as Drop Shadow, the glows and Feather. README, ROADMAP, gaps,
roadmap and target-app-parity no longer say every live effect is left
out, and target-app-parity lists the round-trip tests.
2026-11-07 10:59:50 -08:00
dotCooCoo 6c285d3042 .ai save: the editing copy keeps live effects
The editing copy of a PDF-compatible .ai wrote an object's live effects
as their result (geometry effects) or left them out (the others).
Objects with effects the .ai reader reads are now written as art styles:

- An object is written as its drawn look (a group of its vector art as
  its geometry effects leave it, without shadows, glows or feathers,
  with the object's transparency after it), then the object itself on
  %_ lines (its points painted with nothing, or with its gradient fill
  placed on the outline it draws; a group with its members), its name
  and opacity mask, and
  1 (style) XW naming its style.
- Styles are written in a non-printing block after the symbols, in
  %AI9_BeginArtStyles: a chain of the stack of fills and strokes (with
  the contents where the appearance puts them), the effects and the
  object's transparency. Objects of the same appearance share a style.
- The effects written are the ones the reader reads on objects, with
  their settings in the units it reads them in: Drop Shadow, Inner Glow,
  Outer Glow, Feather, Round Corners, Scribble, Convert to Shape, Offset
  Path, Outline Object, Outline Stroke and Pathfinder > Add.
- Each style is read back before it is written, by a new reader
  function, read_art_style, which runs the reader's style parser and its
  reading of a graphic style with the object's own gradient. An object
  whose style reads back differently (several fills or strokes, fills
  or strokes with transparency or effects of their own, pattern fills,
  other effects), a filled object that fills by the even-odd rule (a
  style's fill fills by the non-zero rule), a group whose effects change
  its members' outlines while a member is written on another outline
  (strokes as filled outlines, pattern fills as tiles, gradients that
  fit their object), or one in a container whose own
  effects change its members, is written as its drawn look with the
  warning, and a style that is not written leaves no gradient
  definition behind. Graphic styles and links to them are left out with
  a warning. No art style is written on a large canvas.

Appearance attributes:
https://helpx.adobe.com/illustrator/using/appearance-attributes.html

Tests: the round trip of objects with each effect, a gradient fill, a
group with transparency and a mask, Pathfinder > Add and a hidden
effect; the same bytes on a second write; the forms written; the
objects the reader would not read; a large canvas; the warning for
graphic styles; filled even-odd objects; a gradient placed on the
outline Offset Path draws; a style that is not written; effects in a
pattern a drawn look uses; the reader's check of a style; fuzz cases
for damaged written styles.

Docs: docs/file-format-parity.md.
2026-11-07 10:17:51 -08:00
dotCooCoo f8e0639d14 .ai save: the editing copy keeps symbols and their instances
The editing copy of a PDF-compatible .ai wrote every symbol instance as
its art. Symbols are now written as symbols, in the form the .ai reader
reads them:

- Every symbol is defined in the setup, in a non-printing block after the
  patterns: %AI14_BeginSymbol, its name, its art in the symbol's own
  space, its registration point as AI13PatternRegistrationType and
  %AI10_EndSymbol, each after the symbols its art uses. The nine points
  are numbered 1 to 9 row by row from the top left, in the order the
  scripting reference lists them. A definition is written the same
  wherever its first instance lies. The symbol list after the palette
  keeps the Symbols panel's order. A hidden root of a symbol's art is
  written shown, as the canvas draws it.
- Each instance is a /SymbolInstance with its symbol's name and the matrix
  that places the art (to ten decimals), followed by its name, opacity
  mask, opacity and blending mode as a group's. In a symbol's art, the
  matrices of instances, gradients and images are written in the
  canvas's space about the canvas point of the ruler origin.
- Pattern fills in a symbol's art are written as their tiles cut to the
  shape. A symbol whose definition holds such tiles, from its own
  objects, a brushed stroke's brush art, a live object, an opacity mask,
  a nested symbol or an inline graphic, has its instances written as
  their art.
- Instances with a fill, appearance changes, isolation, knockout or
  knockout shape of their own, instances of symbols with 9-slice guides
  or pattern paint, instances in pattern tiles or in art cut to a
  clipping object, instances scaled almost flat, instances that turn,
  mirror or scale symbols holding type, live objects, instances written
  as their art, shapes with rounded live corners or strokes written as
  outlines or brush art (the canvas works these out again where the
  instance puts them), instances that scale their symbol more than 100
  times, nested instances included (numbers are written to four
  decimals), and instances on the layers of a large canvas are written
  as their art (the symbols are still defined). Symbols past the 4,096
  the reader keeps are left out and their instances written as art; an
  instance of a symbol the document doesn't have is an empty group.
  9-slice guides, export types and the dynamic type are left out, and
  linked images and type in a symbol's art are embedded and written as
  outlines. Each comes with a warning.

The writer takes the reader's limit of 4,096 definitions (MAX_SYMBOLS)
and its symbol_art_member check, which now are pub(crate).

The editingData option's description lists what the copy now holds,
type written as text included.

Symbols and their instances:
https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/edit-symbols.html
https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/create-and-place-symbols.html
The nine registration points, in the order the scripting reference lists
them:
https://web.archive.org/web/20180219005134/https://wwwimages.adobe.com/content/dam/acom/en/devnet/illustrator/pdf/Illustrator-Scripting-Reference-JavaScript-cs6.pdf#page=252

Tests: the round trip of symbols, nested symbols and instances in every
state, symbols whose art is a group with a state of its own, images and
gradients in a symbol's art, every registration point, symbol names with
special characters, duplicate and missing symbols, a symbol first used
deep in opacity masks, symbols past the reader's count (one found only
while its holder's art is written included), a large canvas, symbols
with pattern fills (directly, nested, in an opacity mask, through an
inline graphic and through a brushed stroke), scaled instances of
symbols with an outside stroke, rounded live corners, type, a blend, a
tiny or thin object, an ellipse or an instance of a symbol with 9-slice
guides, a symbol whose root is hidden, instance matrices from a fixed
seed, a second write of what is read back giving the same bytes, the
forms written, the instances written as art and the options reported.

Docs: docs/file-format-parity.md, README.md, ROADMAP.md, docs/gaps.md,
docs/roadmap.md and docs/target-app-parity.md describe the editing copy
a saved .ai holds.
2026-11-07 08:35:54 -08:00