diff --git a/crates/effects/src/clip.rs b/crates/effects/src/clip.rs index bcb2dd0995..d04a60b70a 100644 --- a/crates/effects/src/clip.rs +++ b/crates/effects/src/clip.rs @@ -36,7 +36,10 @@ pub(crate) fn with_clip_effects_applied(n: &Node) -> Option { /// [`with_clip_effects_applied`] that calls `take` before it visits each member of a group, layer /// or compound path in `n`, at every depth: once `take` returns `false`, that member and the -/// members after it stay as they are, with their effects. +/// members after it are not visited. The members of a group or layer that are not visited stay as +/// they are, with their effects. A compound path with geometry effects of its own, or of its fills +/// and strokes, still becomes a path of its reshaped geometry: the paths in it that are not visited +/// go into that geometry as they are, and their own effects are not applied. pub(crate) fn with_clip_effects_applied_while(n: &Node, take: fn() -> bool) -> Option { let paint = n.clip_paint(); clip_effects_applied(n, paint.fill.is_none() && paint.stroke.is_none(), take) diff --git a/crates/effects/src/group/tests.rs b/crates/effects/src/group/tests.rs index 8b8fda5727..519323e5f0 100644 --- a/crates/effects/src/group/tests.rs +++ b/crates/effects/src/group/tests.rs @@ -871,6 +871,31 @@ fn clipping_path_members_past_the_member_limit_stay_as_they_are() { assert!((widths[0] - 120.0).abs() < 0.5 && (widths[1] - 120.0).abs() < 0.5 && (widths[2] - 100.0).abs() < 0.5, "{widths:?}"); } +/// A compound clipping path with an effect of its own takes that effect when the member limit is +/// reached among its paths: with room for one more member, the first path (at 100 to 200) takes +/// its Offset Path, the second (at 300 to 400) goes in as it is, and the compound path's Offset +/// Path reshapes both, to 80 to 220 and 290 to 410. +#[test] +fn a_compound_clipping_path_takes_its_own_effect_at_the_member_limit() { + let member = |id: u64, x: f64| { + let path = Node::path(NodeId(id), shapes::rectangle(Rect::new(x, 100.0, x + 100.0, 200.0)), black()); + Arc::new(with_effect(path, offset_10())) + }; + let kind = NodeKind::Compound { children: vec![member(2, 100.0), member(3, 300.0)], rule: FillRule::NonZero }; + let clipping = with_effect(Node::new(NodeId(20), kind), offset_10()); + let mut clipped = Node::new(NodeId(5), NodeKind::Group { children: vec![Arc::new(clipping), Arc::new(square_at_100())], clip: true }); + // An enclosing call that has visited all the members it may but one. + let _outer = DrawnFrame::enter().unwrap(); + DRAWN.with(|d| d.set((1, crate::SYMBOL_ART_MAX_NODES - 1))); + members_as_drawn(None, &mut clipped); + let clipping = clipped.children().unwrap()[0].clone(); + assert!(crate::reshape::geometry_effects(&clipping).is_empty(), "the compound path's Offset Path is in its geometry"); + let subpaths = clipping.path_data().unwrap().subpaths.clone(); + let bounds: Vec<(f64, f64)> = subpaths.into_iter().map(|sp| PathData::single(sp).bounds().unwrap()).map(|b| (b.x0, b.x1)).collect(); + let near = |(a, b): (f64, f64), (x0, x1): (f64, f64)| (a - x0).abs() < 0.5 && (b - x1).abs() < 0.5; + assert!(bounds.len() == 2 && near(bounds[0], (80.0, 220.0)) && near(bounds[1], (290.0, 410.0)), "{bounds:?}"); +} + /// An image or a symbol instance that a group's Transform effect moves takes the placed gradients /// of its own fills along, as a path does, and each copy takes them as many times as it takes the /// transform.