From 94ef9b87c28bd289f270d696454dd34a08f3200b Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 31 Oct 2026 19:37:34 -0700 Subject: [PATCH] Type: Character panel toggles show off for a mixed selection, and text.setFormat checks on/off values The Character panel's All Caps, Underline and Strikethrough buttons, and its menu's All Caps, Underline, Strikethrough, Proportional Metrics, Tate-chu-yoko and No Break, read the first selected character's style. With only some characters marked they could show on, and choosing one turned the attribute off for every character. They now show on only when every selected character has the attribute, and a click on a mixed selection turns it on for all, as Small Caps and Standard Vertical Roman Alignment do. type.underline (Cmd+Shift+U) underlines a mixed selection the same way. The Character Alignment and Digits submenus check no item while the selected characters differ. text.setFormat ignored a value that was not true or false for underline, strikethrough, allCaps, proportionalMetrics, tateChuYoko, standardVerticalRoman, noBreak, hyphenate, hyphenateCapitalized and romanHangingPunctuation. It now refuses it with "`` must be true or false" and changes nothing, as text.setRangeStyle does. --- crates/engine/src/cmd/panelcmds.rs | 16 +++ crates/engine/src/tests/textedit.rs | 13 +- crates/ui-egui/src/panels/character.rs | 41 +++--- crates/ui-egui/src/panels/character/tests.rs | 140 ++++++++++++++++++- docs/mcp.md | 3 +- docs/target-app-parity.md | 1 + 6 files changed, 191 insertions(+), 23 deletions(-) diff --git a/crates/engine/src/cmd/panelcmds.rs b/crates/engine/src/cmd/panelcmds.rs index 7934dbe69..68449f5eb 100644 --- a/crates/engine/src/cmd/panelcmds.rs +++ b/crates/engine/src/cmd/panelcmds.rs @@ -318,6 +318,22 @@ fn spacing_param(p: &Value, key: &str, c: &str) -> Result; 3 fn set_format(s: &mut Session, p: &Value) -> Result { const C: &str = "text.setFormat"; let ids = super::typecmd::text_targets(s, p, C)?; + check_switches( + p, + &[ + "underline", + "strikethrough", + "allCaps", + "proportionalMetrics", + "tateChuYoko", + "standardVerticalRoman", + "noBreak", + "hyphenate", + "hyphenateCapitalized", + "romanHangingPunctuation", + ], + C, + )?; let num = |k: &str| p.get(k).and_then(Value::as_f64); let flag = |k: &str| p.get(k).and_then(Value::as_bool); let kerning = match p.get("kerning") { diff --git a/crates/engine/src/tests/textedit.rs b/crates/engine/src/tests/textedit.rs index 025cf6d30..178f8f528 100644 --- a/crates/engine/src/tests/textedit.rs +++ b/crates/engine/src/tests/textedit.rs @@ -325,6 +325,16 @@ fn type_commands_refuse_a_number_or_boolean_given_as_text() { ("text.setRangeStyle", json!({"id": id.0, "allCaps": 1}), "allCaps"), ("text.setRangeStyle", json!({"id": id.0, "noBreak": "true"}), "noBreak"), ("text.setRangeStyle", json!({"id": id.0, "size": 30, "tateChuYoko": 1}), "tateChuYoko"), + ("text.setFormat", json!({"ids": [id.0], "underline": "true"}), "underline"), + ("text.setFormat", json!({"ids": [id.0], "strikethrough": 1}), "strikethrough"), + ("text.setFormat", json!({"ids": [id.0], "allCaps": "false"}), "allCaps"), + ("text.setFormat", json!({"ids": [id.0], "proportionalMetrics": 1}), "proportionalMetrics"), + ("text.setFormat", json!({"ids": [id.0], "tateChuYoko": "true"}), "tateChuYoko"), + ("text.setFormat", json!({"ids": [id.0], "standardVerticalRoman": "true"}), "standardVerticalRoman"), + ("text.setFormat", json!({"ids": [id.0], "rotation": 10, "noBreak": 1}), "noBreak"), + ("text.setFormat", json!({"ids": [id.0], "hyphenate": "false"}), "hyphenate"), + ("text.setFormat", json!({"ids": [id.0], "hyphenateCapitalized": 0}), "hyphenateCapitalized"), + ("text.setFormat", json!({"ids": [id.0], "romanHangingPunctuation": "true"}), "romanHangingPunctuation"), ("text.getRange", json!({"id": id.0, "effective": "true"}), "effective"), ("type.opticalMarginAlignment", json!({"on": "true"}), "on"), ("type.systemLayout", json!({"on": "true"}), "on"), @@ -338,12 +348,13 @@ fn type_commands_refuse_a_number_or_boolean_given_as_text() { assert!(wrong.is_empty(), "not refused: {wrong:#?}"); let style = json!({"id": id.0, "size": 30, "underline": true, "tracking": null, "start": null, "noBreak": true}); s.execute("text.setRangeStyle", &style).unwrap(); + s.execute("text.setFormat", &json!({"ids": [id.0], "strikethrough": true, "hyphenate": null})).unwrap(); s.execute("type.size.increase", &json!({"by": null})).unwrap(); s.execute("type.opticalMarginAlignment", &json!({"on": true})).unwrap(); s.execute("type.ruby", &json!({"text": "a", "size": null, "overhang": false})).unwrap(); let t = obj(&s, id); let st = &t.runs[0].style; - assert!(st.underline && st.no_break && t.optical_margin && st.size > 30.0, "{st:?}"); + assert!(st.underline && st.strikethrough && st.no_break && t.optical_margin && st.size > 30.0, "{st:?}"); assert!(!t.ruby.is_empty() && t.ruby.iter().all(|r| !r.overhang && r.size_percent == 50.0), "{:?}", t.ruby); } diff --git a/crates/ui-egui/src/panels/character.rs b/crates/ui-egui/src/panels/character.rs index f00c33d70..db01f2e38 100644 --- a/crates/ui-egui/src/panels/character.rs +++ b/crates/ui-egui/src/panels/character.rs @@ -419,11 +419,12 @@ pub(crate) fn highlight(app: &mut VectorcraftApp, field: Field) -> Result Result { - let (s, _) = text_style(app).ok_or("select some type first")?; - format(app, json!({"underline": !s.underline})); - Ok(json!(!s.underline)) + text_style(app).ok_or("select some type first")?; + let on = shared(app, |s| s.underline) != Some(true); + format(app, json!({"underline": on})); + Ok(json!(on)) } /// `text.setRangeStyle` with `p` on the range the Type tool has selected (the whole text when @@ -946,15 +947,19 @@ fn attributes(app: &mut VectorcraftApp, ui: &mut Ui) { } ui.add_space(4.0); ui.horizontal(|ui| { + // On only when every character the panel acts on has it; a mixed selection turns it on. + let all_caps = shared(app, |s| s.all_caps) == Some(true); let small = shared(app, |s| s.small_caps.is_some()) == Some(true); let position = shared(app, |s| s.position).map(|p| p.id()).unwrap_or(""); + let underline = shared(app, |s| s.underline) == Some(true); + let strike = shared(app, |s| s.strikethrough) == Some(true); for (kind, tip, on, change) in [ - (Glyph::AllCaps, tl!("All Caps"), s.all_caps, json!({"allCaps": !s.all_caps})), + (Glyph::AllCaps, tl!("All Caps"), all_caps, json!({"allCaps": !all_caps})), (Glyph::SmallCaps, tl!("Small Caps"), small, json!({"smallCaps": !small})), (Glyph::Super, tl!("Superscript"), position == "superscript", position_toggle(position, "superscript")), (Glyph::Sub, tl!("Subscript"), position == "subscript", position_toggle(position, "subscript")), - (Glyph::Underline, tl!("Underline"), s.underline, json!({"underline": !s.underline})), - (Glyph::Strike, tl!("Strikethrough"), s.strikethrough, json!({"strikethrough": !s.strikethrough})), + (Glyph::Underline, tl!("Underline"), underline, json!({"underline": !underline})), + (Glyph::Strike, tl!("Strikethrough"), strike, json!({"strikethrough": !strike})), ] { if style_toggle(ui, kind, tip, on) { format(app, change); @@ -1093,8 +1098,7 @@ fn digits_items(app: &mut VectorcraftApp, ui: &mut Ui, digits: Option