From deb517fbbe43fe8ab7243bf2d3ce14f275afede4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rg=20Lehni?= Date: Sat, 24 Oct 2026 02:05:34 +0200 Subject: [PATCH] Type: an SVG glyph's image is an SVG image by its plain href first The check for images of SVG documents looked at href and xlink:href alike, so an image whose href is a PNG was cut for an SVG xlink:href; it now reads href, else xlink:href, as the importer does (SVG 2). --- crates/svg/src/glyph.rs | 7 +++---- crates/svg/tests/svg_glyphs.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/crates/svg/src/glyph.rs b/crates/svg/src/glyph.rs index 4762bfd9d..601ca2e63 100644 --- a/crates/svg/src/glyph.rs +++ b/crates/svg/src/glyph.rs @@ -99,11 +99,10 @@ fn glyph(svg: &str, id: &str, color: &str) -> Option { // Edits: byte ranges to cut and what to put in their place, in order. let mut edits: Vec<(Range, String)> = vec![]; // Cut with the left-out elements: images of SVG documents, as the table asks (a glyph draws no - // documents inside it). + // documents inside it). Their link is `href`, else `xlink:href`, as the importer reads 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"))) + let href = n.attribute("href").or_else(|| n.attribute(("http://www.w3.org/1999/xlink", "href"))); + n.tag_name().name() == "image" && href.and_then(|h| h.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())); diff --git a/crates/svg/tests/svg_glyphs.rs b/crates/svg/tests/svg_glyphs.rs index 378169699..c9956d6b8 100644 --- a/crates/svg/tests/svg_glyphs.rs +++ b/crates/svg/tests/svg_glyphs.rs @@ -230,3 +230,30 @@ fn svg_images_are_left_out() { 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:?}"); } + +/// Of `href` and `xlink:href`, the plain one counts, as the importer reads them (SVG 2): an image +/// whose `href` is a PNG stays, whatever its `xlink:href`; one whose `href` is a document goes. +#[test] +fn an_images_plain_href_decides_whether_it_is_an_svg_image() { + let png = vectorcraft_svg::base64_encode(&vectorcraft_text::test_fonts::solid_png(4, [0, 0, 255, 255])); + let svg = vectorcraft_svg::base64_encode( + br##""##, + ); + let image = |href: &str, xlink: &str| { + format!( + r##""##, + if href == "png" { &png } else { &svg }, + if xlink == "png" { &png } else { &svg } + ) + }; + let images = |inner: String| { + let doc = format!( + r##"{inner}"## + ); + let mut n = 0; + art(&doc, 1).unwrap().walk(&mut |c| n += usize::from(matches!(c.kind, NodeKind::Image(_)))); + n + }; + assert_eq!(images(image("png", "svg+xml")), 1, "the PNG stays"); + assert_eq!(images(image("svg+xml", "png")), 0, "the document goes"); +}