Commit Graph
731 Commits
Author SHA1 Message Date
BFlat ab959b95a8 Merge pull request #7369 from dotCooCoo/fix/ai-hidden-ruler-guide
.ai import: hidden ruler guides open as ruler guides with their own state
2026-11-14 13:43:11 +07:00
BFlat 0ca2952093 Merge pull request #7363 from dotCooCoo/fix/stained-instance-export
Symbols: a stained instance keeps its stain in SVG, PDF and the other exports
2026-11-14 13:42:39 +07:00
Dylan M. Taylor 1e4e3534f5 Merge pull request #7398 from storytold/feat/craft-libs-migration
Migrate CAD (DXF), EPS/PostScript, and SVG to craft-libs (#778)
2026-11-13 22:14:10 -05:00
BFlat ea24f694cf Merge pull request #7345 from dotCooCoo/fix/ai-twist-straight-guide-reach
.ai import: straight compound paths whose guides reach past them keep the drawn look under Twist
2026-11-14 09:32:51 +07:00
dotCooCoo 1e4b6c4e22 Merge main into fix/ai-hidden-ruler-guide 2026-11-13 17:56:13 -08:00
dotCooCoo cfa76d674f .ai import: hidden ruler guides open as ruler guides with their own state
A straight `(N) *` guide path that runs across the canvas or an artboard
opens as a ruler guide, but a hidden one (`1 Xw`) stayed a guide path.
It now opens as a ruler guide too, and every ruler guide made from such a
path keeps the path's hide, lock and name, so its Layers row shows them.
Before, a locked guide opened unlocked and a named one lost its name.

Hidden or locked guide paths that are short, slanted, grouped or `(S) *`
stay guide paths, and commented copies (`%_`) are still left out.

Docs: docs/target-app-parity.md, docs/file-format-parity.md.
2026-11-13 17:55:31 -08:00
Dylan M. Taylor e927729f25 SVG: delegate entity expansion bounds, sniffing, and SVGZ to craft-svg 2026-11-14 01:40:55 +00:00
BFlat bf3cf422e2 Merge pull request #7311 from dotCooCoo/fix/ai-graphs-keep-data
PDF/.ai import: graphs keep their data and settings when their records read whole
2026-11-14 08:39:57 +07:00
Dylan M. Taylor cb5b06672f EPS: delegate binary preview header, sections, and ASCII85 to craft-eps 2026-11-14 01:28:35 +00:00
dotCooCoo de8ac51502 Symbols: a stained instance keeps its stain in SVG, PDF and the other exports
A symbol instance colored with the Symbol Stainer lost its stain in
export whenever the canvas's stained art was not used:

- An instance with geometry effects was reshaped for export from the
  symbol's art with only its dynamic changes, so SVG, PDF, EPS, .ai,
  DXF, EMF/WMF and HPGL wrote the unstained colors. A color adjustment
  on an instance recolored the unstained art the same way.
- PDF, EPS, legacy .ai, DXF and EMF/WMF wrote an instance without
  effects from the unstained art.

tint_node, stain and instance_art now live in
vectorcraft_doc::symbol_overrides, which both the effects and the
exporters can use; vectorcraft_brush re-exports them. Baking, color
adjustments and the writers above now use instance_art: dynamic changes
first, then the stain.
2026-11-13 17:24:06 -08:00
dotCooCoo be8d614b0c .ai import: straight compound paths whose guides reach past them keep the drawn look under Twist
A straight compound path with Twist was read live up to 180° whatever
guides it held, and the live Twist was measured from bounds that take in
the guide members. A straight compound path with Twist whose guide
members reach past its other members now opens as its drawn look
instead of live, and so does a compound path made only of guides.
Compound paths whose guides lie inside their other members, a Twist of
0°, and single paths keep opening live.

The `.ai` writer keeps a graphic-style link only where the reader reads
the style live, so such an object is now written without the link, with
the existing style-link warning.
2026-11-13 16:35:37 -08:00
BFlat f14aa09bbf Merge pull request #7226 from dotCooCoo/fix/legacy-ai-type-kerning
PDF/.ai import: legacy type keeps manual kerning, discretionary hyphens and language
2026-11-14 07:34:10 +07:00
dotCooCoo 76d99d6afb Merge main into fix/ai-graphs-keep-data 2026-11-13 15:46:07 -08:00
dotCooCoo 11ecbe4e9a Merge main into fix/legacy-ai-type-kerning 2026-11-13 15:00:02 -08:00
dotCooCoo aa5d142bd8 .ai save: the editing copy keeps text wrap Offset and Invert Wrap
A PDF-compatible .ai saved with Include Editing Data for Other Apps
wrote no text wrap options, so a wrap object opened from the file no
longer wrapped type. An object with only wrap options got no
dictionary at all.

The writer now writes each wrap object's options in its dictionary as
`AI11TextWrapOffset` (`/Real`) and `AI11TextWrapInvert` (`/Bool`, 1 or
0), the entries `text_wrap` in crates/eps/src/import/ai/read/document.rs
reads, beside the object's name, variables and opacity mask.

- The offset is the one in the art's space, so on a large canvas it is
  written at 1/10 and read back at its size. An offset that is not
  finite is written as the default 6 pt, and one past the -1000 to
  1000 pt the reader keeps as the nearer end of that range.
- Art the writer draws for an object (a blend's steps, a symbol
  instance written as its art, a brushed stroke's pieces, pattern
  tiles, an object's drawn look) is written without the options of the
  objects it is made from. The object itself and a symbol's own art
  keep theirs.
- A clipping path written as its fills and its strokes has its options
  once.

Docs: docs/file-format-parity.md, docs/target-app-parity.md.
2026-11-13 14:48:16 -08:00
BFlat 39027eb42e Merge pull request #7292 from dotCooCoo/fix/ai-mask-art-live-effect
.ai export: opacity mask art keeps its live effects in the editing data
2026-11-14 05:42:16 +07:00
dotCooCoo 2fc344cb8c PDF/.ai import: graphs keep their data and settings when their records read whole
A legacy .ai graph (%_Gs ... %_GS) opened as plain art, with its table,
type, axes and settings left out. A grouped column, stacked column, line
or area graph now keeps them on its group when every record has the
shape and values the version 7 specification's appendix A gives, the
table holds at least one number, the drawn art strokes the plot's left
and bottom edges, and each series group is tagged once as a whole
column, holds only paths and paints them with one fill and one stroke.
The saved art stays as it is on open; Graph Data redraws the graph from
its data afterward, with each series' fill and stroke kept.

Any other graph (pie and scatter graphs, designs, customizations, record
shapes of later versions, tables past 10,000 categories, 256 series or
2,600,000 cells) keeps its art as plain objects with the existing note.

Saving a graph to .ai, in either form, now warns that its data and
settings are not written.
2026-11-13 14:15:19 -08:00
BFlat 4558196e16 Merge pull request #7053 from dotCooCoo/feat/warichu
Type: Warichu stacks selected characters on lines inside their line
2026-11-14 04:32:40 +07:00
BFlat a0e8701256 Merge pull request #7248 from dotCooCoo/fix/ai-twist-past-180-keeps-look
.ai import: straight paths twisted past 180° keep the drawn look
2026-11-14 04:32:07 +07:00
dotCooCoo 185ba12b8c .ai export: opacity mask art keeps its live effects in the editing data
An object in an opacity mask's art is written with its art style, as on
the layers, so a mask whose art has Offset Path opens from the editing
data with the effect live and draws the masked object as it did. This
covers a hidden art root and masks inside another mask's art.

Geometry effects on mask art that no art style holds, such as Roughen,
are applied to the art written, in the masks of layers, symbols and
pattern tiles. Before, the mask's art was written without them.
2026-11-13 13:08:24 -08:00
BFlat cc0413ee34 Merge pull request #7217 from dotCooCoo/fix/brush-repeated-names-numbered
Brushes: repeated brush names are numbered when a document loads
2026-11-14 03:23:54 +07:00
dotCooCoo fdedfb0e59 Merge main into feat/warichu 2026-11-13 11:59:22 -08:00
BFlat 1e385ba06e Merge pull request #7210 from dotCooCoo/fix/envelope-empty-type-box
Envelope: type that draws nothing adds no box next to other content
2026-11-14 02:38:27 +07:00
dotCooCoo b6e3e96db3 Brushes: tests expect the first brush of a repeated name
With repeated names numbered when a brush library is read, a stroke that
names a repeated brush draws the first brush of that name, and a later
brush of the name is read as "Name 2". Three tests built a library with a
repeated name and expected the later brush:

- Document Info lists a name two stored brushes share with the first
  brush's type (art), not the last one's.
- The two render memory tests add their own `Dots` scatter brush beside
  the default library's `Dots`. They now remove the default `Dots` first,
  so the stroke draws their brush.
- The .ai writer test that leaves out a Pattern brush "X" gets the
  Calligraphic brush after it back as "X 2", the name it had in the
  document.
2026-11-13 11:23:40 -08:00
dotCooCoo 69deae55cf .ai import: straight paths twisted past 180° keep the drawn look
A path or compound path with straight segments and nothing stroked was
read with its Twist live at any angle. It is now read live up to 180°
either way, a limit VectorCraft sets, and one twisted further keeps the
file's drawn look, as curves and groups already do past 90°.

The .ai writer keeps a graphic-style link only where the reader reads the
style live, so a straight object twisted past 180° is now written without
the link, with the existing style-link warning.

Docs: docs/file-format-parity.md, docs/target-app-parity.md.
2026-11-13 11:21:08 -08:00
dotCooCoo 0c20d09051 Legacy .ai save: manual kerning of 0 is written as Tk
A pair with manual kerning of 0 was saved with no Tk and opened with automatic kerning. It is now written as 0 0 Tk and reads back as manual kerning of 0.
2026-11-13 11:12:23 -08:00
dotCooCoo 6643d5cf2a PDF/.ai import: legacy TG, Xu and Tu with bad operands give no warning
A line breaking value (TG) or Japanese layout rules (Xu, Tu) with missing or non-numeric operands no longer mark the setting as set, so the next visible text object gives no warning about a setting the file did not specify.
2026-11-13 11:02:01 -08:00
BFlat 1c1bb2f96e Merge pull request #7191 from dotCooCoo/feat/gradient-hue-blend
Gradients: Hue Clockwise and Hue Counterclockwise mix stops around the hue wheel
2026-11-14 01:53:39 +07:00
dotCooCoo 78d3f6e1a5 PDF/.ai import: legacy type keeps manual kerning, discretionary hyphens and language
Legacy .ai type (version 7) now reads the operators it used to skip:

- `autoKern kernValue Tk` (and `TK` in overflow text) sets the manual
  kerning of the character before the pair, or at the start of a line
  the space before the line's first character; autoKern 1 sets the
  pair back to automatic. Values are kept to -1000..10000.
- `T+` and `T-` (discretionary hyphens) become soft hyphens.
- `languageID XL` sets the characters' language (ids 0 to 11).
- A visible text object read while a line breaking value (`TG`) or
  Japanese layout rules (`Xu`, `Tu`) are set opens with a warning,
  once per file for each.

Final-form positions (Tc, Tw, Tm, Td, T*, TR) stay ignored.

Legacy .ai saves now write manual kerning as `0 value Tk` after each
character that has it and before a paragraph's first character with
space before it, and soft hyphens as `T+`; manual kerning is no longer
listed among the attributes left out. The writer's prolog defines Tk
and T+.

Docs: docs/file-format-parity.md, docs/target-app-parity.md.
2026-11-13 10:12:57 -08:00
dotCooCoo b53cd7b52e Envelope: type that draws nothing adds no box next to other content
An envelope around type of only spaces and a rectangle mapped from the
union of the rectangle and the type's laid-out box, so the empty type
moved and scaled how the rectangle was distorted. Type that draws
nothing now adds no box while other content in the envelope has one,
in groups and layers and as a clipping path too. When nothing else in
the envelope has a box, the type keeps its laid-out box, so an envelope
of only empty type keeps its frame, Make with Mesh and Reset with Mesh
still work, and Release still gives back the warp shape.

This applies to Make with Warp, Make with Mesh, Make with Top Object,
Reset with Mesh, Edit Contents and the stroke margins. Type that has
not been measured yet keeps its laid-out box as before.

The PDF/.ai import's boxes for envelopes with Distort Appearance
measure each object as the whole contents measure it, so the margins
the import tries match the contents' geometric box. The three import
tests that measure type without registering the glyph measure now
register it first, so another test registering it while they run no
longer changes their result.
2026-11-13 09:37:03 -08:00
jmverges deabf78747 Merge remote-tracking branch 'upstream/main' into fix/page-check-typed-envelopes
# Conflicts:
#	docs/target-app-parity.md
2026-11-13 17:22:20 +00:00
dotCooCoo 5f77b65aa2 Gradients: Hue Clockwise and Hue Counterclockwise mix stops around the hue wheel
The Gradient panel's Method menu gets two methods of VectorCraft's own
beside Classic and Perceptual. They mix a linear, radial, conic or
diamond gradient's colors around the hue wheel: hue, saturation and
brightness each change evenly from one stop to the next, and the hue
angle keeps one direction, as the fixed-direction rules of
https://www.w3.org/TR/css-color-4/#hue-interpolation do. Hue Clockwise
lowers the angle (red, magenta, blue), which is clockwise on the color
wheels, and Hue Counterclockwise raises it (red, yellow, green).

Two stops whose hues are less than half a color level apart count as
one hue and keep it, so a color and a tint or shade of it do not turn
the whole wheel. A stop whose RGB numbers are less than half a color
level apart is gray and takes the hue of the stop it mixes with, and
black takes that stop's saturation too.

- `paint.editGradient` and `paint.setFill` take `method: hue-clockwise`
  or `hue-counterclockwise`, in any case, with a space or an underscore
  for the hyphen or without it; other spellings are an error. The native
  file writes `HueClockwise` and `HueCounterclockwise`.
- The canvas and raster exports draw the gradient through stops on its
  curve, within half a color level.
- Reverse turns the method the other way with the stops, and a Reflect
  spread is repeated from the curve's stops, so both keep the colors.
- SVG and PDF write the gradient as an RGB image, CMYK and spot color
  stops included, with the spot warning. EPS writes a shading in process
  colors with a warning for spot stops. A legacy .ai file and the .ai
  editing data get the colors between the stops as stops of their own,
  in process colors, at most 256, with a warning. CSS gets those stops
  too.
- The `method` error names the four values, in every catalog.
- The Method menu is as wide as its longest name in the interface
  language, within the panel.

Docs: docs/mcp.md, docs/plugins.md, docs/target-app-parity.md, ROADMAP.
2026-11-13 08:26:57 -08:00
jmverges 24bcdb5116 Merge remote-tracking branch 'upstream/main' into fix/page-check-typed-envelopes
# Conflicts:
#	crates/eps/src/import/ai.rs
#	docs/file-format-parity.md
#	docs/target-app-parity.md
2026-11-13 16:22:41 +00:00
dotCooCoo a68b843d8c Merge main into feat/ai-image-warp-live 2026-11-13 08:12:50 -08:00
BFlat 508c1b658e Merge pull request #7096 from dotCooCoo/feat/ai-save-envelopes
PDF/.ai save: envelopes stay editable when the file is reopened
2026-11-13 23:04:30 +07:00
BFlat 02445d5f02 Merge pull request #7130 from dotCooCoo/fix/eps-image-samples-once
EPS: an image drawn more than once writes its samples once
2026-11-13 23:00:45 +07:00
jmvergesandClaude Opus 5.5 67d947a3c6 .ai and EPS import: weigh each placement of placed files on its own for envelopes of type
Two findings in the review of #7146, in how layers whose differing
objects come in as the page's art treat envelopes of type:

- The envelopes of type the page holds against the layers were weighed
  on the layers as read only, and that choice was also applied at the
  placed files' other placement, which can move a placed file over or
  under an envelope. Each placement is now weighed on its own. The
  places of those left live, for layers that open alone, still come
  from the layers as read: a .ai that opens alone has no other
  placement.
- A placement that draws as the page with only the held envelopes put
  back was dropped, as nothing in it differs for its objects to come in
  as the page's art. It is now used as it is, as layers that draw as the
  page with every live envelope's drawn art put back already are. The
  try with every envelope of type put back keeps its order: objects
  that differ come in as the page's art, as before.

Part of #7141.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 14:53:39 +00:00
dotCooCoo 9bb2250476 .ai: an image's Warp effect is saved and opened live
An embedded image whose appearance is one Warp effect, with no opacity
mask, is written to the editing data with an art style of that Warp:
its bent pieces as its drawn look and the image itself on `%_` lines,
as other styled objects are written. The reader takes such an image
with its Warp live when the live warp draws as the drawn look, using
the comparison and budget it uses for warped type; otherwise the drawn
look stays, with the drawn-look note. A linked image with a Warp is
written as its drawn art, as before, without its live Warp. Images with
an opacity mask and images with another effect keep their drawn art.

An embedded image whose samples are on `%_` lines is now read as the
hidden copy it is. Before, it came in as a second visible object beside
its drawn look.
2026-11-13 06:10:35 -08:00
jmvergesandClaude Opus 5.5 05f38616e0 .ai and EPS import: envelopes of type stay live where other art differs from the page
When the layers with their live envelopes didn't draw as the page, and
didn't with the drawn art of every live envelope put back either, every
envelope of type got its drawn art back wherever the layers were still
used: the editing data trusted, the objects that differ coming in as
the page's art, or a .ai whose layers would open alone opening alone.
Art that differed anywhere on the page cost every envelope of type its
editable type, though putting its drawn art back changed nothing.

Now each envelope of type is weighed in the page check's own drawing,
on the pixels that it and its drawn art ink (the two drawn together on
a layer of their own, without the rest of the layers). It keeps its
drawn art and hidden type where the layers with it differ from the page
in more of those pixels than its own check allows against its drawn art
(10%, or 3% with all their neighbours), or where the envelopes that stay
live would together differ there in more of a page than half of what
the page check allows (a budget of its own, the same size as the one
their own checks share). Art differing on its pixels still holds it.

It also keeps its drawn art, as before, where the layers don't draw it
where it is and as itself, on the page or off it: under a clip or an
opacity mask, in a group with an appearance of its own, or in another
envelope, a repeat or a blend. Another envelope on no page stays live,
except on a page that holds all the art (an EPS's art box), where one
reaching past the page keeps its drawn art. Past 12 on the pages, all
keep it.

A .ai whose layers would open alone still opens alone: its envelopes of
type are settled again as they were with the page, in the same order
and with the same checks, so each that opens live draws as it was
weighed, and those the page held get their drawn art back. Where the
differing objects are to come in as the page's art and can't with
envelopes of type live (an object that holds type isn't replaced),
every envelope of type gets its drawn art back and that is tried again,
as before. Where putting back the drawn art of every live envelope
makes the layers draw as the page, it is still put back.

On 224 of our own files (used locally), envelopes of type that open
live go from 174 to 186 in 8 files, none fewer; no file changes between
its layers and its PDF part.

Part of #3179.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 14:04:38 +00:00
dotCooCoo 19ab993479 EPS: an image drawn more than once writes its samples once
An image placed several times in an EPS file (the pieces of an envelope
or a Warp effect, copies of one image, a symbol's instances) wrote its
whole ASCII85 data at every placement: 200 copies for one envelope at
the default fidelity, enough for a large photo to pass the 256 MB page
limit and lose pieces.

The samples of such an image are now written once in the setup as
VCim<i>, and each placement reads them from there:
- Level 3: a ReusableStreamDecode stream, rewound with resetfile and
  read through a new FlateDecode filter at each placement.
- Level 2: an array of strings of at most 49,152 compressed bytes, fed
  to RunLengthDecode by a procedure.

An image placed once is written where it is drawn, byte for byte as
before. The page limit counts a repeated image's samples once, and a
colorized image is reused per ink.
2026-11-13 05:38:06 -08:00
dotCooCoo 7b30424538 Merge main into feat/warichu 2026-11-13 04:06:56 -08:00
jmvergesandClaude Opus 5.5 728fd9ef14 Envelopes: a top object's side that is one split curve follows its parameter
A top-object envelope gives each piece of a side a share of the content
in proportion to its control polygon's length. When anchors split a
curve without changing its shape, the pieces' polygons don't divide the
side where the curve's own parameter does, so the envelope bent its
contents differently although its outline was the same: Add Anchor
Points on a top object moved the art, and a top object whose .ai mesh
saves each side's curve split into thirds drew its type up to about
half a point off its drawn art, which follows the whole curve's
parameter.

Coons::from_top_object now checks whether a side's pieces, from corner
to corner, are all pieces of one cubic. The pieces' spans of that
curve's parameter come from the handle lengths either side of each
split, the curve from the outer ends and handles, and every piece must
lie within 0.02 pt of the curve's piece over its span (each control
point). Such a side runs along that curve, by its parameter. Any other
side keeps the polygon shares, including a side only partly made of one
curve and a smooth side drawn in many pieces that isn't one curve as a
whole. Corners found from the outline can still move when anchors are
added, as before. Gradient meshes made from a path are unchanged.

Existing top-object envelopes with such sides redraw.
a_split_curve_moves_the_top_object_share_to_its_new_anchor, which
pinned the earlier behaviour, becomes a_split_curve_keeps_the_top_object_patch.

Tests: a_split_curve_keeps_the_top_object_patch,
a_top_object_joins_a_split_curve_saved_with_rounded_coordinates,
a_top_object_keeps_smoothly_joined_curves_apart,
a_top_object_side_partly_of_one_curve_keeps_the_polygon_shares and
a_top_object_side_of_many_arcs_stays_on_them (vectorcraft-doc),
envelope_top_object_keeps_its_look_when_anchors_are_added
(vectorcraft-engine) and
a_top_object_whose_top_is_one_curve_split_in_thirds_opens_live
(vectorcraft-eps).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 11:18:40 +00:00
dotCooCoo 737d9bc5b8 PDF/.ai save: envelopes stay editable when the file is reopened
A PDF-compatible .ai saved with Include Editing Data for Other Apps now
writes warp, mesh and top-object envelopes as live envelope records in
its editing copy: the grid or contents, Envelope Options, Fidelity,
Edit Contents, a warp's settings and the contents' box, beside the
distorted art, in the form the .ai envelope reader reads. The file
opens with the same live envelopes.

Envelopes the reader would open in another shape or with other
settings are written as their distorted art with a warning: type in
the contents, a turned frame, an old warp map, a mesh laid out another
way round, a top object whose sides do not pair up, a Fidelity that is
not a whole number, bounds margins kept with Distort Appearance off,
envelopes in opacity masks, pattern tiles, clipped art or another
envelope's contents, on a large canvas, and after the first 256.
2026-11-13 02:28:11 -08:00
BFlat d118fcb17c Merge pull request #7046 from dotCooCoo/fix/issue-3349
.ai import: graphic styles of one paint open with their fill and stroke
2026-11-13 16:04:36 +07:00
BFlat dd8446f3a4 Merge pull request #7054 from dotCooCoo/feat/nine-slice-guides-outside-art
Symbols: 9-slice guides may lie outside the symbol's art
2026-11-13 15:03:59 +07:00
dotCooCoo 199a9dbce3 Merge main into feat/warichu 2026-11-12 22:28:38 -08:00
BFlat 499903878e Merge pull request #7055 from dotCooCoo/feat/mojikumi-presets
Type: the Mojikumi Set menu offers the predefined spacing sets
2026-11-13 13:17:36 +07:00
dotCooCoo ecb994540c .ai import: a one-paint style's gradient paints the side its flag names
A gradient instance in a one-paint style marked both the fill and the
stroke as not read, so a style with a solid fill and a stroke gradient
was left out even when its `Xd` turns on only the fill. The instance's
flag (`1 Bb` for the stroke) now says which side the gradient paints, as
the object reader reads it; a gradient outside an instance still marks
both.
2026-11-12 21:36:18 -08:00
BFlat 6c3022d6e1 Merge pull request #7008 from jmverges/fix/6983-import-font-exact-postscript-name
.ai and EPS import: type takes the face of its exact PostScript name and version
2026-11-13 12:27:09 +07:00
dotCooCoo a1f6eaed77 .ai import: a one-paint style reads the last fill and stroke paint
A one-paint style took a gradient (`Bg`) after a solid stroke color as
the solid stroke, and left out a style whose solid fill color came after
a pattern or a gradient. Each later paint operator now replaces the one
before it on its own side, and a gradient marks both sides as not read,
since its operands do not say which one it paints.
2026-11-12 21:25:12 -08:00