diff --git a/crates/engine/src/cmd/docmenu.rs b/crates/engine/src/cmd/docmenu.rs index 58d5daf17..413e8c401 100644 --- a/crates/engine/src/cmd/docmenu.rs +++ b/crates/engine/src/cmd/docmenu.rs @@ -304,21 +304,16 @@ fn same(s: &mut Session, cmd: &str, eq: fn(&Node, &Node) -> bool) -> Result Option { let st = s.active()?; let mut out = None; - for id in &st.selection.objects { - st.doc.node(*id)?.walk(&mut |n| { - if out.is_none() - && let NodeKind::Text(t) = &n.kind - { - out = Some(t.first_style()); - } - }); - if out.is_some() { - break; + each_selected_text(s, |id| { + if let Some(NodeKind::Text(t)) = st.doc.node(id).map(|n| &n.kind) { + out = Some(t.first_style()); } - } + out.is_none() + }); out } diff --git a/crates/engine/src/tests/menucmds.rs b/crates/engine/src/tests/menucmds.rs index 0c351dba7..18ffe69cb 100644 --- a/crates/engine/src/tests/menucmds.rs +++ b/crates/engine/src/tests/menucmds.rs @@ -1948,6 +1948,53 @@ fn select_same_font_size_and_fill() { assert!(s.execute("select.same.fontSize", &json!({})).is_err()); } +/// The font Same commands take their reference from the first selected type that can be selected: +/// type that is hidden or locked inside a selected group, on a template layer, or outside the +/// isolated group is passed over. +#[test] +fn select_same_font_skips_type_that_cannot_be_selected() { + let mut s = session(); + let typed = + |s: &mut Session, y: f64, font: &str| id_of(&s.execute("text.create", &json!({"x": 0, "y": y, "text": "type", "font": font})).unwrap()); + let hid = typed(&mut s, 20.0, "Arial"); + let courier = typed(&mut s, 60.0, "Courier"); + let arial = typed(&mut s, 100.0, "Arial"); + let other_courier = typed(&mut s, 140.0, "Courier"); + sel(&mut s, &[hid, courier]); + let g = id_of(&s.execute("object.group", &json!({})).unwrap()); + let l2 = id_of(&s.execute("layer.new", &json!({"top": true})).unwrap()); + let on_template = typed(&mut s, 180.0, "Courier"); + s.execute("layer.setProps", &json!({"id": l2.0, "template": true})).unwrap(); + // Hidden or locked type inside the selected group is not the reference. + for (on, off) in [ + (json!({"id": hid.0, "visible": false}), json!({"id": hid.0, "visible": true})), + (json!({"id": hid.0, "locked": true}), json!({"id": hid.0, "locked": false})), + ] { + s.execute("layer.setProps", &on).unwrap(); + sel(&mut s, &[g]); + assert_eq!(s.execute("select.same.fontFamily", &json!({})).unwrap()["count"], 2, "{on}"); + assert_eq!(selected(&s), vec![courier, other_courier], "{on}"); + s.execute("layer.setProps", &off).unwrap(); + } + // Type on a template layer, selected by id, is not the reference. + sel(&mut s, &[on_template, arial]); + s.execute("select.same.fontFamily", &json!({})).unwrap(); + assert_eq!(selected(&s), vec![hid, arial]); + // Type outside the isolated group is not the reference. + s.execute("object.isolate", &json!({"id": g.0})).unwrap(); + sel(&mut s, &[arial, courier]); + s.execute("select.same.fontFamily", &json!({})).unwrap(); + assert_eq!(selected(&s), vec![courier]); + s.execute("object.exitIsolation", &json!({})).unwrap(); + // A selection whose only type cannot be selected has no reference. + for id in [hid, courier] { + s.execute("layer.setProps", &json!({"id": id.0, "visible": false})).unwrap(); + } + sel(&mut s, &[g]); + assert!(s.execute("select.same.fontFamily", &json!({})).is_err()); + assert_eq!(selected(&s), vec![g]); +} + /// 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 688d57ffc..fd739b406 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1445,6 +1445,12 @@ several runs, one run has to match both: type with the fill in one run and the s and stroke of other objects are not compared. Without type among the selected objects it returns the error "select a text object", and it records no undo step. +The font and text color commands (`select.same.fontFamily`, `fontFamilyStyle`, `fontFamilyStyleSize`, `fontSize`, +`textFillColor`, `textStrokeColor` and `textFillAndStrokeColor`) take the first character of the first type among the +selected objects and the objects inside them, in selection order, as the one to match. Type that is hidden or locked, +inside a hidden or locked group or layer, on a template layer, or outside the isolated group is passed over. When the +selection holds no other type, they return the error "select a text object". + ## Shaper Groups `shaper.releaseSource {source, group?}` takes the original shape `source` out of its Shaper Group: the shape becomes a diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index fc40894ea..d813ec507 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -922,6 +922,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur Type › Create Outlines, Object › Expand and Object › Expand Appearance on a frame of threaded text take it out of the thread with the text it shows, which the outlines draw, and the other frames keep the text they hold. When the last frame is outlined, the text it has no room for goes after the text of the last frame that stays. When Expand Appearance keeps the frame as type in a group with its shadow, outer glow or Crop Marks, the type stays in the thread. The public help on threads ([Create text threads between objects](https://helpx.adobe.com/illustrator/desktop/design-with-text/add-manage-text/create-text-threads-between-objects.html), [Remove or break text threads between objects](https://helpx.adobe.com/illustrator/desktop/design-with-text/add-manage-text/remove-or-break-text-threads-between-objects.html)) does not describe outlining a threaded frame; this handling is VectorCraft's own behavior. 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. 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.