diff --git a/crates/engine/src/interchange/vector.rs b/crates/engine/src/interchange/vector.rs index b52517cb3..569e8e5bc 100644 --- a/crates/engine/src/interchange/vector.rs +++ b/crates/engine/src/interchange/vector.rs @@ -477,35 +477,51 @@ pub fn split_at_images(doc: &Doc) -> Vec { }) .sum() } - /// Keep the shapes between images `seg − 1` and `seg`; `seen` counts images passed. - fn prune(g: &effectcraft_svg::Group, seg: usize, seen: &mut usize) -> effectcraft_svg::Group { - let mut out = g.clone(); - out.children = vec![]; + /// `g` without its children, holding `children` instead (nothing below `g` is copied). + fn shell(g: &effectcraft_svg::Group, children: Vec) -> effectcraft_svg::Group { + effectcraft_svg::Group { + name: g.name.clone(), + transform: g.transform, + opacity: g.opacity, + children, + clip: g.clip.clone(), + blend: g.blend, + mask: g.mask.clone(), + isolated: g.isolated, + knockout: g.knockout, + } + } + /// One traversal for every segment: the pieces of `g` as (segment, group), by ascending + /// segment; `seen` counts images passed. Each shape is copied once, into its own segment. + fn split(g: &effectcraft_svg::Group, seen: &mut usize) -> Vec<(usize, effectcraft_svg::Group)> { + let mut by: Vec<(usize, Vec)> = vec![]; + let mut put = |seg: usize, node: Node| match by.last_mut() { + Some(last) if last.0 == seg => last.1.push(node), + _ => by.push((seg, vec![node])), + }; for c in &g.children { match c { Node::Image(_) => *seen += 1, - Node::Shape(_) => { - if *seen == seg { - out.children.push(c.clone()); - } - } + Node::Shape(_) => put(*seen, c.clone()), Node::Group(s) => { - let p = prune(s, seg, seen); - if !p.children.is_empty() { - out.children.push(Node::Group(p)); + for (seg, part) in split(s, seen) { + put(seg, Node::Group(part)); } } } } - out + by.into_iter().map(|(seg, children)| (seg, shell(g, children))).collect() } let n = count(&doc.root); - (0..=n) - .map(|seg| { - let mut d = doc.clone(); - d.root = prune(&doc.root, seg, &mut 0); - d - }) + let mut roots: Vec> = vec![None; n + 1]; + for (seg, part) in split(&doc.root, &mut 0) { + if let Some(slot) = roots.get_mut(seg) { + *slot = Some(part); + } + } + roots + .into_iter() + .map(|root| Doc { width: doc.width, height: doc.height, root: root.unwrap_or_else(|| shell(&doc.root, vec![])), skipped: doc.skipped.clone() }) .collect() } @@ -655,3 +671,73 @@ pub fn masks_layer(proj: &mut Project, comp: &Comp, name: &str, outlines: Vec Node { + let geom = Geom::Rect { x: 0.0, y: 0.0, w: 1.0, h: 1.0, rx: 0.0, ry: 0.0 }; + Node::Shape(Shape { + name: name.into(), + geom, + transform: Affine::IDENTITY, + fill: None, + fill_opacity: 1.0, + fill_rule: effectcraft_svg::FillRule::NonZero, + stroke: None, + opacity: 1.0, + }) + } + fn image() -> Node { + Node::Image(Image { name: "i".into(), transform: Affine::IDENTITY, width: 1, height: 1, rgba: std::sync::Arc::new(vec![0; 4]), opacity: 1.0 }) + } + fn group(name: &str, children: Vec) -> Node { + let mut g = Group::new(name); + g.children = children; + Node::Group(g) + } + fn names(g: &Group, out: &mut Vec) { + for c in &g.children { + match c { + Node::Shape(s) => out.push(s.name.clone()), + Node::Group(s) => { + out.push(format!("<{}", s.name)); + names(s, out); + out.push(format!("{}>", s.name)); + } + Node::Image(_) => out.push("IMG".into()), + } + } + } + + /// Segment `k` keeps the shapes between image `k - 1` and `k`, with the groups that hold them. + #[test] + fn split_keeps_each_segments_shapes_and_their_groups() { + let mut root = Group::new("root"); + root.children = vec![ + shape("a"), + image(), + group("g1", vec![shape("b"), image(), shape("c"), group("g2", vec![shape("d"), image(), shape("e")])]), + image(), + group("empty", vec![image()]), + shape("f"), + ]; + let doc = Doc { width: 10.0, height: 10.0, root, skipped: vec![] }; + let segs = split_at_images(&doc); + assert_eq!(segs.len(), 6); + let got: Vec> = segs + .iter() + .map(|d| { + let mut v = vec![]; + names(&d.root, &mut v); + v + }) + .collect(); + let want: Vec> = + vec![vec!["a"], vec![""], vec!["", "g1>"], vec!["", "g1>"], vec![], vec!["f"]]; + assert_eq!(got, want); + assert!(segs.iter().all(|d| d.root.name == "root" && d.width == 10.0)); + } +}