Commit Graph
287 Commits
Author SHA1 Message Date
JonRC e44f5a1b77 Fix #5312: EPS files written by Ghostscript's eps2write open
`gcheck` and `scheck` answered false for every operand. The PLRM (3rd
ed., `gcheck`) says they are true for a simple object, and false only
for a composite one in local VM. The eps2write prolog copies its
initial graphics state into global VM and leaves a value alone only
when `gcheck` is true. So the integers in the `currenthalftone`
dictionary took the copy path, left the stack one item off, and the
next `put` failed with a typecheck in `cp2g`.

Simple objects now answer true. Composites still answer false, since
every composite here is in local VM.
2026-11-03 01:07:29 -03: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 83f2bfe9d6 Symbols: a scaled instance of a symbol with 9-slice guides keeps its corners and edges unstretched
Squashed from #5079 onto main.
2026-11-02 15:22:07 +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 71d706fdcc .ai import: a placed file's tick operator gets its own method 2026-11-02 14:32:56 +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 2a41c74690 .ai import: a group still open at the end of the art is an error
A group (`u`), clipping group (`q`) or compound path (`*u`) opened after
the last layer and never closed was accepted, and the art inside it was
dropped without a message. The reader now stops with "it ends inside a
group or compound path", as it already does for data that ends inside a
layer. Pattern and symbol definitions, which are read inside a group of
their own, are not affected.
2026-11-02 14:32:56 +07:00
bflatastic 188754f68e Tests: the listed Shadow style writes every Drop Shadow setting 2026-11-02 14:32:54 +07:00
bflatastic 83daab708d .ai import: graphic styles scale with a document drawn on its page 2026-11-02 14:32:54 +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
bflatastic dc363ec356 Tests: Convert to Shape beside Offset Path is a style any object takes 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
bflatastic 645757448f .ai import: the layer-end operator gets its own method 2026-11-02 12:04:26 +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 13fef54e7f .ai import: cap area type gutters after scaling
Gutters and the first-baseline minimum were capped at 10,000 before the
art's scale was applied, so a Large Canvas file could bring in a gutter
of up to 100,000 pt, past the limit text.areaOptions sets. The cap now
also applies to the scaled value.
2026-11-02 12:04:07 +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 c3093fcafb .ai import: a wraparound group's graphics wrap with no offset
Every graphic in a version 7 wraparound group (`*w` … `*W`), before
or after its type, is now a wrap object with an offset of 0 instead of
the 6 pt default, and a group with no type in it marks all of its
graphics. A graphic before the type is below it, so the type flows
around it only after it is moved above. Operands on `*w` or `*W`, and
a `*w` inside a compound path, are errors.

Docs: docs/file-format-parity.md.
2026-11-02 12:04:06 +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
dotCooCoo 100730d4d3 .ai import: a file whose PDF part can't be read opens from its editing data
Squashed from #5051 onto main.
2026-11-02 12:04:04 +07:00
dotCooCoo b4c01e3120 Tests: move the repeated-records timing test next to the other objects-on-path record tests 2026-11-02 12:03:57 +07:00
dotCooCoo 9596130be4 .ai import: Objects on Path records measure each group once
Each Objects on Path record measured the bounds of every object it names,
and checked its path, before it was turned down. A thousand copies of a
record whose object is a group of 20,000 paths took 5.6 s to open instead
of about 50 ms. A record now checks its group and ids first, and the path
and objects of a group are measured for one record at most.
2026-11-02 12:03:56 +07:00
dotCooCoo bb354fc92a .ai import: page art moved to its artboard takes its pattern tiles along
Art taken from a `.ai`'s PDF part on a later page is moved to where the
editing data puts that artboard. The move left pattern fills and strokes
where they were, so their tiles no longer lined up with the art. This
happened in three places: the page art that stands in for an unread
object (#5050), the page's type given to text objects, and the art on
each page when the file comes in as its PDF part with the artboard
layout kept. Each move now transforms the pattern tiles too.
2026-11-02 12:03:56 +07:00
bflatastic 54b79c02b0 .ai import: Convert to Shape records string entries through the shared dictionary parser 2026-11-02 12:03:54 +07:00
dotCooCoo e130cf3e0c .ai import: Convert to Shape opens live on paths and type
Squashed from #5142 onto main.
2026-11-02 12:03:54 +07:00
dotCooCoo 664fe8a12b .ai import: Stylize effects open with their saved settings
An object whose art style holds Drop Shadow, Inner Glow, Outer Glow,
Feather, Round Corners or Scribble now opens as itself with those
effects live, in their order. Feather is read under the name and key
the files use (`Adobe Fuzzy Mask`, `Radius`).

Each setting is read only when it is written as its kind (`/Int`,
`/Real`, a fill style for a color) with a value in range. An unknown
key or choice, a missing setting, or a Scribble with a color of its own
keeps the drawn look, where the reader used to cast, clamp or default
the value.
2026-11-02 12:03:53 +07:00
dotCooCoo 0ccc6d9df5 .ai import: paths keep their notes and Show Center
Squashed from #5129 onto main.
2026-11-02 12:03:35 +07:00
dotCooCoo c21ff5698e Tests: move the legacy type tests next to the text object tests
The legacy type helpers and tests move from the end of crates/eps/src/import/ai/tests.rs to after text_objects_are_slots_named_after_their_story, and the helper stream7 is renamed type_stream7.
2026-11-02 12:03:33 +07:00
dotCooCoo db11446502 .ai import: legacy type keeps its spacing, hanging punctuation and hyphenation
Legacy type (`To` ... `TO`) now reads `min opt max TC` and `min opt max TW`
into the paragraph's letter and word spacing, `hangingQuotes Tq` into Roman
hanging punctuation, and `hyphenate limit lead tail max Th` into hyphenation
on or off, After First, Before Last and the Hyphen Limit (0 when the limit
flag is 0). Values are kept to the ranges the Justification and Hyphenation
options take, and a spacing triple out of order goes back to its default.

Operands of the legacy type attributes are read by their place: an attribute
whose operands are missing, are not numbers, or are flags other than 0 and 1
stays as it was. Before, a value that was not a number shifted the later
operands (`5 /x 7 Ti` set indents of 5, 7 and 0), and a bad `TA` or `Ta`
turned kerning on or set the alignment to left. One long `Tx` no longer goes
past the reader's limit on characters.
2026-11-02 12:03:33 +07:00
dotCooCoo 06d037c1b2 .ai import: a clipping path keeps its stroke
A clipping path painted with a stroke (`W S`, `W B`) lost the stroke when
a .ai file opened from its editing data. The clipping path now keeps it,
and it paints over the clipped art as a clipping path's stroke does in
VectorCraft. A gradient-filled clipping path stroked with `1 BB` or `2 BB`
put the stroke on the painted copy of its fill, under the clipped art; it
now goes on the clipping path too. Several clipping paths in one group
make one compound path, which keeps their stroke, opacity and blend mode
when they all have the same ones.
2026-11-02 12:03:31 +07:00
Brandon Thomas b676c626e0 Merge pull request #5186 from dotCooCoo/fix/legacy-ai-character-rotation
Legacy .ai: type with rotated characters is written as outlines that keep the rotation
2026-11-01 23:45:34 -05:00
dotCooCoo 880a078053 Legacy .ai: type with rotated characters is written as outlines that keep the rotation
A version 7 text object has no arbitrary-angle character rotation: its
character attributes include baseline shift (Ts), scaling (Tz) and
tracking (Tt), Tv only turns Roman characters 90 degrees in vertical
text, and the positioning operators such as Tm are ignored on reading.
Type with Character Rotation was written as editable text with the
rotation left out, so rotated characters opened upright.

Such type is now written as its glyphs' outlines, the way type 8-bit text
can't hold already is, with the warning "type with rotated characters is
written as outlines, which keep the rotation, and is no longer editable
text". The outlines keep each character's rotation, baseline shift and
scale. Type without rotated characters, and type turned a whole number of
times, is still written as text.

Docs: docs/file-format-parity.md, docs/mcp.md, docs/target-app-parity.md.
2026-11-01 16:35:49 -08:00
dotCooCoo 7984606471 EPS: spot inks named All or None print as spot inks
A Separation space whose colorant is named All paints every plate, and
one named None paints nothing (ISO 32000-1:2008, 8.6.6.4). EPS export
wrote a spot swatch's name as its colorant, so a spot color named All
printed on every plate and one named None did not print.

Such an ink is now written as All Spot or None Spot, numbered All Spot 2
and on when a spot swatch already has that name, in its Separation
space, DeviceN spaces and the %%DocumentCustomColors and
%%CMYKCustomColor comments. At PostScript Level 3, a gradient that mixes
one of these inks with another spot ink or with process or gray stops is
now a DeviceN shading instead of process colors. Registration still
writes All.
2026-11-01 16:32:41 -08:00
dotCooCoo f1fd255120 .ai import: version 7 palettes keep their swatches without names
Version 7 files write each palette swatch as its paint and `Pc`, with no
name. The reader only kept a swatch with a name before `Pc`, so such a
palette gave no swatches and the document got the default set.

A swatch without a name is now named by its spot color, gradient or
pattern, or by its values in the format the swatch import already uses
for unnamed colors ("Gray K=25", "C=25 M=0 Y=0 K=0"). `Pn` and a `Pc`
with no paint before it add nothing. Named swatches keep their names.

An operator the reader does not know, or a `Pg`, now ends the paint
before it, so a later `(name) Pc` no longer takes that paint. The
swatch limit is kept with a running count instead of summing every
group at each `Pc`.
2026-11-01 06:55:53 -08:00
dotCooCoo 427bce12b0 Character: Font Size can measure the cap height, x-height or ICF box
Squashed from #5026 onto main.
2026-11-01 11:08:55 +07:00
dotCooCoo af133b1ee4 .ai import: envelopes open as live envelopes
An envelope in a .ai's editing data opened as its distorted art, with
its mesh and contents dropped. It now opens as a live envelope of its
contents: a warp with its style, bend, distortions and orientation, a
mesh with its points and handles, or a top object with its outline,
with Envelope Options, Fidelity and Edit Contents.

The envelope replaces its drawn art only when VectorCraft draws it like
that art, with a budget so that live envelopes don't make the file open
from its PDF part. When the layers still differ too much from the page,
the drawn art is put back. An envelope that can't be read, or that
draws differently, keeps its drawn art with a note. Envelopes of type
keep their drawn art and hidden type as before.
2026-11-01 11:08:49 +07:00
dotCooCoo c027947362 .ai import: Objects on Path groups open live with their path, Pivot and Rotate
Squashed from #5059 onto main.
2026-11-01 11:08:47 +07:00
dotCooCoo 70651f585e .ai import: template layers and ruler guides open as they were set
The editing data's layer options set a template layer from the flag four
places after the colour index. The colour index of nine-operand options,
and the colour index and dimming percentage of eleven and twelve operand
options, are read from their own places (they were read one place later).

A straight (N) * guide path directly on a layer that runs across the
whole canvas, or from edge to edge of one artboard, opens as a ruler
guide on that layer (an artboard guide of that artboard); other guide
paths stay guide paths. Guides scale with the document when its editing
data is in the units of a UserUnit page, and an EPS that keeps only its
first artboard keeps only that artboard's guides.

Fixes #779
2026-11-01 11:08:18 +07:00
dotCooCoo 18b9c6113e .ai import: one object the reader can't read comes in as the page's art, and the layers stay
Squashed from #5050 onto main.
2026-11-01 09:24:23 +07:00
dotCooCoo 1840b9a582 EPS export: gradients of spot colors keep their spot inks
EPS export wrote every gradient with a spot color stop in process
colors. A gradient of tints of one spot color and paper white is now
written in that ink's Separation space, at Level 2 and 3. At Level 3, a
gradient that mixes a spot color with CMYK or gray colors, or two spot
colors, is written in a DeviceN space of the process inks its stops use
and the spot inks, each spot ink with the CMYK equivalent its Separation
space has. An off-center midpoint is a stop with the inks halfway
between its two stops. Paper white is an exact white, whether or not
the stop is linked to a process swatch.

These gradients are still written in process colors, each with its own
warning: a mix with other inks at Level 2; a mix with RGB or Lab colors
in an RGB export (a CMYK export converts those stops to CMYK first); a
DeviceN space that would need more than 32 colorants or a spot ink named
Cyan, Magenta, Yellow, Black, All or None; and a Perceptual gradient.
The old warning for every spot gradient has been removed.

The header lists two or more spot colors with each %%+ line right after
the comment it continues: %%DocumentCustomColors and %%CMYKCustomColor
were interleaved, so a DSC reader took the second ink's name as part of
the first ink's %%CMYKCustomColor.
2026-11-01 06:11:12 +07:00
bflatastic f22caa6108 Fix #4938: Dynamic symbols keep per-instance appearance linked to the symbol 2026-11-01 04:53:38 +07:00
dotCooCoo d2d4315524 .ai import: a Zstandard frame that ends in a zero byte keeps it
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.
2026-11-01 01:22:08 +07:00
dotCooCoo d6297dbfb9 .ai import: type comes from the text document alone when every shown story reads
Squashed from #4865 onto main.
2026-11-01 00:35:35 +07:00
dotCooCoo bb6cbfb2b4 Images: SVG and EPS export write colorized grayscale and 1-bit images in their fill color
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.
2026-11-01 00:35:27 +07:00