Vector import: split a document at its images in one traversal (#3514)

This commit is contained in:
Dylan M. Taylor
2026-11-11 14:35:34 -05:00
committed by GitHub
parent f0e49012dc
commit f9111a0d8f
+105 -19
View File
@@ -477,35 +477,51 @@ pub fn split_at_images(doc: &Doc) -> Vec<Doc> {
})
.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<Node>) -> 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<Node>)> = 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<Option<effectcraft_svg::Group>> = 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<Sh
}
l
}
#[cfg(test)]
mod tests {
use super::*;
use effectcraft_svg::{Geom, Group, Image, Shape};
fn shape(name: &str) -> 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>) -> Node {
let mut g = Group::new(name);
g.children = children;
Node::Group(g)
}
fn names(g: &Group, out: &mut Vec<String>) {
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<Vec<String>> = segs
.iter()
.map(|d| {
let mut v = vec![];
names(&d.root, &mut v);
v
})
.collect();
let want: Vec<Vec<&str>> =
vec![vec!["a"], vec!["<g1", "b", "g1>"], vec!["<g1", "c", "<g2", "d", "g2>", "g1>"], vec!["<g1", "<g2", "e", "g2>", "g1>"], vec![], vec!["f"]];
assert_eq!(got, want);
assert!(segs.iter().all(|d| d.root.name == "root" && d.width == 10.0));
}
}