Fix #3705: Draw Inside's mask gets ids of its own, and an artboard alone pastes no clip group

Review follow-up (CodeRabbit): the clip group's mask copied the target with
only a new root id, so a compound path's subpaths had the same ids in the
mask and in the object. The mask is now Document::reid's copy. Paste on All
Artboards with only an artboard on the clipboard made an empty Draw Inside
clip group; it now needs objects to paste.

draw_inside_a_compound_path_gives_its_mask_ids_of_its_own: without the
change an id is used twice (7 ids for 6 nodes).
This commit is contained in:
Taha-Mahmoodi
2026-10-23 09:56:08 +07:00
committed by bflatastic
parent eb4635fd4b
commit 26daaf1153
3 changed files with 39 additions and 3 deletions
+3 -2
View File
@@ -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;
+2 -1
View File
@@ -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
+34
View File
@@ -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();