diff --git a/crates/svg/src/import.rs b/crates/svg/src/import.rs index a3529278..4deefef7 100644 --- a/crates/svg/src/import.rs +++ b/crates/svg/src/import.rs @@ -965,8 +965,9 @@ impl Importer { nodes = rest; } let mut children: Vec> = nodes.iter().filter_map(|c| self.node(c, ts)).map(Arc::new).collect(); + // Mask art that is all hidden draws nothing: that is an empty mask, not no mask (black all + // over when clipped, white when not, inverted when the mask is). let art = match children.len() { - 0 => return None, 1 => Arc::unwrap_or_clone(children.pop()?), _ => self.named("", NodeKind::Group { children, clip: false }), }; diff --git a/crates/svg/tests/mask_options.rs b/crates/svg/tests/mask_options.rs index ab9a1518..9084aeb3 100644 --- a/crates/svg/tests/mask_options.rs +++ b/crates/svg/tests/mask_options.rs @@ -8,6 +8,7 @@ use vectorcraft_color::{Color, Paint}; use vectorcraft_doc::{Appearance, Document, Node, OpacityMask}; use vectorcraft_geom::{Rect, shapes}; use vectorcraft_svg::{ExportOptions, export, import_with_report}; +use vectorcraft_testkit::raster::{Image, render_artboard}; /// A red square masked by white art over its left half. fn masked(clip: bool, invert: bool) -> Document { @@ -67,3 +68,37 @@ fn foreign_masks_keep_their_art() { assert!(m.clip && !m.invert); assert_eq!(m.art.children().map(|c| c.len()), Some(2)); } + +/// `svg` drawn by resvg, `w`×`h` on white. +fn resvg_render(svg: &str, w: u32, h: u32) -> Image { + let tree = resvg::usvg::Tree::from_str(svg, &resvg::usvg::Options::default()).expect("parse"); + let mut pm = resvg::tiny_skia::Pixmap::new(w, h).unwrap(); + pm.fill(resvg::tiny_skia::Color::WHITE); + resvg::render(&tree, resvg::tiny_skia::Transform::identity(), &mut pm.as_mut()); + Image { width: w, height: h, rgba: pm.data().to_vec() } +} + +#[test] +fn a_mask_whose_art_is_all_hidden_hides_its_object() { + // Hidden mask art draws nothing: the mask is black all over and the red square doesn't show, + // as resvg draws the file. The square keeps its (empty) mask, so it can still be edited. + for art in [ + r#""#, + r#""#, + ] { + let svg = format!( + r#" + {art} + "# + ); + let (d, _) = import_with_report(&svg).unwrap(); + let [m] = &masks(&d)[..] else { panic!("the square keeps its mask: {svg}") }; + assert!(m.clip && !m.invert); + assert!(m.art.geometric_bounds().is_none(), "no visible art: {:?}", m.art.kind); + let (canvas, reference) = (render_artboard(&d), resvg_render(&svg, 100, 100)); + for (x, y) in [(10, 10), (50, 50), (90, 90)] { + assert_eq!(reference.over_white(x, y), [255, 255, 255], "resvg ({x},{y}): {svg}"); + assert_eq!(canvas.over_white(x, y), [255, 255, 255], "canvas ({x},{y}): {svg}"); + } + } +}