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 <noreply@anthropic.com>
This commit is contained in:
moppeeer 2026-10-11 10:41:01 +02:00 committed by paaspaas00
parent d020b4e85d
commit ef87428481
2 changed files with 37 additions and 2 deletions

View File

@ -253,7 +253,7 @@ pub fn specs() -> Vec<CommandSpec> {
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<Value> {
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<Value> {
let st = s.doc()?;
let targets = format_targets(s);
@ -980,8 +983,17 @@ fn selection_attrs(s: &mut Session, _p: &Value) -> Result<Value> {
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();

View File

@ -179,6 +179,8 @@ pub struct Prefs {
pub rich_black_output: bool,
/// Font menu favourites (family names).
pub favorite_fonts: Vec<String>,
/// Fonts picked in the font menus, most recent first (shown at the menus' top).
pub recent_fonts: Vec<String>,
/// 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,