From 40d0b31cbe4de180f99d61c5abc82371aca4c13d Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 14 Nov 2026 08:54:44 -0800 Subject: [PATCH] Effects: the clipping path doc comment says what the member limit leaves of a compound path When the member limit is reached among the paths of a compound clipping path that has geometry effects of its own, the compound path still becomes one path of its reshaped geometry. The paths that were not visited go into that geometry as they are, and their own effects are not applied. The doc comment of with_clip_effects_applied_while said that they keep their effects, which holds for the members of a group or layer only. New test a_compound_clipping_path_takes_its_own_effect_at_the_member_limit covers the compound path. --- crates/effects/src/clip.rs | 5 ++++- crates/effects/src/group/tests.rs | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) 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.