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.
This commit is contained in:
dotCooCoo
2026-11-11 01:04:34 -08:00
parent 9f44e37274
commit afdaa47cd5
8 changed files with 771 additions and 39 deletions
+20
View File
@@ -663,6 +663,26 @@ proptest! {
survive(&svg, || vectorcraft_svg::import(&svg).ok())?;
}
/// Clip paths made of text, of `<use>` elements showing text, and of both with shapes, each with
/// hostile attributes, clipping art that may clip by them in turn.
#[test]
fn svg_text_clip_paths_never_panic(
text in prop::collection::vec(arb_svg_attr(), 0..5),
used in prop::collection::vec(arb_svg_attr(), 0..5),
clip in prop::collection::vec(arb_svg_attr(), 0..3),
body in prop::collection::vec(arb_svg_element(1), 0..3),
) {
let svg = format!(
"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"><defs><text id=\"t\" font-size=\"20\"{}>ab<tspan dy=\"3\">c</tspan></text></defs><clipPath id=\"e\"{}><text y=\"30\"{}>Clip<tspan x=\"5\" y=\"60\">it</tspan></text><use href=\"#t\"{}/><rect width=\"5\" height=\"5\"/></clipPath><clipPath id=\"f\" clip-path=\"url(#e)\"><use xlink:href=\"#t\" x=\"9\"/></clipPath><rect width=\"90\" height=\"90\" clip-path=\"url(#e)\"/><g clip-path=\"url(#f)\">{}</g></svg>",
text.concat(),
clip.concat(),
text.concat(),
used.concat(),
body.concat()
);
survive(&svg, || vectorcraft_svg::import(&svg).ok())?;
}
#[test]
fn svg_mutated_export_never_panics(
cut in 0usize..20_000,