The reader cut every trailing zero byte off the Zstandard editing data
before decoding it, to drop the padding after the last frame. A frame can
end in a zero byte itself (a raw or RLE block, or a checksum whose last
byte is zero), and cutting it left the frame short, so the file failed to
open with "its Zstandard compression is damaged". The reader now decodes
frame after frame and stops when only zeros are left.
SVG export ignored an image's colorize paint and wrote the image's own
black, white and gray pixels. EPS export flattened every colorized image
into a process-color raster, so a spot color lost its plate. Both now
write the image in the fill color, with the coverage the canvas and PDF
export use (darkness times alpha), so white and transparent pixels take
no color.
- SVG writes a coverage PNG as a mask over a rectangle in the color,
shared between repeated images while each keeps its own color. Linked
mode writes the coverage PNG as a file, and SVG Tiny 1.2 writes a PNG
in the color. A colorized image whose pixels are missing, or whose
grayscale pixels can't be decoded, is left out with a warning.
- EPS writes fully covered or uncovered pixels as an imagemask in the
color's process or Separation ink (Registration on every plate), and a
grayscale image as ink samples scaled by coverage. Level 3 masks its
uncovered pixels out and Level 2 writes them white; gray midtones are
written over white, with the existing warnings.
- EPS flattening no longer counts a solid colorize paint on a grayscale
or 1-bit image as transparency by itself. Opacity, blending modes,
opacity masks, effects and overlapping transparent art still flatten
it, and so does a colorize paint the writer can't draw.
The svg crate's tests turn on resvg's raster-images feature to render
the embedded PNGs.
Docs: docs/mcp.md, docs/target-app-parity.md.
putinterval, copy of a string, roll, counttomark, clear to mark and cvn
worked on up to 4 MiB or 100,000 objects for the one operation each is charged,
so a small file looped over them could keep the importer busy for hours within
the operation budget. Each now spends one operation per 256 bytes or objects.
Fixes#3584
An envelope whose contents are being edited is written the other way
round from one that isn't: its contents, a group on `%_` lines, then
its dictionary, which holds the mesh that shapes them. Only a group of
meshes was left out before an envelope's dictionary, so those contents
were read as art and their undistorted shapes drew over the distorted
art that follows on plain lines (and that the page draws).
The group on `%_` lines before an envelope's dictionary is now left
out whatever it holds; text in it stays as hidden text, as the
dictionary's own source text does.
# Conflicts:
# docs/file-format-parity.md
Art inside a dictionary in the editing data, such as an area text's
frame (`/ConfiningPath`) or a mask's art, can set a transparency
(`Xy`). The writer goes on from it: the objects after an area text
whose frame sets one have no `Xy` of their own. The reader dropped
that state when the dictionary closed, so those objects took the
transparency before the dictionary instead; in a user's file a Lighten
glow before an area text made everything after it fade into the light
background.
The last transparency the art inside a dictionary sets is now the
current one after it (and the enclosing dictionary's). Dictionaries
outside the layers (the setup's styles) are left as they were.
# Conflicts:
# docs/file-format-parity.md
Area type made from the editing data's text document was laid out with
its first baseline the font's ascent below the frame's top. The text
document keeps where each of its lines starts, and the file's page draws
them there: for some fonts the first line sits well above the font's
ascent (Oswald's at about its cap height), so headings came in lower
than on the page and the layers were taken to differ from it.
When the file's first line is more than half a point from where it is
laid out here, the first baseline is now fixed where the file has it
(First Baseline: Fixed), measured in the text document's own frame so a
copied object's type moves with its frame.
# Conflicts:
# docs/file-format-parity.md
Type made from an Illustrator file's text document took each character
attribute its run doesn't set from the document's base attributes, not
from the character style the run names ([Normal Character Style] or
another of the file's). Where the two differ the type came in wrong: a
file whose base fill is white and whose Normal Character Style is black
came in with its text white, invisible, so the layers differed from the
page wherever the page's own type didn't stand in for it.
A run now takes its own attributes, then its character style's (and the
styles that one is based on, up to 16), then the document's.
#4757 gives a blend the Xy written after its record. When the blend is a
layer's last object, the layer's own state block (Xy, Xd, 7 () XW, LB)
went to the blend instead, and the layer stayed opaque. The blend's state
is now tentative like a mesh's (#4748): its own 6 () XW confirms it, the
layer's LB gives it to the layer and the blend keeps what it had.
Also records the .ai/EPS audit fixes under G9, the File formats row and M4.
A PDF saved with Illustrator editing data for only some of its artboards
has pages for those artboards, while the editing data keeps them all. The
layers were compared with the first page on the first artboard, so a page
showing a later artboard never matched and the file came in as its PDF
part, without its layers.
With fewer pages than artboards, the first page is now taken to be the
artboard of its size whose art draws most like it (up to 16 weighed), and
the pages' type goes to that artboard and the ones after it. All the
artboards still come in, and the comparison still decides whether the
layers are used.
A gradient mesh with an opacity mask has its own state (`Xy`) after the
mask's dictionary, not right after its `/End X!`. The operators of the
mask's art, on `%_` lines, cleared the flag that says a mesh just ended,
so the state was read as the next object's and each masked mesh in a
run drew with the blend mode of the one before it.
The flag now goes back to what it was when a dictionary opened once it
closes, as `after_object` does.
A layer's own state block (Xy, Xd, 7 () XW before LB) can have the
stroke style (J j w M d XR) between its Xy and Xd. Read as the art moving
on, it dropped the layer's opacity, so a layer at 17% drew opaque and the
file came in as its page. The stroke style now keeps the pending layer
state, and the state a mesh before it had; an object's own 6 () XW block
still isn't the layer's.
An EPS saved for Illustrator 10 keeps its type in the editing data as
legacy text objects (`To` ... `TO`), with no `/AI11Text` slot for the
page's type to go in, so a file whose page had text came in as its page.
Those text objects are now the layers' own: the page's type isn't put
anywhere, and the comparison with the page decides as before. Type the
editing data keeps only as outlines stays outlines, with a note counting
the page's text objects that are.
An Xy right after a gradient mesh's /End X! is the mesh's only when the
mesh's own 6 () XW follows. When the layer's LB follows (directly or
after the layer's Xd and 7 () XW) it is the layer's, as before, and the
mesh keeps the state it had. Also corrects the revision-history row that
still said meshes with handle weights of 0 aren't read.
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.
The corner placement of placed files was tried only when the first
placement failed the comparison with the page. A small logo in the wrong
place differs too little to fail, so it stayed there. When the corner
placement draws closer to the page and still matches it, use it.
A .ai whose layers hold placed (linked) PDFs and no text object came in as
its PDF part, saying its layers had no text objects for its page's text.
The PDF part draws each placed file, type and all, where the layers show
its preview, so type that lies inside the placed files (at either of their
placements) is theirs and needs no text object. Type outside them still
does.
An Illustrator EPS draws its crop marks after the last layer, between
%AI3_BeginCrops and %AI3_EndCrops (inside userdict /AI3_noCropMarks known
not { ... } if). The reader took them for art outside the layers, and the
whole file came in as its page in one layer. The section is now skipped,
as other non-art sections are; the crop area is %AI3_Cropmarks.
In the editing data of a .ai or EPS file, a gradient mesh's own state
(`Xy`) comes right after its `/End X!`, before its `Xd` and `XW`, as a
group's comes after its `U`. The reader took it as the next object's, so
a Multiply shadow mesh came in as Normal and painted opaque white over
the art under it, while the file's page shows the shadow.
An `Xy` read straight after a mesh ends now also applies to that mesh.
An `Xy` after the mesh's `Xd` and `XW` stays the next object's.
Some files write a handle weight of 0 on some gradient mesh corners. The
reader took only weights of 1, so the mesh and the whole file's layers
were unread. A weight of 0 draws as the same cubic edge against the files'
pages; other weights still leave the mesh unread.
Follow-ups to the review of #4697 and #4698:
- With textAs: outlines, a .ai saved without PDF compatibility, read
from its editing data alone, has its type outlined too, as an empty
page or a text-only placeholder already did.
- Threaded type read from the editing data holds its story in its first
frame: it now flows through its frames before it is outlined, so each
frame's outlines show the text it holds, and the outlined frames
leave their threads.
- Each outline group keeps its type's opacity, blend mode, isolation,
opacity mask and effects (the same carry-over the flattener uses).
- A gradient-mesh corner (/N) holding anything but numbers is unread,
instead of being read with the numbers after it shifted into its
colour and opacity.
A page cropped to the art is recognised in a UserUnit page's units too,
when the art's box is the page in those units and the artboard isn't the
page in points. Such pages are taken only when every page is the art's
box, and only the first page's type is read: the later pages are copies
whose type was moved by the first page's offset and could fill the
layers' text objects twice.
prune_unseen ran before the retry that moves placed files to their box's
corner, and that retry cloned the pruned layers: text an image covered
only at its first placement was already gone, so the file came in without
it or as its PDF part. Clone the layers before pruning and prune each
placement on its own.
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 mesh in the editing data that the reader couldn't read made the whole
file come in as its page or PDF part, without its layers. Three cases
were rejected that files write routinely:
- A mesh starts grey, and `/CS` is written only where the colour space
changes, before the corner it applies to, even within a patch. Meshes
with no `/CS`, or one that changed between corners, were unread.
- Named colours (`[c m y k /DeviceCMYK (name) /Process]` or `/Spot`) and
`/NChannel` inks: each corner gives the tint, 1 being the full colour.
- Grids over 64 patches a side or 4,096 patches (files have 97 x 72).
Each corner now takes the colour space last set before it; named
colours paint at their tint and inks overprint at theirs; the limits
are 128 a side and 16,384 patches.
A placed (linked) file is put where its matrix takes the canvas point of
art space's origin (the centre of %AI3_TemplateBox). In many files the
matrix takes the corner of the box the file is written with instead, so
the image landed far off its artboard, the layers differed from the
page, and the file came in as its PDF part.
The reader keeps that second placement for each placed file where the
two differ. When the layers don't draw as the page, they are compared
again with those files at their box's corner, and used if that draws
as the page. Files whose layers already match keep their placement.
A placed (linked) file written as an object's editable copy, on %_
lines after its drawn look, has its %AI5_BeginPlace and %AI5_EndPlace
markers on those lines too. The lexer dropped comments after %_ that
don't start a section, so the placed file never began, the ~ that ends
it was an operator the reader didn't know, and the whole file came in
as its PDF part. The lexer now returns those two markers from %_ lines,
and the preview reader skips the %_ its header line starts with.
A PDF-compatible .ai whose PDF part's pages are cropped to its art (each
page the box of all the art, the editing data's bounding box) rather
than to its artboards came in as that PDF part: the editing data's
artboard wasn't the page's size, so its layers were refused. With
several artboards every page held all the art, so the document had a
copy of everything per page.
When the artboard isn't the page's size but the editing data's bounding
box is, the layers are compared with the page on that box and keep
their artboards. The comparison of their art with the page still
decides whether they are used.
A layered .ai or Illustrator EPS whose editing data (embedded rasters,
mostly) unpacks past 512 MiB opened as its PDF part or printed page, in
one layer, with "it decompresses to more than 512 MB" (#4628 on 0.17.0
hit the older 256 MiB limit the same way).
The limit is now an eighth of installed memory, from 512 MiB (what every
64-bit build read before) to 2 GiB: 1 GiB on an 8 GiB machine, 2 GiB on
16 GiB or more. Reading takes about as much memory again as the data
(1.1 GB of data with 24 embedded 4096 x 4096 images peaked 1.1 GB above
the start), so the ceiling keeps a small file that unpacks without end
from taking a large machine's memory. 32-bit and web builds keep 256 MiB.
vectorcraft_render::installed_memory exposes the reading the image cache
already sizes itself from.
When the editing data's art is rejected (another page size, art the page
doesn't draw, too many text objects…), the file still comes in as its page,
but now on a layer above the editing data's layers. They come in empty, in
their order, with their names, visibility, lock, print option and sublayers.
The page's layer takes a free name when the file's layers have its Layer 1.
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.
Versioned content can hold three alternatives: newer files write a
Dimension tool group, the same group again after
`%AI17_Alternate_Content_if_version_gt:…` for a version in between, then
plain art after `%AI17_Alternate_Content`. The reader skipped from the
plain `Alternate_Content` marker only, so the middle alternative was read
as art too and those objects came in twice.
The reader now reads the first alternative and skips from the next one,
whatever its marker, to the end of the block, nested blocks included. A
block that doesn't end is read whole with a note instead of failing the
file. The palette reader tracked alternatives with one flag that a block
nested in an alternative reset; it now tracks the depth.
A .ai swatch library without layers comes in as its PDF part with the
swatches of its editing data's palette. A pattern swatch was kept only
when the PDF part defined its pattern, and a pattern library's PDF part
usually defines none, so its pattern swatches were dropped.
ai::palette now gives pattern swatches for the patterns the editing
data defines too, and returns the definitions the PDF part lacks that a
palette swatch paints with, with the images their art shows. The
document gets them with new object ids.
A .ai file whose editing data has no layers, as swatch libraries are
saved, opens from its PDF part, and the PDF part has the default
swatches. layered_ai applied the editing data's palette only when it
built the document from the layers, so Other Library… listed the
default swatches for such a library, and File › Open gave the file the
default swatches as well. A .ai whose layers can't be used (an image
too large for the editing data's reader, or a read error) lost its
palette the same way, and so did an EPS that opens as its page.
The reader now reads the palette on its own (ai::palette, which shares
palette_swatches with the layer reader), and its swatches and color
groups replace the default swatches in those three cases. Spot and
pattern swatches of the PDF part's art that the palette does not have
follow the palette's swatches.
Fixes#4100. Part of #3349.
Found while testing crates/eps/src/import/lex.rs:287-289 (number)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/eps/src/import/text.rs:501-502 (glyph_char)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>