mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 02:07:09 -06:00
SVG export: a chosen artboard's file leaves out the art of other artboards inside a shared group
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
bflatastic
co-authored by
Claude Opus 5.5
parent
e22b6a5d45
commit
0a4e9ea8c8
@@ -94,13 +94,14 @@ fn selected_part(n: &Node, keep: &HashSet<NodeId>) -> Option<Node> {
|
||||
}
|
||||
|
||||
/// `doc` with only the art over `rect` (an artboard): objects whose visual bounds miss it are left
|
||||
/// out, at any depth of layers and sublayers, and objects that reach it stay whole (the file's
|
||||
/// bounds clip them), as DXF's crop leaves them. Symbol instances are measured by their art, and
|
||||
/// the box of one whose art holds brushed strokes grows by as far as their brush art can paint
|
||||
/// ([`SymbolBoxes::with_brush_art`]). An object that holds a symbol instance also stays when what
|
||||
/// the canvas paints for it reaches `rect` ([`vectorcraft_render::painted_bounds`]: the effects in
|
||||
/// the symbols' art and its own effects). A clipping layer keeps every object, its clipping path
|
||||
/// among them. An artboard's file then holds its own art, not a whole document of artboards.
|
||||
/// out, at any depth of layers, sublayers and groups that don't clip, and objects that reach it
|
||||
/// stay whole (the file's bounds clip them), as DXF's crop leaves them. Symbol instances are
|
||||
/// measured by their art, and the box of one whose art holds brushed strokes grows by as far as
|
||||
/// their brush art can paint ([`SymbolBoxes::with_brush_art`]). An object that holds a symbol
|
||||
/// instance also stays when what the canvas paints for it reaches `rect`
|
||||
/// ([`vectorcraft_render::painted_bounds`]: the effects in the symbols' art and its own effects). A
|
||||
/// clipping layer keeps every object, its clipping path among them. An artboard's file then holds
|
||||
/// its own art, not a whole document of artboards.
|
||||
pub(super) fn art_over(doc: &Document, rect: vectorcraft_geom::Rect) -> Document {
|
||||
fn kept(n: &Arc<Node>, doc: &Document, symbols: &SymbolBoxes, rect: vectorcraft_geom::Rect) -> Option<Arc<Node>> {
|
||||
if matches!(n.kind, NodeKind::Layer { clip: false, .. }) {
|
||||
@@ -109,6 +110,15 @@ pub(super) fn art_over(doc: &Document, rect: vectorcraft_geom::Rect) -> Document
|
||||
*children = children.iter().filter_map(|c| kept(c, doc, symbols, rect)).collect();
|
||||
return Some(Arc::new(layer));
|
||||
}
|
||||
// A group that doesn't clip keeps only its members over the artboard, so art gathered in one
|
||||
// group across several artboards splits by artboard. Not with effects of its own: a shadow
|
||||
// can reach in from a member outside.
|
||||
if matches!(n.kind, NodeKind::Group { clip: false, .. }) && n.appearance.effects.is_empty() {
|
||||
let mut group = Node::clone(n);
|
||||
let children = group.children_mut()?;
|
||||
*children = children.iter().filter_map(|c| kept(c, doc, symbols, rect)).collect();
|
||||
return (!children.is_empty()).then(|| Arc::new(group));
|
||||
}
|
||||
let over = |b: vectorcraft_geom::Rect| b.x0 <= rect.x1 && rect.x0 <= b.x1 && b.y0 <= rect.y1 && rect.y0 <= b.y1;
|
||||
let mut holds_instance = false;
|
||||
n.walk(&mut |c| holds_instance |= matches!(c.kind, NodeKind::SymbolInstance { .. }));
|
||||
|
||||
@@ -74,3 +74,20 @@ fn a_chosen_artboard_s_svg_holds_only_its_own_art() {
|
||||
let whole = text(&s.execute("document.serialize", &json!({"format": "svg"})).unwrap());
|
||||
assert_eq!(shapes(&whole).0, 2, "nothing named: the whole document's art");
|
||||
}
|
||||
|
||||
/// Art grouped across artboards (one variant per artboard in one group) splits by artboard too.
|
||||
#[test]
|
||||
fn a_chosen_artboard_s_svg_leaves_out_its_neighbours_art_in_a_shared_group() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100, "artboards": 3})).unwrap();
|
||||
let x = |s: &Session, b: usize| s.doc().unwrap().doc.artboards[b].rect.x0;
|
||||
let (first, third) = (x(&s, 0), x(&s, 2));
|
||||
s.execute("shape.rectangle", &json!({"x": first + 10.0, "y": 10, "width": 30, "height": 30})).unwrap();
|
||||
s.execute("shape.ellipse", &json!({"x": third + 10.0, "y": 10, "width": 30, "height": 30})).unwrap();
|
||||
s.execute("select.all", &json!({})).unwrap();
|
||||
s.execute("object.group", &json!({})).unwrap();
|
||||
let text = |v: &serde_json::Value| v["text"].as_str().unwrap().to_string();
|
||||
let third_only = text(&s.execute("document.serialize", &json!({"format": "svg", "useArtboards": true, "range": "3"})).unwrap());
|
||||
assert_eq!(third_only.matches("<path").count() + third_only.matches("<rect").count(), 1, "{third_only}");
|
||||
assert!(third_only.contains(" C") || third_only.contains("<ellipse"), "{third_only}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user