Type on a path from the editing data's text document came in at the
font's advances, while the file's own layout (and its page) closes the
glyphs up round the outside of a curve and opens them up round the
inside, even where the frame keeps no spacing. Round a circle the type
ran on past where the page ends it.
The spacing (Type on a Path Options > Spacing) that reproduces the
layout is now fitted from the layout's glyph positions, advances and
turns, using the text engine's own model, and set on the imported text
object. Corners and straight runs are left out of the fit.
Character attribute 12 of the editing data's text document is the case
(1 Small Caps, 2 All Caps). The reader skipped it, so type set in All Caps
came in as typed, in lower or mixed case where the page draws capitals.
Map it onto CharStyle::all_caps and CharStyle::small_caps.
A file's PDF part draws a right-to-left paragraph's words in the order
they show ("mm 120.5"), while its text object keeps them in the order
they are typed (" 120.5 mm "). The page's type was matched to the text
objects by their letters in order, so it matched none and stayed beside
the label as a second copy. It is now matched word by word.
A point type label turned a quarter (a vertical dimension's) lay outside
the box its text object was looked for in, which was always level; that
box now runs along the type's baseline.
The text document's paragraph key 33 (from the paragraph or the
document's defaults) said a paragraph runs right to left, but the reader
only used it to swap left and right alignment: the paragraph's direction
stayed unset, so it went by its first strong letter, and a label with no
Arabic or Hebrew letters (" 120.5 mm ") laid out left to right instead of
reading "mm 120.5". The paragraph style now gets that direction.
Round a closed path the editing data's text document writes the end of
the type's range unwrapped, past the last segment (the start plus the
run), or wrapped the other way. The importer clamped it to the path's
end, so the type kept only the stretch from its start bracket to the
path's end and overflowed. Both brackets are now wrapped onto the loop
(non-finite values fall back to the start); an open path is clamped as
before.
The object's own path that then replaces the frame is written open,
ending on its start: such a path is now closed where its ends meet, so
the span is measured round the loop.
Hand-written tests: an unwrapped, a reversed and a negative range on a
closed circle, a range inside it, an exact full turn that fits a short
line, an open path still clamped, and a path ending on its start closed.
**What:** Illustrator type styled through the Appearance panel came in as its drawn look (outlined compound paths). Comic and "text effect" templates are the common case, and the type couldn't be retyped. This keeps it as editable type with the style as a live VectorCraft appearance.
**Changes**
- **Art styles parsed as a tree.** Compound filters close at their `;`, basic ones at `/Part`. Each fill and stroke in a stack keeps its own effects, opacity and blend mode, and object-level effects stay on the object.
- **Type keeps any plain stack.** That covers several fills and strokes, pattern and gradient fills, Warp, Offset Path, Transform with copies, Drop Shadow, Outer Glow, Feather, Gaussian Blur and Bas Relief. Bas Relief settings are left at their defaults, because Illustrator stores them in a Photoshop plug-in's binary block. Gradients and patterns are placed as the drawn look places them. Other objects keep the existing plain-style rule, since their drawn look is the more faithful option.
- **Point type without a frame matrix.** Illustrator 24 omits the matrix when point type is neither rotated nor scaled. Such stories were left out entirely; they're now read with an identity matrix.
- **Page comparison.** The page's rendering of a style isn't held to match VectorCraft's live rendering where the replaced drawn look lay, and styled made type isn't pruned as unseen.
- **EPS override.** `editingData: "always"` / `convert --editing-data` now applies to Illustrator EPS as well as `.ai`.
**Tests:** 6 new tests, written from scratch; `vectorcraft-eps` passes all 298 on current main. clippy `-D warnings` and rustfmt are clean for eps, engine and cli. `docs/file-format-parity.md` has been updated, including its revision history.
**Clean-room note:** I found the bugs with a user's licensed stock EPS, used locally only; nothing from it is committed. Filter and key meanings come from the public Illustrator Effect docs and public LiveEffect scripting examples (`jntp`, the Warp style order).
**Not included:** VectorCraft's pixel effects treat transparent content as empty instead of using Document Raster Effects Settings › Background (white by default). Because of that, Bas Relief on a transparent pattern gives flat grey. That needs a separate renderer change.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
A right-to-left paragraph in an Illustrator editing copy (key 33 of its
paragraph attributes) stores its alignment relative to its direction:
"left" lays the line out ending at the anchor and "right" starting there.
The importer read the alignment alone, so RTL point type opened shifted by
its own width (Arabic headings past the artboard edge, subtitles starting
where their title ends, a label over a QR code).
Checked locally against ~1,440 single-line point-type stories in a user's
own files (never committed): RTL + left ended at the anchor in 332 of 336,
RTL + right started there in 51 of 51; LTR and centred type unchanged.
Justified alignments keep their current mapping (too few samples).
When a .ai or EPS file opens from its editing data, the import left
out the page's type that no text object owns whenever the regions of
the text objects made from the text document covered at least half of
it, without comparing letters. The region of point type reaches its
longest line's length in characters times its size, plus its size, to
the left and right, and one and a half times its size above and below.
A 21-character line at 10 pt reaches 220 pt to each side. The page's
other type inside that box was lost.
Now the region of a made text object counts toward that half only
when its story has the Latin letters and digits of one of the type's
pieces, compared without case, and only when such stories between
them have the letters of every piece. Only those text objects count
as drawn by the page. The check that decides whether the text
document is trusted still counts every region.
Docs: docs/target-app-parity.md.
Area type whose frame is too short for a line of the font it shows in here
drew nothing: a font that isn't available shows in the fallback font, whose
line may be taller than a frame made for one line of the file's font (overset
type isn't drawn). Where the file itself lays lines out in the frame, the
frame of the imported text object is made taller, from its top, by what its
first line needs, and the open result says how many frames were.
A .ai file with placed files opened from its PDF part (no guides, no sublayers,
no hidden objects), because the editing-data reader stopped at them.
- Lexer: a placed file's preview section is one token (it was skipped).
- Reader: %AI5_BeginPlace ... ~ becomes a linked image with the file's path,
showing the preview the data keeps of it (premultiplied ARGB, capped at
MAX_PIXELS, its rows checked against the bytes there), in its place: the
matrix takes the file's pixels from the canvas, where they start at the point
of art space's origin. A placed file without a preview, a path or a matrix
still makes the file open from its PDF part.
- The open result lists one warning saying linked pictures show the
low-resolution preview until their files are found (rows in the catalogs).
- Engine: a link nothing is known of whose image shows a preview is read where
it is found as the document opens, into the image's bounds, and isn't
reported as modified.
- Tests: lexer, reader (hand-written data), engine open with the link missing
and found, fuzz tokens.
- Docs: file-format-parity, target-app-parity, gaps, ROADMAP.
The leading of a text object made from the file's text document was the
distance between the first two lines of the file's own layout. Those lines
are where the paragraph spacing has put them, and the text object applies
that spacing again, so paragraphs with a space after (or before) came in with
their lines too close (negative space) or too far apart (positive).
The leading is now that distance less the space after the paragraph before
and the space before the paragraph after; two lines of one paragraph have
none between them.
Tests on synthetic stories: negative and positive spacing, a frame scale,
line breaks inside a paragraph, a single line.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Illustrator EPS and .ai type is made whole from the file's text
document, shown or hidden: point type, area type (a story in several
frames as threaded type that flows when the file opens) and type on a
path, with its fonts, size, leading, tracking, scaling, baseline shift,
fill and stroke colours (grey, RGB or CMYK), alignment, indents and
paragraph spacing. The page's drawing of that type, often in pieces, is
left out; where the text document can't say, type that shows still
comes from the page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- a story is read once however many text objects name it; the text objects, their pairs with the
page's type, the pieces of a line and a story's size are capped, and over a cap the layers aren't used
- the ASCII85 of the text document is bounded before it is decoded
- joining the pieces of lines that interleave no longer removes the wrong pieces
- a run length too large for the text saturates instead of overflowing
- a dictionary left open at the end of a layer, and layers nested deeper than are read, make the
file one that comes in as its page (they were silently dropped)
- a file whose editing data can't be decoded says so, and the notes about art that was too much or
too far away count as losses, so writing over the source asks first
- the private data of a .ai is inflated under a cap of its own (the PDF reader's decoder has none)
- the other writers (PDF, selection, office, print) ask before replacing a source that lost things,
and a recovered copy remembers that
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An Illustrator EPS is a flattened page plus, after %%EOF, an editing copy of
the document; a PDF-compatible .ai carries the same in its private data.
Importing only the page lost layer names, order, visibility, hidden layers
and hidden objects and hidden text.
- eps: read the editing copy (Zstandard %AI24_DataStream, older zlib
%AI9_DataStream) into layers, sublayers, groups, compound paths, clip
groups and hidden objects, run by the existing PostScript interpreter.
Falls back to the page, with a warning, when a shown layer uses an
operator that isn't read or the art differs from the page by over 5%.
- eps: read the text document (ate.rs) to place type on hidden layers and
objects; the page's type is matched to the file's text objects by
position (pieces of a line included) and kept with the nearest one when
none claims it. Area type and type on a path are left out with a note.
- pdf/engine: .ai files go through the same reader.
- engine/cli: a file whose import left things out isn't overwritten unless
asked (acknowledgeLoss, --replace-lossy).
- eps: font names re-encoded with a *<n> suffix resolve to their family.
- i18n: es/it/ru for the new import messages; ROADMAP updated.
Container and text-document formats were inferred from user-owned files; the
Adobe file format specification was not read.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>