From 8c3bbdc09f6b8f40e43795397abfc413ebd72ed1 Mon Sep 17 00:00:00 2001 From: aiongg Date: Fri, 16 Oct 2026 16:48:00 +0000 Subject: [PATCH] Read images inside SVGs only from data: URLs --- crates/images/src/lib.rs | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/crates/images/src/lib.rs b/crates/images/src/lib.rs index 24eeedb5f..2bed1c483 100644 --- a/crates/images/src/lib.rs +++ b/crates/images/src/lib.rs @@ -72,7 +72,11 @@ fn svg_options() -> &'static usvg::Options<'static> { db.set_sans_serif_family("Source Sans 3"); db.set_monospace_family("JetBrains Mono"); let font_resolver = usvg::FontResolver { select_font: select_font(), select_fallback: select_fallback() }; - usvg::Options { fontdb: Arc::new(db), font_family: "Source Sans 3".into(), font_resolver, ..Default::default() } + // Images inside an SVG come only from `data:` URLs: an `href` naming a file is not read + // (it is left out, as usvg leaves out an image it can't load). + let image_href_resolver = + usvg::ImageHrefResolver { resolve_data: usvg::ImageHrefResolver::default_data_resolver(), resolve_string: Box::new(|_, _| None) }; + usvg::Options { fontdb: Arc::new(db), font_family: "Source Sans 3".into(), font_resolver, image_href_resolver, ..Default::default() } }) } @@ -510,6 +514,39 @@ mod tests { assert!(!is_svg(b"\x89PNG")); } + #[test] + fn svg_images_come_only_from_data_urls() { + let dir = std::env::temp_dir().join(format!("dc-svg-href-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let file = dir.join("red.png"); + image::RgbaImage::from_pixel(4, 4, image::Rgba([255, 0, 0, 255])).save(&file).unwrap(); + let blue = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Crect width='10' height='10' fill='blue'/%3E%3C/svg%3E"; + let svg = format!( + r#" + + + +"#, + file.display() + ); + // The image nodes usvg kept: only the data URL's (PDF export embeds every one). + fn images(g: &usvg::Group) -> usize { + g.children() + .iter() + .map(|n| match n { + usvg::Node::Group(g) => images(g), + usvg::Node::Image(_) => 1, + _ => 0, + }) + .sum() + } + assert_eq!(images(svg_tree(svg.as_bytes()).unwrap().root()), 1, "files the SVG names are not read"); + let (px, w, _) = render_svg(svg.as_bytes(), 30).unwrap(); + let at = |x: usize, y: usize| px[(y * w as usize + x) * 4..][..4].to_vec(); + assert_eq!(at(15, 5), [0, 0, 255, 255], "a data URL is drawn"); + let _ = std::fs::remove_dir_all(&dir); + } + /// The family of the font that sets each text glyph of `svg`, and the glyph's id. fn glyph_families(svg: &str) -> Vec<(String, u16)> { fn walk(g: &usvg::Group, tree: &usvg::Tree, out: &mut Vec<(String, u16)>) {