diff --git a/crates/usvg/src/parser/image.rs b/crates/usvg/src/parser/image.rs index 35a908b9b..21e8cc626 100644 --- a/crates/usvg/src/parser/image.rs +++ b/crates/usvg/src/parser/image.rs @@ -277,6 +277,21 @@ pub(crate) fn convert_inner( g2.children.push(Node::Group(Box::new(g))); g2.calculate_bounding_boxes(); + // With `slice`, the image is scaled to *cover* the viewport and the + // overflow is cut away by the rectangular clip above. The bounding box + // of the element is therefore its `rect` (the viewport), not the + // larger scaled-to-cover image that `calculate_bounding_boxes` derives + // from the children. Without this correction an `objectBoundingBox` + // clip/mask/filter applied to the `` would be resolved against + // the oversized image. See https://github.com/linebender/resvg/issues/1034 + let bbox = rect.to_rect(); + g2.bounding_box = bbox; + g2.stroke_bounding_box = bbox; + if let Some(abs) = bbox.transform(g2.abs_transform) { + g2.abs_bounding_box = abs; + g2.abs_stroke_bounding_box = abs; + } + parent.children.push(Node::Group(Box::new(g2))); } else { parent.children.push(Node::Group(Box::new(g))); diff --git a/crates/usvg/tests/parser.rs b/crates/usvg/tests/parser.rs index 905bbc0db..4aadc4ec2 100644 --- a/crates/usvg/tests/parser.rs +++ b/crates/usvg/tests/parser.rs @@ -601,3 +601,45 @@ fn flattened_text_should_inherit_absolute_transform() { path.abs_bounding_box() ); } + +#[test] +fn image_slice_object_bounding_box_clip() { + // Regression test for https://github.com/linebender/resvg/issues/1034 + // + // An `` with `preserveAspectRatio="... slice"` is scaled to *cover* + // its viewport and clipped to it. The element's bounding box is therefore + // its `x/y/width/height` rect, not the larger scaled-to-cover image. An + // `objectBoundingBox` clip-path applied to such an image must be resolved + // against that rect, otherwise its shape collapses. + // + // Here the intrinsic image is 120x10 (aspect 12:1) sliced into a 30x60 + // viewport at (10, 20), so the scaled-to-cover image is 720x60. The clip + // transform must be `matrix(30 0 0 60 10 20)` (the viewport), *not* + // `matrix(720 0 0 60 -335 20)` (the oversized image). + let svg = "\ + \ + \ + \ + \ + "; + + let tree = usvg::Tree::from_str(svg, &usvg::Options::default()).unwrap(); + + let clip = tree + .clip_paths() + .iter() + .find(|c| c.id() == "cp") + .expect("objectBoundingBox clip 'cp' should exist"); + + let ts = clip.transform(); + assert!( + (ts.sx - 30.0).abs() < 1e-3 + && (ts.sy - 60.0).abs() < 1e-3 + && (ts.tx - 10.0).abs() < 1e-3 + && (ts.ty - 20.0).abs() < 1e-3, + "clip resolved against the wrong bounding box: {ts:?}" + ); +}