diff --git a/crates/svg/src/glyph.rs b/crates/svg/src/glyph.rs index 5b7753432..4762bfd9d 100644 --- a/crates/svg/src/glyph.rs +++ b/crates/svg/src/glyph.rs @@ -17,7 +17,7 @@ use crate::ImportOptions; const MAX_SVG_GLYPH_BYTES: u64 = 1 << 20; /// Elements left out of a glyph, as the `SVG ` table rules them out: text (type inside type), -/// embedded content and scripts. +/// embedded content and scripts (and images of SVG documents, in [`glyph`]). const LEFT_OUT: [&str; 3] = ["text", "foreignObject", "script"]; /// Glyph `gid`'s art from its SVG `document` (possibly gzipped), in a font of `upem` units per em: @@ -98,7 +98,14 @@ fn glyph(svg: &str, id: &str, color: &str) -> Option { let at = target.range(); // Edits: byte ranges to cut and what to put in their place, in order. let mut edits: Vec<(Range, String)> = vec![]; - for n in root.descendants().filter(|n| n.is_element() && LEFT_OUT.contains(&n.tag_name().name())) { + // Cut with the left-out elements: images of SVG documents, as the table asks (a glyph draws no + // documents inside it). + let svg_image = |n: &roxmltree::Node| { + n.tag_name().name() == "image" + && n.attributes() + .any(|a| a.name() == "href" && a.value().trim_start().get(..14).is_some_and(|v| v.eq_ignore_ascii_case("data:image/svg"))) + }; + for n in root.descendants().filter(|n| n.is_element() && (LEFT_OUT.contains(&n.tag_name().name()) || svg_image(n))) { edits.push((n.range(), String::new())); } for a in root.attributes().filter(|a| matches!(a.name(), "width" | "height" | "color")) { diff --git a/crates/svg/tests/svg_glyphs.rs b/crates/svg/tests/svg_glyphs.rs index bdd621961..378169699 100644 --- a/crates/svg/tests/svg_glyphs.rs +++ b/crates/svg/tests/svg_glyphs.rs @@ -211,3 +211,22 @@ fn images_are_in_the_art_with_their_pixels() { let b = art.geometric_bounds().unwrap(); assert!(b.x0.abs() < 1e-6 && (b.width() - 500.0).abs() < 1e-6 && (b.y0 + 500.0).abs() < 1e-6, "in font units: {b:?}"); } + +/// An image whose data is an SVG document is left out, as the `SVG ` table asks: a glyph draws no +/// documents inside it. +#[test] +fn svg_images_are_left_out() { + let inner = r##""##; + let doc = format!( + r##" + + + + "##, + vectorcraft_svg::base64_encode(inner.as_bytes()), + vectorcraft_svg::xml_escape(&inner.replace('#', "%23")) + ); + let fills = fills(&art(&doc, 1).unwrap()); + assert!(fills.contains(&Paint::solid(Color::rgb8(0, 0, 255))), "the glyph's own art"); + assert!(!fills.contains(&Paint::solid(Color::rgb8(255, 0, 0))), "not the images' documents: {fills:?}"); +}