Commit Graph
185 Commits
Author SHA1 Message Date
dotCooCoo afdaa47cd5 SVG import: a clip path made of text clips by the text
A <text> in a <clipPath> was dropped with a warning, so a clip path made
only of text had no content and the art it clipped was hidden. The
clipPath content model in CSS Masking Module Level 1 lists text and use
(https://www.w3.org/TR/css-masking-1/#ClipPathElement).

The importer now clips by the text's glyph outlines, together with any
shapes in the same clip path, and keeps clipPathUnits, the transforms,
clip-rule, visibility, the conditional processing attributes and a clip
path on the text. A <use> in a clip path that shows a text clips the
same way, placed by the <use>; a clip path on the <use> applies in the
<use>'s user space, and a reference goes to the first element with the
id. When the clip path holds only one visible text, with no clip path
of its own and no even-odd clip rule, the clipping path of the clip
group is the text object with no fill or stroke, the same structure
Make Clipping Mask produces for type. A text or a <use> with an id in a
clip path can still be shown by a <use> outside it. Each text's
outlines are laid out once, however many elements use the clip path.

A transform set by a style rule or a style attribute on a text now
places it, as it does a shape. Before, only the transform attribute was
read, inside and outside clip paths.
2026-11-11 01:04:34 -08:00
ruchirguitarandClaude Opus 5.5 5fe12c205c Test #3566: test entity references in element content against the expansion limit
The existing entity tests are unit tests that place the references in
attributes. This adds an import-level test with the references inside
<desc>, which the import refuses, and checks that the namespace entities
drawing apps write (&ns_svg;) still import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 19:39:18 +05:30
dotCooCoo 8ae9ca2012 SVG export: masks over type keep the full region; live art measured expanded
A mask, inverted mask or knockout group over type now keeps the region
that covers any artwork. Type is measured by its layout or, on a path,
by the path, and a reader that sets live type in its own font can draw
it wider, so a region sized from that box could cut glyphs off.

A live object (blend, envelope, repeat, mesh, placed document,
intertwine) under a mask is measured as the art the writer writes for
it, expanded on its own with the same limits.

The writer keeps its symbol boxes and symbols' art reach in plain
fields instead of cells.
2026-11-09 22:37:17 -08:00
dotCooCoo bd0515ccc8 SVG export: mask and filter regions cover the art they apply to
Opacity masks, inverted and unclipped masks, and knockout groups were
written with mask and filter regions of 200,000 x 200,000 units around
the origin. CairoSVG draws each mask into an image of its region's size
and stops with "out of memory", so a file with any of them did not
open; Firefox drew inverted masks wrong and WebKit drew knockout groups
and inverted masks wrong.

Each region now covers what the masked or knocked-out art paints: its
visual box grown by its raster effects, brush art and members' reach,
the mask art's box, and in a scaled symbol instance its strokes and
effects at their own size. A mask whose art reaches past its object
still comes back whole on import.
2026-11-09 22:16:58 -08:00
dotCooCoo 35db931810 Fix #6316: a group copies each member in place when a member makes more art
A group with a visible member that carries a geometry, Pathfinder or 3D effect, also in an opacity mask, keeps its previous copies. Copies stop at the budget with what each copy's own effects make, and a group's copies count the art of opacity masks.

The PDF and SVG tests for a group's image copies now expect the original on top.
2026-11-09 16:24:13 -08:00
dotCooCoo ac0bb3c4ce Gradients: freeform points that share a color model mix in that model
The canvas, the gradient chips and the SVG, PDF, EPS and EMF writers mixed
freeform points in display RGB, while Object > Expand and Create Gradient
Mesh mixed them in the model the points share. Points that are all CMYK,
Gray, Lab or RGB now mix in that model in those places too. Points in
different models mix as before: in display RGB, and on ink planes as the
inks of each point.

This changes how existing art looks:

- CMYK and Lab points. With the default Generic CMYK working space and no
  ink planes, halfway between C100 and K100 the canvas drew (17, 106, 139)
  and now draws (54, 118, 143).
- RGB points in a working RGB space other than sRGB mix in that space.
- On ink planes (a CMYK document that shows transparency), RGB and Lab
  points mix first and then separate.

Gray mixes keep their values, and CMYK and Gray points on ink planes draw
as before. RGB points in an sRGB working space draw as before except on
ink planes.

- PDF and EPS write a gradient whose points are all CMYK, or all Gray in a
  CMYK file, as a CMYK image. PDF's Output settings convert it as they
  convert any CMYK image.
- Darkening applies to the points before they mix.
- With Appearance of Black set to Rich, the canvas mixes a gradient with a
  pure CMYK black point in display RGB, and the point fades from RGB black.
2026-11-08 17:18:43 -08:00
dotCooCoo 83248b9bb4 SVG Filters: export writes a filter live only when import can read it back
SVG export wrote an SVG filter as a live <filter> when its SVG 1.1 form
was at most 64 KB, without counting the region and id it writes into
it. A filter close to the limit came back over it, SVG import dropped
it, and the reopened object had no filter.

Export now leaves room for the id and the measured region, checks the
SVG 1.1 form the way import does, and counts each character past U+00FF
as the character reference Latin-1 output writes. A filter that does not
fit is exported as the other pixel effects are. The SVG 1.1 form and its
check are kept for the filters seen last, as checked filters already
are, so export does not check one filter again for each object.
2026-11-09 04:19:44 +07:00
dotCooCoo 0e7bd10033 SVG export: outlined type keeps its text as an accessible name
Type that SVG text can't draw is written as outlines: vertical type, ruby,
right-to-left text, type on a path with an effect other than Rainbow, an end
bracket, Align to Path, Spacing, hanging punctuation, bullets or inline
graphics. Only glyphs bent along a path gave the outlined group a
`role="img"` and `aria-label`; every other case left screen readers and
search with no text. Each of them now names the group with its text.
Convert to Outlines still writes the paths alone, as before.

Inline graphics' placeholder characters stay out of the name, and empty
type gets none. An object whose id is its name also gets `data-name`, so
opening the file again keeps the name instead of taking the label.
2026-11-09 04:19:41 +07:00
dotCooCoo 7b8d5ba8f5 Type: Tab and Shift+Tab move a list item down and up a level
Squashed from #5037 onto main.
2026-11-09 03:39:05 +07:00
dotCooCoo fd91b29e20 SVG export: type on a path with Skew, 3D Ribbon, Stair Step or Gravity keeps its look
SVG viewers center each <textPath> glyph on its midpoint on the path and
turn it to follow the path, which is how the Rainbow effect draws. Type on
a path with any other effect and no spacing was written as a plain
<textPath>, so a viewer drew it as Rainbow. It is now written as outlines
with a warning, as type with an end bracket, Align to Path or Spacing
already is.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-09 00:34:39 +07:00
dotCooCoo cca9a42c6c Effects: Effect › SVG Filters applies the document's SVG filters as live effects
Squashed from #6200 onto main.
2026-11-09 00:34:23 +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 e29e01b650 SVG import: each linked file keeps image keys of its own
Images were keyed by their bytes alone, so two <image> elements that
link different files with the same bytes shared one image key. Every
missing file shows the same placeholder, and files can also be copies
of each other. Export reads a linked file's pixels into the image key
when the document holds only a preview, so once one of the files was
found or changed, the images of the other file exported and printed
that file's pixels.

A linked image's key now also holds a hash of its link's path, as the
.ai reader's linked images do. Images of one file still share a key.
The missing-file placeholder comes from
vectorcraft_doc::links::missing_placeholder instead of a copy in the
SVG importer.
2026-11-08 12:23:53 +07:00
vangel76andClaude Opus 5.5 8b85809a0a Write imported classes in the SVG Tiny 1.2 profile too
SVG Tiny 1.2 lists class among its core attributes, so the Tiny export no longer drops an object's own classes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 06:21:44 +01:00
vangel76andClaude Sonnet 5.5 128d362730 Merge upstream/main into fix/2414-svg-classes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 16:21:40 +01:00
Kyle Kanouse 86564528d3 SVG: centred and right-aligned type on a path keeps its place
Squashed from #5977 onto main.
2026-11-07 05:15:17 +07:00
Don Omondi f08381df8d SVG import: filter and mask regions clip what they declare (#5876, #5875)
Squashed from #5913 onto main.
2026-11-06 16:39:04 +07:00
Don Omondi 41ceaadc7e SVG import: Gaussian blurs keep color-interpolation-filters; Gaussian Blur can blur in linear light (#5877) 2026-11-06 16:05:55 +07:00
Don Omondi 427ec0017c SVG import: gradients with color-interpolation="linearRGB" mix their stops in linear light (#5878) 2026-11-06 15:33:02 +07:00
Kushagra Kumar 316f0d6213 SVG import: clip path on clipPath children clips child silhouettes before union (#5841)
Squashed from #5855 onto main.
2026-11-06 07:23:31 +07:00
Don Omondi 8236a8e006 SVG import: textLength and lengthAdjust fit text to its length (#5842)
SVG text with a textLength came in at its natural width. Following
SVG 1.1 §10.9.1, the characters of each text or tspan with a textLength
are now fitted to it: by spacing them out (lengthAdjust="spacing", the
default), which becomes kerning, or by stretching them
(spacingAndGlyphs), which becomes horizontal scale (kept within
1–10000%). Inner elements are fitted first; vertical text is spaced out
only, with a warning for spacingAndGlyphs. Negative lengths are ignored.
2026-11-06 05:38:51 +07:00
Don Omondi 446c3f071f SVG import: a clipPath's children clip to their union, each under its own clip-rule (#5840)
A clipPath with several children was imported as one compound path under
the first child's fill rule, so children wound in opposite directions
cancelled where they overlapped, and an even-odd child after a non-zero
one lost its hole. SVG 1.1 §14.3.5 defines the clip as the union of the
children's silhouettes, each computed on its own.

A clipPath with several children now comes in as a group of them as the
clip group's clipping path, each child keeping its own rule; a group
clips to the union of its members. One child imports as before.
2026-11-06 05:32:19 +07:00
vangel76andClaude Sonnet 5.5 1601459bb0 Merge upstream/main into fix/2414-svg-classes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 06:27:13 +01:00
dotCooCoo 88b5170622 Glyphs panel: alternates for the current text selection
Squashed from #5442 onto main.
2026-11-04 22:29:53 +07:00
Dylan M. Taylor 0086c85bc7 fix(svg): glyphs keep only typed raster data URLs and no links 2026-11-03 23:29:09 +07:00
dotCooCoo b751dce8e3 SVG import preserves selector matches while retaining metadata 2026-11-03 21:09:57 +07:00
dotCooCoo 449dd5f5ed Preserve SVG styles with encoded attribute selectors 2026-11-03 16:03:40 +07:00
Brandon Thomas c6131cf798 Merge pull request #5243 from dotCooCoo/fix/svg-round-trip-edge-ellipse
SVG: an exported ellipse imports as the four-anchor ellipse it was written from
2026-11-03 00:18:50 -05:00
NetterSilverandClaude Haiku 5.5 6ecf286f6b Drop invalid XML characters from SVG link URLs and aria-labels
Found while testing crates/svg/src/export/nodes.rs:492 (Writer::node, `xlink:href` built with xml_escape)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 21:42:27 +00:00
vangel76andClaude Sonnet 5.5 e3e49e4f5d Merge upstream/main into fix/2414-svg-classes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 20:23:08 +01:00
dotCooCoo 442ae20e14 SVG: made-up ids skip every id the file names
An id the importer writes into an element no longer matches a style sheet's
#id selector or a <use> link that no element matched before. The ids are
read from the decoded attribute values and text of the file, once.
2026-11-02 03:22:10 -08:00
dotCooCoo 7c7a127ec8 SVG: a thin ellipse far from the origin imports with standard handles
An ellipse whose radius is smaller than the point tolerance at its coordinates, but at least two
f32 steps, now imports as the four-anchor ellipse instead of usvg's curves with rounded handles.
2026-11-02 03:09:08 -08:00
dotCooCoo 85b6c2a5eb SVG: ids made up on import never repeat an id in the file
The import gives an element an id when it needs to find it again in
usvg's tree (links, symbol uses, events, data attributes, and now
circles and ellipses). A made-up id could repeat one the file already
had, such as vectorcraft-id-0, so a <use> or paint reference to that
element showed the other one. Made-up ids now skip every id in the file.

Circles and ellipses in a file whose style sheet has an [id selector get
no made-up id and keep usvg's curves, so the selector matches the same
elements as before.
2026-11-02 02:59:51 -08:00
dotCooCoo 820a750310 SVG: a small circle far from the origin imports as a four-anchor ellipse
The tolerance that compares the points usvg makes of a <circle> or
<ellipse> grew with the center's distance from the origin, so a circle
of radius 400 at x 40,000,000 fell back to eight anchors. The tolerance
is now a few f32 steps at the size of the coordinates, and the on-ellipse
check allows the same steps relative to the smaller radius.
2026-11-02 02:53:34 -08:00
dotCooCoo 60e1600caa SVG: circles and ellipses import as four-anchor ellipses
usvg turns each <circle> and <ellipse> into arcs and then into a number
of cubics that depends on f32 rounding: an ellipse that export wrote as
<ellipse> could come back with eight anchors instead of the four it was
written from. The renderer flattens the two outlines differently, so a
round trip of a flat filled ellipse redrew 158 edge pixels by up to 43
levels, and prop_svg::plain_art_survives_visually failed on main.

Import now records the ids of <circle> and <ellipse> elements in the
prepass and rebuilds each as the ellipse VectorCraft draws: four smooth
anchors on the axes, starting at the right as SVG defines, with handles
KAPPA of the radii long, then transformed as before.

Tests: prop_svg::filled_ellipse_on_the_artboard_edge_survives_visually
(the failing case) and basic_shapes::circles_and_ellipses_import_as_the_ellipse_drawn
fail without the change.
2026-11-02 02:49:36 -08: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
Prin.IngandClaude Haiku 5.5 743f8c526b Filter invalid XML characters from SVG Dublin Core metadata
Found while testing crates/svg/src/export/output.rs:360 (dublin_core, around line 81)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 14:36:13 +07:00
vangel76andClaude Sonnet 5.5 6241ef6e2b Merge upstream/main into fix/2414-svg-classes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 14:56:38 +01: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
bflatastic f22caa6108 Fix #4938: Dynamic symbols keep per-instance appearance linked to the symbol 2026-11-01 04:53:38 +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
SusieeTheFemboyandGLM-5.3-Flash c7dc443ee1 SVG import: a feConvolveMatrix order that overflows usvg's multiply is refused
usvg 0.48 casts the filter's order attribute through i32, so a value like
1066570220609 saturates and the u32 order_x * order_y multiply overflows.
Only debug and overflow-checks builds panic on that; release builds already
import the file — the multiply wraps, and VectorCraft drops the filter as
an unsupported primitive with a warning, so nothing reaches the document,
render or export (observed on main in a --release build: both repro files
import without error; with a one-entry kernelMatrix the wrapped product
2147483647² mod 2³² = 1 even passes usvg's length check and leaves a filter
claiming a 2147483647×2147483647 kernel, still dropped downstream).

The import now parses order the way usvg does (svgtypes numbers, the same
as-i32 cast with its saturation, the first side defaulting the second, both
positive or both back to the 3×3 default) and refuses exactly the one case:
when the product of the two sides overflows u32 (checked_mul). Any order
usvg can multiply keeps importing as before, however large on its own, and
a missing, unparseable or non-positive order still falls back to 3.

Regression tests cover the overflow repros, the release wrap case, large
but fitting orders (100, 80 80, 65535 65535, 2147483647 1), the
pass-through shapes, and sane kernels, in debug and in release.

Co-Authored-By: GLM-5.3-Flash <noreply@z.ai>
2026-10-31 19:43:17 +07:00
Don Omondi 9259434a89 SVG export: a gradient mesh patch is cut no finer than its colour changes
Each patch of a gradient mesh was cut into 8 x 8 flat quads for SVG export,
Object > Expand and the other outputs that cannot shade a mesh, whatever its
colours, so a patch of one colour became 64 identical pieces. Neighbouring
quads are now joined into square blocks wherever the colour changes by no
more than about one 8-bit step across the block; the block's outline runs
through its quads' corners, and a patch that folds over itself is not joined.
2026-10-31 19:43:16 +07:00
vangel76andCodex 86e5f80aae Merge upstream/main into fix/2414-svg-classes
Preserve SVG class metadata together with upstream basic-shape export helpers and current file-format evidence.

Co-Authored-By: Codex <noreply@openai.com>
2026-10-31 11:48:25 +01:00
bflatastic beb0ae6515 SVG export: rectangles, circles, ellipses, lines and straight paths as SVG shape elements (#4652) 2026-10-30 09:45:59 +07:00
vangel76andClaude Sonnet 5.5 42dd57d439 Merge upstream/main into fix/2414-svg-classes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 21:12:34 +01:00
el-j dc4150d7db perf(svg): compute unique ID prefix via single-pass placeholder replacement (#4555) 2026-10-29 22:12:21 +07:00
Ridzzz0Alam e110c6c770 Fix #3209: SVG export writes type sizes and spacing in CSS with px
With Styling set to Internal CSS, Inline Style or Style Entities, font-size
and letter-spacing were written as bare numbers. Only presentation attributes
take a bare number; in CSS a browser drops them, so the type was drawn at its
default size. They are now written in px, SVG's user unit, and presentation
attributes keep bare numbers.
2026-10-29 22:12:07 +07:00
Max MittelstädtandGPT-6-Luna 2b37302fe5 Preserve comment markers inside CSS strings
Found while testing crates/svg/src/import/css.rs:346

Co-Authored-By: GPT-6-Luna <noreply@openai.com>
2026-10-29 18:45:51 +07:00
dotCooCoo dfa9f883c0 Fix Drop Shadow Darkness in source color space 2026-10-29 14:36:41 +07:00