From ef874284815cbebecce851319b3a8b7a28fc93ad Mon Sep 17 00:00:00 2001 From: moppeeer <339764117+moppeeer@users.noreply.github.com> Date: Sun, 11 Oct 2026 10:41:01 +0200 Subject: [PATCH] feat(engine): Report the selected text; remember recently used fonts type.selectionAttrs reports the start of the selected text on one line (`selectedText`, up to 60 characters), for previews such as the font menu's Selected Text sample. The preferences keep `recentFonts`, the fonts last picked in the font menus. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/text.rs | 36 +++++++++++++++++++++++++++++++++-- crates/engine/src/lib.rs | 3 +++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/text.rs b/crates/engine/src/cmd/text.rs index 71d390d..1e3272f 100644 --- a/crates/engine/src/cmd/text.rs +++ b/crates/engine/src/cmd/text.rs @@ -253,7 +253,7 @@ pub fn specs() -> Vec { has_text_or_frames, break_link ), - cmd!(query "type.selectionAttrs", "Selection Attributes", [], None, "{} → resolved character/paragraph attributes at the text selection", has_doc, selection_attrs), + cmd!(query "type.selectionAttrs", "Selection Attributes", [], None, "{} → resolved character/paragraph attributes at the text selection, and its start as `selectedText` (one line, up to 60 characters)", has_doc, selection_attrs), ] } @@ -970,6 +970,9 @@ fn open_type(s: &mut Session, p: &Value) -> Result { Ok(json!({"features": list, "figures": otf::figures(&list)})) } +/// How much of the selected text `type.selectionAttrs` reports. +const SELECTED_TEXT_CHARS: usize = 60; + fn selection_attrs(s: &mut Session, _p: &Value) -> Result { let st = s.doc()?; let targets = format_targets(s); @@ -980,8 +983,17 @@ fn selection_attrs(s: &mut Session, _p: &Value) -> Result { let (pp, base) = st.doc.styles.resolve_para(pf); let cf = if r.is_empty() { story.char_format_at(r.start) } else { story.format_after(r.start) }; let cp = st.doc.styles.resolve_char(&base, cf); + // The selected text (its start, on one line), for previews such as the font menu's. + let line: String = story + .text + .get(r.start..r.end) + .unwrap_or_default() + .chars() + .map(|c| if c.is_control() || c == designcraft_doc::OBJECT_MARK { ' ' } else { c }) + .collect(); + let selected: String = line.trim_start().chars().take(SELECTED_TEXT_CHARS).collect(); Ok(json!({"story": sid.0, "paragraphStyle": pf.style, "characterStyle": cf.style, "para": pp, "chars": cp, - "paraOverrides": pf.para.count(), "charOverrides": cf.over.count()})) + "paraOverrides": pf.para.count(), "charOverrides": cf.over.count(), "selectedText": selected.trim()})) } /// Our own filler text (not Adobe's). @@ -1154,6 +1166,26 @@ mod open_type_tests { s.execute("text.move", &json!({"dir": "right"})).unwrap(); } + #[test] + fn selection_attrs_report_the_selected_text() { + let mut s = Session::new(); + s.execute("file.new", &json!({})).unwrap(); + let r = s + .execute( + "frame.create", + &json!({"rect": [72, 72, 300, 200], "content": "text", "text": "Spring issue +What's new in typography this season, and why it matters to every designer"}), + ) + .unwrap(); + s.execute("text.select", &json!({"story": r["story"], "anchor": 0, "focus": 13})).unwrap(); + assert_eq!(s.execute("type.selectionAttrs", &json!({})).unwrap()["selectedText"], "Spring issue", "on one line, trimmed"); + s.execute("text.select", &json!({"story": r["story"], "anchor": 13, "focus": 200})).unwrap(); + let long = s.execute("type.selectionAttrs", &json!({})).unwrap()["selectedText"].as_str().unwrap().to_string(); + assert!(long.starts_with("What's new") && (55..=60).contains(&long.chars().count()), "the line break skipped, capped: {long}"); + s.execute("text.select", &json!({"story": r["story"], "anchor": 3, "focus": 3})).unwrap(); + assert_eq!(s.execute("type.selectionAttrs", &json!({})).unwrap()["selectedText"], "", "a caret selects nothing"); + } + #[test] fn open_type_toggles_reach_the_text() { let mut s = Session::new(); diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 1647193..061165c 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -179,6 +179,8 @@ pub struct Prefs { pub rich_black_output: bool, /// Font menu favourites (family names). pub favorite_fonts: Vec, + /// Fonts picked in the font menus, most recent first (shown at the menus' top). + pub recent_fonts: Vec, /// Preferences › Type › Show Font Names in English: the font menus show CJK families by their /// English names instead of their native ones. Documents store the English name either way. pub show_font_names_in_english: bool, @@ -235,6 +237,7 @@ impl Default for Prefs { highlight_substituted_fonts: true, rich_black_output: false, favorite_fonts: Vec::new(), + recent_fonts: Vec::new(), show_font_names_in_english: false, smart_text_reflow: true, autocorrect: false,