diff --git a/crates/engine/src/cmd/create.rs b/crates/engine/src/cmd/create.rs index 28891e255..40c411803 100644 --- a/crates/engine/src/cmd/create.rs +++ b/crates/engine/src/cmd/create.rs @@ -191,8 +191,9 @@ pub(crate) fn draw_inside_group(d: &mut vectorcraft_doc::Document, target: NodeI return Ok(p); } let (par, idx, _) = d.position(target).ok_or(crate::EngineError::NoNode(target))?; - let mut mask = d.node(target).cloned().ok_or(crate::EngineError::NoNode(target))?; - mask.id = d.alloc_id(); + // The mask is a copy with ids of its own, its subpaths' too (a compound path's). + let original = d.node(target).cloned().ok_or(crate::EngineError::NoNode(target))?; + let mut mask = d.reid(&original); mask.appearance = Appearance::basic(vectorcraft_color::Paint::None, vectorcraft_color::Paint::None, 0.0).into(); if let NodeKind::Path { clipping, .. } = &mut mask.kind { *clipping = true; diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index 127e1f0f2..8757c99b1 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -691,7 +691,8 @@ fn paste_clip(s: &mut Session, p: &Value, mode: PasteMode, clip: &Clipboard, cho // Paste on All Artboards bring behind the selection, as Paste in Back does. Paste in Front and // Paste in Back keep their own places, and copied artboards' art goes back into its layers. let modal = boards.is_none() && matches!(mode, PasteMode::Offset | PasteMode::InPlace | PasteMode::AllArtboards); - let inside = s.draw_inside.filter(|t| modal && s.draw_mode == crate::DrawMode::Inside && st.doc.node(*t).is_some()); + // (Only with objects to paste: an artboard copied without art makes no clip group.) + let inside = s.draw_inside.filter(|t| modal && !clip.nodes.is_empty() && s.draw_mode == crate::DrawMode::Inside && st.doc.node(*t).is_some()); let behind = modal && mode != PasteMode::Offset && s.draw_mode == crate::DrawMode::Behind; let (front, back) = (mode == PasteMode::Front, mode == PasteMode::Back || behind); // Paste Remembers Layers (not while isolating a group: pastes stay in it, nor in Draw diff --git a/crates/engine/src/tests_file.rs b/crates/engine/src/tests_file.rs index c88709f2b..7c9fc14c6 100644 --- a/crates/engine/src/tests_file.rs +++ b/crates/engine/src/tests_file.rs @@ -152,6 +152,40 @@ fn double_clicking_outside_the_draw_inside_object_returns_to_draw_normal() { /// the object drawn into, as drawn art goes, each paste one undo step; the first one makes the /// clip group and the next ones add to it, with Paste Remembers Layers on and with the object /// isolated by a double-click too. Paste in Front and Paste in Back keep their places. +/// Draw Inside a compound path: the clip group's mask is a copy whose subpaths have ids of their +/// own, so no two objects share an id. An artboard copied without art, pasted on all artboards in +/// Draw Inside, makes no clip group. +#[test] +fn draw_inside_a_compound_path_gives_its_mask_ids_of_its_own() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 400, "height": 300, "artboards": 2})).unwrap(); + let a = s.execute("shape.rectangle", &json!({"x": 100, "y": 100, "width": 100, "height": 100})).unwrap()["id"].as_u64().unwrap(); + let b = s.execute("shape.rectangle", &json!({"x": 130, "y": 130, "width": 40, "height": 40})).unwrap()["id"].as_u64().unwrap(); + s.execute("select.set", &json!({"ids": [a, b]})).unwrap(); + let c = s.execute("object.compoundPath.make", &json!({})).unwrap()["id"].as_u64().unwrap(); + s.execute("select.set", &json!({"ids": [c]})).unwrap(); + s.execute("view.drawMode", &json!({"mode": "inside"})).unwrap(); + s.execute("shape.ellipse", &json!({"x": 120, "y": 120, "width": 30, "height": 30})).unwrap(); + let d = &s.doc().unwrap().doc; + let mut ids = Vec::new(); + d.walk(|n| ids.push(n.id.0)); + let all = ids.len(); + ids.sort_unstable(); + ids.dedup(); + assert_eq!(ids.len(), all, "an id is used twice"); + assert_eq!(d.node(d.parent_of(NodeId(c)).unwrap()).unwrap().kind_label(), "Clip Group"); + // An artboard alone on the clipboard: Paste on All Artboards adds nothing inside the object. + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 400, "height": 300, "artboards": 2})).unwrap(); + let a = s.execute("shape.rectangle", &json!({"x": 100, "y": 100, "width": 100, "height": 100})).unwrap()["id"].as_u64().unwrap(); + s.execute("artboard.copy", &json!({"index": 1, "art": false})).unwrap(); + s.execute("select.set", &json!({"ids": [a]})).unwrap(); + s.execute("view.drawMode", &json!({"mode": "inside"})).unwrap(); + let _ = s.execute("edit.pasteOnAllArtboards", &json!({})); + let d = &s.doc().unwrap().doc; + assert!(d.parent_of(NodeId(a)).and_then(|p| d.node(p)).is_some_and(|p| p.is_layer()), "no clip group around the object"); +} + #[test] fn pasting_in_draw_inside_puts_the_copies_inside_the_object() { let mut s = Session::new();