diff --git a/crates/effects/src/group.rs b/crates/effects/src/group.rs index 0e37be9ded..d2a6687dd7 100644 --- a/crates/effects/src/group.rs +++ b/crates/effects/src/group.rs @@ -832,13 +832,15 @@ pub fn evaluate_container(doc: Option<&Document>, n: &Node) -> Option { return None; } let mut m = n.clone(); - // Its ruler guides take no part: they go back above the evaluated art at the end. - let mut guides = m.children_mut().map(|ch| ch.split_off(Node::guide_run_start(ch))).unwrap_or_default(); - // So do its other guides and those in its sublayers and groups, which would otherwise go with - // the art the effects replace or copy: they stay on `n`, hidden and locked as their containers - // were. + // Its ruler guides take no part: they go back above the evaluated art at the end. With effects + // that replace or copy the art, so do its guides among the art and those in its sublayers and + // groups, which would otherwise go with that art: they stay on `n`, in order, hidden and + // locked as their containers were. + let mut guides = vec![]; if pathfinder || !fx.is_empty() { take_sublayer_guides(&mut m, &mut guides, &Node::is_ruler_guide); + } else if let Some(ch) = m.children_mut() { + guides = ch.split_off(Node::guide_run_start(ch)); } if pathfinder { let hook = |c: &Node| crate::outline_object(doc, c); diff --git a/crates/effects/src/group/tests.rs b/crates/effects/src/group/tests.rs index b2b802cc44..50fbf8f759 100644 --- a/crates/effects/src/group/tests.rs +++ b/crates/effects/src/group/tests.rs @@ -1998,6 +1998,35 @@ fn a_ruler_guide_in_a_group_survives_its_containers_effects() { } } +/// The ruler guides of a layer or group with a Pathfinder effect or Transform copies, one among +/// its art and one after it, go after the evaluated art in the order they were in. +#[test] +fn a_containers_ruler_guides_keep_their_order_through_its_effects() { + use vectorcraft_doc::LayerColor; + let guide = |id| Arc::new(Node::new(NodeId(id), NodeKind::RulerGuide { vertical: false, pos: 3.0, artboard: None })); + for (effect, params) in [("pathfinder.add", serde_json::json!({})), ("distort.transform", serde_json::json!({"moveH": 30, "copies": 2}))] { + let members = || vec![sq(1, 0.0, Color::BLACK), guide(20), sq(2, 5.0, Color::BLACK), guide(21)]; + let fx = Effect { id: effect.into(), params: params.clone(), visible: true }; + let mut l = Node::layer(NodeId(10), "Layer 1", LayerColor::Preset(0)); + if let Some(ch) = l.children_mut() { + *ch = members(); + } + l.appearance.effects.push(fx.clone()); + let mut g = Node::group(NodeId(11), members()); + g.appearance.effects.push(fx); + for (what, n) in [("layer", l), ("group", g)] { + let art = evaluate_container(None, &n).unwrap(); + let mut all = vec![]; + ruler_guides_in(&art, &mut all); + let ids = all.iter().map(|(id, ..)| *id).collect::>(); + assert_eq!(ids, [NodeId(20), NodeId(21)], "{effect} on a {what}: the guides, in order"); + let ch = art.children().unwrap(); + let tail = ch[ch.len() - 2..].iter().map(|c| c.id).collect::>(); + assert_eq!(tail, [NodeId(20), NodeId(21)], "{effect} on a {what}: last, in order"); + } + } +} + /// A group's own Fill + Offset Path below its contents follows the outline of a clip group among /// its members, its clipping path, not the clipped art it hides outside that path (#7289). #[test]