diff --git a/crates/engine/src/cmd/docmenu.rs b/crates/engine/src/cmd/docmenu.rs index f2972a1f1..fc146fdec 100644 --- a/crates/engine/src/cmd/docmenu.rs +++ b/crates/engine/src/cmd/docmenu.rs @@ -24,7 +24,7 @@ pub fn specs() -> Vec { "Symbol Instance", ["Select", "Same"], None, - "{} select the instances of the symbol of the first selected object that can be selected; objects that are hidden or locked, inside a hidden or locked object or layer, on a template layer, on a locked artboard while nothing is isolated, or outside the isolated group or layer are passed over → {count}", + "{} select the instances of the symbol of the first selected object that can be selected, including a group member selected on its own; when that object is not a symbol instance, such as a group, nothing is selected; objects that are hidden or locked, inside a hidden or locked object or layer, on a template layer, on a locked artboard while nothing is isolated (new art while it stays selected excepted), or outside the isolated group or layer are passed over → {count}", has_selection, |s, _| same(s, "select.same.symbolInstance", |a, b| match (&a.kind, &b.kind) { (NodeKind::SymbolInstance { symbol: x, .. }, NodeKind::SymbolInstance { symbol: y, .. }) => x == y, diff --git a/crates/engine/src/tests/menucmds.rs b/crates/engine/src/tests/menucmds.rs index b5cb95255..b201a60c3 100644 --- a/crates/engine/src/tests/menucmds.rs +++ b/crates/engine/src/tests/menucmds.rs @@ -2176,6 +2176,44 @@ fn select_same_symbol_instance_skips_objects_that_cannot_be_selected() { assert_eq!(selected(&s), vec![a1, on_template]); } +/// Select › Same › Symbol Instance takes as its reference the first selected object that can be +/// selected, whatever its kind: an instance inside a selected group, and an instance just placed +/// on a locked artboard while it stays selected. A first object that is not an instance matches +/// nothing, so the selection is cleared. +#[test] +fn select_same_symbol_instance_reference_can_be_any_selectable_object() { + const C: &str = "select.same.symbolInstance"; + let mut s = session(); + let place = |s: &mut Session, name: &str, x: f64| id_of(&s.execute("symbol.place", &json!({"name": name, "x": x, "y": 200})).unwrap()); + rect(&mut s, 0.0, 0.0, 20.0, 20.0); + let a1 = id_of(&s.execute("symbol.new", &json!({"name": "A"})).unwrap()); + rect(&mut s, 50.0, 0.0, 20.0, 20.0); + let b1 = id_of(&s.execute("symbol.new", &json!({"name": "B"})).unwrap()); + let b2 = place(&mut s, "B", 200.0); + let plain = rect(&mut s, 0.0, 100.0, 20.0, 20.0); + // A path selected before an instance is the reference, and no instance matches it. + sel(&mut s, &[plain, b1]); + assert_eq!(s.execute(C, &json!({})).unwrap()["count"], 0); + assert!(selected(&s).is_empty()); + // An instance inside a group, selected on its own, is the reference. + sel(&mut s, &[b1, plain]); + s.execute("object.group", &json!({})).unwrap(); + sel(&mut s, &[b1]); + assert_eq!(s.execute(C, &json!({})).unwrap()["count"], 2); + let mut got = selected(&s); + got.sort(); + let mut want = vec![b1, b2]; + want.sort(); + assert_eq!(got, want); + // An instance placed on a locked artboard is the reference while it stays selected. + s.execute("artboard.new", &json!({"x": 1000, "y": 0, "width": 400, "height": 400})).unwrap(); + s.execute("artboard.setProps", &json!({"index": 1, "locked": true})).unwrap(); + let a2 = place(&mut s, "A", 1100.0); + assert_eq!(selected(&s), vec![a2]); + assert_eq!(s.execute(C, &json!({})).unwrap()["count"], 2); + assert_eq!(selected(&s), vec![a1, a2]); +} + /// Select › Same › Text Fill & Stroke Color selects the type with characters whose fill and stroke /// both match the first character of the selected type: the same fill with another stroke, or the /// same stroke with another fill, is left out, and in type with several runs one run has to match diff --git a/docs/mcp.md b/docs/mcp.md index 89ec25768..ba467c12a 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1475,9 +1475,11 @@ selection holds no other type, they return the error "select a text object". `select.same.fillColor`, `strokeColor`, `fillAndStroke`, `strokeWeight`, `opacity`, `blendingMode`, `appearance`, `shapeType`, `graphicStyle`, `appearanceAttribute` and `symbolInstance` compare the other objects with the first -selected object, in selection order, that these commands could select. A selected object that is hidden or locked, -inside a hidden or locked group or layer, on a template layer, on a locked artboard while nothing is isolated, or -outside the isolated group is passed over. When the selection holds no other object, they return the error "nothing +selected object, in selection order, that can be selected. That object can be of any kind: a group, a member of a +group selected on its own, or, for `symbolInstance`, an object that is not a symbol instance, which matches nothing and +leaves the selection empty. A selected object that is hidden or locked, inside a hidden or locked group or layer, on a +template layer, on a locked artboard while nothing is isolated, or outside the isolated group is passed over. New art, +such as a symbol instance just placed, is not passed over for being on a locked artboard while it stays selected. When the selection holds no other object, they return the error "nothing selected". ## Shaper Groups diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index c762d355b..68e17e748 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -932,7 +932,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur Select › Same › Link Block Series selects every frame of the threads of the selected type; type in no thread selects itself (`select.same.linkBlockSeries`). The menu entry is listed in [Select objects by characteristics](https://helpx.adobe.com/illustrator/desktop/manage-objects/select-objects/select-objects-by-characteristics.html); selecting several threads at once and type in no thread selecting itself are VectorCraft's own behavior. Select › Same › Text Fill & Stroke Color selects the type whose characters match both the fill and the stroke of the selected type (`select.same.textFillAndStrokeColor`). The menu entry is listed in [Select objects by characteristics](https://helpx.adobe.com/illustrator/desktop/manage-objects/select-objects/select-objects-by-characteristics.html); requiring one run of characters to match both is VectorCraft's own behavior. The Select › Same font and text color commands take the type to match from the selected type that can be selected: type that is hidden or locked, on a template layer, or outside the isolated group is passed over. This is VectorCraft's own behavior. - Select › Same › Fill Color, Stroke Color, Fill & Stroke, Stroke Weight, Opacity, Blending Mode, Appearance, Shape, Graphic Style, Appearance Attribute and Symbol Instance compare the other objects with the first selected object that can be selected: an object that is hidden or locked, on a template layer, on a locked artboard while nothing is isolated, or outside the isolated group is passed over. This is VectorCraft's own behavior. + Select › Same › Fill Color, Stroke Color, Fill & Stroke, Stroke Weight, Opacity, Blending Mode, Appearance, Shape, Graphic Style, Appearance Attribute and Symbol Instance compare the other objects with the first selected object that can be selected, of any kind: an object that is hidden or locked, on a template layer, on a locked artboard while nothing is isolated (except new art while it stays selected), or outside the isolated group is passed over. This is VectorCraft's own behavior. Object › Flatten Transparency takes a frame of threaded text out of the thread in the same way when Convert All Text to Outlines outlines it or when the frame becomes flat regions or an image, and the other frames keep the text they hold. New Symbol and Redefine Symbol on a frame of threaded text take it out of the thread with the text it shows, which the symbol's art holds, and the other frames keep the text they hold. When the last frame goes into the symbol, the text it has no room for goes after the text of the last frame that stays. When every frame of a thread goes into the symbol, the copy of the last frame keeps all its text. With Optical Margin Alignment on, or Roman Hanging Punctuation on in the first paragraph, the glyphs at the ends of left-to-right type on a path hang outside its brackets, measured along the path. The type hangs at the start bracket when its line is placed there, hangs at the end bracket when the line is placed there, and hangs at both when it is centered. Glyphs that hang past the end bracket do not make the type overflow, and a hang stops at an open path's end. SVG export writes such type as outlines.