diff --git a/crates/engine/src/cmd/fonts.rs b/crates/engine/src/cmd/fonts.rs index f648a00a3..ba91f87fa 100644 --- a/crates/engine/src/cmd/fonts.rs +++ b/crates/engine/src/cmd/fonts.rs @@ -10,8 +10,9 @@ use vectorcraft_color::Paint; use vectorcraft_doc::{AppearanceItem, Document, Node, NodeId, NodeKind, TextKind, TextObject, TextRun}; use vectorcraft_geom::Rect; +use super::select::on_artboard; use super::typecmd::refresh_bounds; -use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, has_doc, str_param}; +use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, has_doc, index, str_param}; pub fn specs() -> Vec { vec![ @@ -38,7 +39,7 @@ pub fn specs() -> Vec { "Select Text by Font", [], None, - "{family, style?} select the text objects using the font, and the instances of the symbols whose art uses it, that are not hidden or locked, in a hidden or locked group or layer, or on a locked artboard → {count}", + "{family, style?, scope?: \"document\" (default) | {artboards: [index, …] (0-based)} | {layers: [id, …] (layers or sublayers)}} select the text objects using the font, and the instances of the symbols whose art uses it, that are not hidden or locked, in a hidden or locked group or layer, or on a locked artboard; with scope, only those on one of the artboards (as All on Active Artboard takes them: their bounds overlap it or their center is on it) or in one of the layers → {count}", has_doc, select_font ), @@ -713,6 +714,7 @@ fn select_font(s: &mut Session, p: &Value) -> Result { let style = str_param(p, "style").map(str::to_string); let ids = scope(s, false, None)?; let d = &s.doc()?.doc; + let within = FontScope::read(d, p)?; // Only text the Selection tool can reach: not hidden or locked, in a hidden or locked group // or layer, or on a locked artboard. let editable = d.editable(); @@ -758,11 +760,65 @@ fn select_font(s: &mut Session, p: &Value) -> Result { }); } drop(editable); + let symbols = d.symbol_boxes(); + hits.retain(|id| within.holds(d, &symbols, *id)); + drop(symbols); let n = hits.len(); s.select(|_, sel| sel.set(hits))?; Ok(json!({ "count": n })) } +/// Where Select Text by Font looks: its `scope`. +enum FontScope { + Document, + /// The artboards' rectangles. + Artboards(Vec), + /// The layers and sublayers, with what they hold. + Layers(BTreeSet), +} + +impl FontScope { + fn read(d: &Document, p: &Value) -> Result { + const C: &str = "select.font"; + let expected = || bad(C, "`scope` must be \"document\", {artboards: [index, …]} or {layers: [id, …]}"); + let scope = match p.get("scope") { + None | Some(Value::Null) => return Ok(Self::Document), + Some(Value::String(v)) if v == "document" => return Ok(Self::Document), + Some(Value::Object(m)) if m.len() == 1 => m, + Some(_) => return Err(expected()), + }; + let list = |key: &str| -> Result>> { + match scope.get(key) { + None => Ok(None), + Some(Value::Array(a)) if !a.is_empty() => Ok(Some(a)), + Some(_) => Err(bad(C, format!("`scope.{key}` must be a non-empty array"))), + } + }; + if let Some(a) = list("artboards")? { + let rect = |v: &Value| v.as_u64().and_then(|i| d.artboards.get(index(i))).map(|b| b.rect); + return a.iter().map(|v| rect(v).ok_or_else(|| bad(C, format!("no such artboard {v}")))).collect::>().map(Self::Artboards); + } + if let Some(a) = list("layers")? { + let layer = |v: &Value| v.as_u64().map(NodeId).filter(|id| d.node(*id).is_some_and(Node::is_layer)); + return a + .iter() + .map(|v| layer(v).ok_or_else(|| bad(C, format!("{v} names no layer or sublayer")))) + .collect::>() + .map(Self::Layers); + } + Err(expected()) + } + + /// Is object `id` in the scope. + fn holds(&self, d: &Document, symbols: &vectorcraft_doc::SymbolBoxes, id: NodeId) -> bool { + match self { + Self::Document => true, + Self::Artboards(rects) => d.node(id).is_some_and(|n| rects.iter().any(|r| on_artboard(n, symbols, *r))), + Self::Layers(layers) => d.ancestry(id).is_some_and(|a| a.iter().any(|i| layers.contains(i))), + } + } +} + fn font_list(_: &mut Session, p: &Value) -> Result { let db = vectorcraft_text::FontDb::global(); match str_param(p, "family") { @@ -911,6 +967,95 @@ mod tests { assert!(s.doc().unwrap().selection.objects.is_empty()); } + /// Select Text by Font with an artboard scope takes the text on the artboards, as All on + /// Active Artboard does: overlapping one, or centered on it, a straddling one included. + #[test] + fn select_font_in_artboards() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap(); + s.execute("artboard.new", &json!({"x": 300, "y": 0, "width": 200, "height": 200})).unwrap(); + s.execute("artboard.new", &json!({"x": 600, "y": 0, "width": 200, "height": 200})).unwrap(); + let text = |s: &mut Session, x: i32| { + NodeId(s.execute("text.create", &json!({"x": x, "y": 50, "text": "Serif", "font": "Source Serif 4"})).unwrap()["id"].as_u64().unwrap()) + }; + let first = text(&mut s, 10); + let straddling = text(&mut s, 180); + let second = text(&mut s, 350); + let third = text(&mut s, 650); + let _off = text(&mut s, 1000); + let select = |s: &mut Session, scope: Value| { + s.execute("select.font", &json!({"family": "Source Serif 4", "scope": scope})).unwrap(); + s.doc().unwrap().selection.objects.clone() + }; + assert_eq!(select(&mut s, json!({"artboards": [0]})), [first, straddling]); + assert_eq!(select(&mut s, json!({"artboards": [1, 2]})), [second, third]); + assert_eq!(select(&mut s, json!("document")).len(), 5); + // The same objects All on Active Artboard takes. + s.execute("select.allOnArtboard", &json!({"artboard": 0})).unwrap(); + assert_eq!(s.doc().unwrap().selection.objects, [first, straddling]); + // Hidden text stays out. + s.execute("select.set", &json!({"ids": [third.0]})).unwrap(); + s.execute("object.hide", &json!({})).unwrap(); + assert_eq!(select(&mut s, json!({"artboards": [1, 2]})), [second]); + for bad in [json!({"artboards": [3]}), json!({"artboards": []}), json!({"artboards": ["x"]}), json!({"pages": [0]}), json!("all"), json!(7)] { + assert!(s.execute("select.font", &json!({"family": "Source Serif 4", "scope": bad})).is_err(), "{bad}"); + } + } + + /// Select Text by Font with a layer scope takes the text in the layers, their sublayers + /// included, and still leaves out hidden and locked text. + #[test] + fn select_font_in_layers() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap(); + let text = |s: &mut Session, y: i32| { + NodeId(s.execute("text.create", &json!({"x": 10, "y": y, "text": "Serif", "font": "Source Serif 4"})).unwrap()["id"].as_u64().unwrap()) + }; + let first_layer = s.doc().unwrap().doc.layers[0].id; + let a = text(&mut s, 20); + let second_layer = s.execute("layer.new", &json!({})).unwrap()["id"].as_u64().unwrap(); + let b = text(&mut s, 40); + let sub = s.execute("layer.newSublayer", &json!({"parent": second_layer})).unwrap()["id"].as_u64().unwrap(); + s.execute("layer.setCurrent", &json!({"id": sub})).unwrap(); + let in_sub = text(&mut s, 60); + let locked = text(&mut s, 80); + s.execute("select.set", &json!({"ids": [locked.0]})).unwrap(); + s.execute("object.lock", &json!({})).unwrap(); + let select = |s: &mut Session, layers: Value| { + s.execute("select.font", &json!({"family": "Source Serif 4", "scope": {"layers": layers}})).unwrap(); + let mut v = s.doc().unwrap().selection.objects.clone(); + v.sort(); + v + }; + let d = &s.doc().unwrap().doc; + assert_eq!(d.ancestry(in_sub).unwrap()[1], NodeId(sub), "the text is in the sublayer"); + assert_eq!(select(&mut s, json!([first_layer.0])), [a]); + assert_eq!(select(&mut s, json!([second_layer])), [b, in_sub]); + assert_eq!(select(&mut s, json!([sub])), [in_sub]); + assert_eq!(select(&mut s, json!([first_layer.0, sub])), [a, in_sub]); + // Not a layer: an error, the selection as it was. + assert!(s.execute("select.font", &json!({"family": "Source Serif 4", "scope": {"layers": [a.0]}})).is_err()); + assert!(s.execute("select.font", &json!({"family": "Source Serif 4", "scope": {"layers": [u64::MAX]}})).is_err()); + assert_eq!(s.doc().unwrap().selection.objects.len(), 2); + } + + /// A symbol instance whose art uses the font is selected when the instance is in scope. + #[test] + fn select_font_scope_takes_symbol_instances_in_it() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap(); + s.execute("artboard.new", &json!({"x": 300, "y": 0, "width": 200, "height": 200})).unwrap(); + let id = s.execute("text.create", &json!({"x": 10, "y": 50, "text": "Serif", "font": "Source Serif 4"})).unwrap()["id"].clone(); + s.execute("symbol.new", &json!({"name": "Mark", "ids": [id]})).unwrap(); + let instance = s.doc().unwrap().selection.objects[0]; + let select = |s: &mut Session, i: u64| { + s.execute("select.font", &json!({"family": "Source Serif 4", "scope": {"artboards": [i]}})).unwrap()["count"].clone() + }; + assert_eq!(select(&mut s, 0), 1); + assert_eq!(s.doc().unwrap().selection.objects, [instance]); + assert_eq!(select(&mut s, 1), 0); + } + /// Replace Font leaves hidden and locked text in its font, and text inside a hidden or locked /// group or layer, and says so. Text on a visible, unlocked template layer is changed. #[test] diff --git a/crates/engine/tests/command_sweep.rs b/crates/engine/tests/command_sweep.rs index aca3d27a8..863a574cd 100644 --- a/crates/engine/tests/command_sweep.rs +++ b/crates/engine/tests/command_sweep.rs @@ -230,6 +230,10 @@ fn structured_junk() { ("document.export", json!({"format": "bmp", "rect": {"x": 1}, "artboard": 3})), ("document.export", json!({"format": "svg", "rect": [0, 0, 10, 10]})), ("select.add", json!({"ids": [[1], {"a": 1}]})), + ("select.font", json!({"family": "Source Sans 3", "scope": {"artboards": [0, u64::MAX, -1, "x", null, 1.5]}})), + ("select.font", json!({"family": "Source Sans 3", "scope": {"layers": [0, u64::MAX, -1, [1], {"a": 1}]}})), + ("select.font", json!({"family": "Source Sans 3", "scope": {"artboards": [], "layers": [1]}})), + ("select.font", json!({"family": "Source Sans 3", "scope": 7})), ("select.anchors", json!({"id": 2, "anchors": [[99, 99], [-1, 0], ["a", "b"]], "mode": "set"})), ("select.anchorsMany", json!({"items": [{"id": 2, "anchors": [[0, 1000]]}, null, 5]})), ("select.anchorsMany", json!({"items": [{"id": 2, "anchors": [[0, 1000], [0, 0]]}, {"id": u64::MAX}], "mode": "toggle"})), diff --git a/crates/ui-egui/src/find_font.rs b/crates/ui-egui/src/find_font.rs index e73d78736..d63905e48 100644 --- a/crates/ui-egui/src/find_font.rs +++ b/crates/ui-egui/src/find_font.rs @@ -1,7 +1,8 @@ //! Type → Find Font…: the fonts the document uses (missing ones marked), Find (select the text //! using a font), Change / Change All to another font from the list Replace With Font From shows //! (the document's fonts, the recent fonts or every installed font), and Find in Folder… (the -//! missing fonts' files, in the Missing Fonts dialog: `ui.findFontsInFolder`). +//! missing fonts' files, in the Missing Fonts dialog: `ui.findFontsInFolder`). Find In limits Find +//! to the active artboard or the selected layers. use egui::Ui; use serde_json::{Value, json}; @@ -23,9 +24,10 @@ pub fn open_on_missing(app: &mut VectorcraftApp) { open_at(app, first.unwrap_or(0)); } -/// Open the dialog with the document's font `selected` (an index into `text.fonts`) chosen, and -/// System's font ([`system_family`]) to change to. +/// Open the dialog with the document's font `selected` (an index into `text.fonts`) chosen, +/// System's font ([`system_family`]) to change to, and the last Find In choice (Document at first). fn open_at(app: &mut VectorcraftApp, selected: usize) { + let find_in = FIND_IN.iter().find(|f| **f == app.ui.find_font_in).copied().unwrap_or("document"); let used = fonts(app); let source = "system"; let rows = replacement_rows(app, source, &used); @@ -33,7 +35,7 @@ fn open_at(app: &mut VectorcraftApp, selected: usize) { let style = rows.iter().find(|r| r.family.eq_ignore_ascii_case(&family)).map(|r| r.style.clone()).unwrap_or_default(); app.ui.dialog = Some(Dialog::new( "findFont", - json!({ "selected": selected, "family": family, "style": style, "selectionOnly": false, "replaceFrom": source, "rows": rows_json(&rows) }), + json!({ "selected": selected, "family": family, "style": style, "selectionOnly": false, "replaceFrom": source, "rows": rows_json(&rows), "findIn": find_in }), )); } @@ -210,6 +212,42 @@ fn set_source(app: &VectorcraftApp, d: &mut Dialog, source: &str, used: &[Value] d.fields.insert("style".into(), json!(pick.map(|r| r.style.clone()).unwrap_or_default())); } +/// Find In's choices (the `findIn` field): the whole document, the active artboard, or the layers +/// highlighted in the Layers panel (else the current layer). +const FIND_IN: [&str; 3] = ["document", "artboard", "layers"]; +const FIND_IN_LABELS: [&str; 3] = ["Document", "Artboard", "Layers"]; + +/// The `scope` Find passes `select.font` for Find In choice `find_in`. A document without +/// artboards, or without a layer to look in, is looked through whole. +fn find_scope(app: &VectorcraftApp, find_in: &str) -> Value { + let Some(st) = app.session.active() else { return json!("document") }; + let n = st.doc.artboards.len(); + match find_in { + "artboard" if n > 0 => json!({ "artboards": [crate::panels::artboards::selected(app, n)] }), + "layers" => { + let is_layer = |id: &vectorcraft_doc::NodeId| st.doc.node(*id).is_some_and(vectorcraft_doc::Node::is_layer); + let mut layers: Vec = st.highlighted_rows().into_iter().filter(is_layer).map(|id| id.0).collect(); + if layers.is_empty() { + layers.extend(st.current_layer().map(|id| id.0)); + } + if layers.is_empty() { json!("document") } else { json!({ "layers": layers }) } + } + _ => json!("document"), + } +} + +/// Run the dialog's button `button` (find, change or changeAll) on the document font `from`. +fn run_button(app: &mut VectorcraftApp, d: &Dialog, button: &str, from: &Value) -> Result { + match button { + "find" => { + let scope = find_scope(app, &d.str("findIn")); + app.run("select.font", json!({ "family": from["family"], "style": from["style"], "scope": scope })) + } + "change" => change(app, d, from, false), + _ => change(app, d, from, true), + } +} + fn fonts(app: &mut VectorcraftApp) -> Vec { app.session.execute("text.fonts", &json!({})).ok().and_then(|v| v.as_array().cloned()).unwrap_or_default() } @@ -342,6 +380,14 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { } } ui.add_space(14.0); + ui.horizontal(|ui| { + widgets::field_label(ui, tl!("Find In")); + let at = FIND_IN.iter().position(|s| *s == d.str("findIn")).unwrap_or(0); + if let Some(i) = widgets::dropdown_named(ui, "ff-find-in", Some(tl!("Find In")), FIND_IN_LABELS[at], &FIND_IN_LABELS, 120.0) { + d.fields.insert("findIn".into(), json!(FIND_IN[i])); + } + }); + ui.add_space(6.0); // An empty list (Recent before any font was applied) leaves nothing to change to. let replace = sel < list.len() && can_replace(&d, families.as_deref()); ui.horizontal(|ui| { @@ -362,14 +408,10 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { }); }); }); + app.ui.find_font_in = d.str("findIn"); let sel = d.fields.get("selected").and_then(Value::as_u64).unwrap_or(0) as usize; if let (Some(a), Some(from)) = (act, list.get(sel)) { - let r = match a { - "find" => app.run("select.font", json!({ "family": from["family"], "style": from["style"] })), - "change" => change(app, &d, from, false), - _ => change(app, &d, from, true), - }; - match r { + match run_button(app, &d, a, from) { // The rows are a snapshot: the document's fonts and the recent fonts have moved on. Ok(_) if a != "find" => { let used = fonts(app); @@ -433,6 +475,48 @@ mod tests { assert_eq!(fonts(&mut app)[0]["family"], "Source Sans 3"); } + /// Find In passes Find the active artboard, or the selected layers (else the current + /// layer), as `select.font`'s scope. + #[test] + fn find_in_limits_find_to_the_artboard_or_the_layers() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap(); + app.session.execute("artboard.new", &json!({"x": 200, "y": 0, "width": 100, "height": 100})).unwrap(); + let text = |app: &mut VectorcraftApp, x: i32| { + app.session.execute("text.create", &json!({"x": x, "y": 40, "text": "Hi", "font": "Inter"})).unwrap()["id"].as_u64().unwrap() + }; + let first = text(&mut app, 10); + let layer = app.session.execute("layer.new", &json!({})).unwrap()["id"].as_u64().unwrap(); + let second = text(&mut app, 210); + let third = text(&mut app, 20); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + // Choose Find In on the open dialog, draw a frame, then press Find. + let find = |app: &mut VectorcraftApp, find_in: &str| { + open(app); + app.ui.dialog.as_mut().unwrap().fields.insert("findIn".into(), json!(find_in)); + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(app, ui.ctx())); + out.textures_delta.clear(); + let d = app.ui.dialog.clone().unwrap(); + let from = fonts(app).into_iter().find(|f| f["family"] == "Inter").unwrap(); + run_button(app, &d, "find", &from).unwrap(); + let mut ids: Vec = app.session.active().unwrap().selection.objects.iter().map(|id| id.0).collect(); + ids.sort_unstable(); + ids + }; + open(&mut app); + assert_eq!(app.ui.dialog.as_ref().unwrap().str("findIn"), "document"); + assert_eq!(find(&mut app, "document"), [first, second, third]); + // Without a view, the active artboard is the first. + assert_eq!(find(&mut app, "artboard"), [first, third]); + assert_eq!(find_scope(&app, "layers"), json!({ "layers": [layer] })); + assert_eq!(find(&mut app, "layers"), [second, third]); + // Closing and opening the dialog again keeps the last choice. + app.ui.dialog = None; + open(&mut app); + assert_eq!(app.ui.dialog.as_ref().unwrap().str("findIn"), "layers"); + } + /// Change All leaves locked text in its font, and the status bar says so. #[test] fn change_all_clears_the_skip_warning_after_unlocking_text() { diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index d758e8db3..d9536d7a0 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2699,6 +2699,7 @@ digits Malayalam Малаялам Reset Transformation Нулиране на трансформацията Replace With Font Замяна с шрифт Replace With Font From Замяна с шрифт от + Find In Търсене в System Система Replaces the saved preset of that name. Заменя записаната предварителна настройка с това име. Replace… Замяна… diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 44d90cfba..ce486cb67 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -4000,6 +4000,7 @@ effect Regular Obično Find in Folder… Pronađi u folderu… Replace With Font Zamijeni fontom Replace With Font From Zamijeni fontom iz + Find In Traži u System Sistem — substituted by {style} — zamijenjeno stilom {style} — {n} characters from another font — {n} znakova iz drugog fonta diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index c87981c47..b2b88879c 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -3406,6 +3406,7 @@ digits Malayalam Malaiàlam Reset Transformation Restableix la transformació Replace With Font Reemplaça per un tipus de lletra Replace With Font From Reemplaça per un tipus de lletra de + Find In Cerca a System Sistema Replace “{name}”, the file this document was opened from? Voleu reemplaçar “{name}”, el fitxer amb què es va obrir aquest document? Replaces the saved preset of that name. Reemplaça el preajust desat amb aquest nom. diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 2b051382b..089cc5e33 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4050,6 +4050,7 @@ effect Regular Pravidelný Find in Folder… Najít ve složce… Replace With Font Nahradit písmem Replace With Font From Nahradit písmem z + Find In Hledat v System Systém — substituted by {style} — nahrazeno řezem {style} — {n} characters from another font — znaky z jiného písma: {n} diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 22c0d19bd..c8c6db18b 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -3766,6 +3766,7 @@ effect Regular Regular Find in Folder… Find i mappe… Replace With Font Erstat med skrifttype Replace With Font From Erstat med skrifttype fra + Find In Søg i System System — substituted by {style} — erstattet af {style} — {n} characters from another font — {n} tegn fra en anden skrifttype diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 0bfb960b8..ea1f60441 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2731,6 +2731,7 @@ digits Malayalam Malayalam Reset Transformation Transformation zurücksetzen Replace With Font Durch Schrift ersetzen Replace With Font From Ersetzende Schrift aus + Find In Suchen in System System Replaces the saved preset of that name. Ersetzt die gespeicherte Vorgabe dieses Namens. Replace… Ersetzen… diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 6c22f4976..a24e6fb51 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -3341,6 +3341,7 @@ digits Malayalam Μαλαγιάλαμ Reset Transformation Επαναφορά μετασχηματισμού Replace With Font Αντικατάσταση με γραμματοσειρά Replace With Font From Αντικατάσταση με γραμματοσειρά από + Find In Αναζήτηση σε System Σύστημα Replaces the saved preset of that name. Αντικαθιστά την αποθηκευμένη προεπιλογή με αυτό το όνομα. Require a password to open the document Απαίτηση κωδικού για το άνοιγμα εγγράφου diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 0ba8a2bfc..b20bbb6b7 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2774,6 +2774,7 @@ digits Malayalam Malayálam Reset Transformation Restablecer transformación Replace With Font Reemplazar con fuente Replace With Font From Reemplazar con fuente de + Find In Buscar en System Sistema Replaces the saved preset of that name. Reemplaza el preajuste guardado con ese nombre. Replace… Reemplazar… diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 91ad598b9..8e1c377f9 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -3969,6 +3969,7 @@ effect Regular Normaali Find in Folder… Etsi kansiosta… Replace With Font Korvaa fontilla Replace With Font From Korvaavan fontin lähde + Find In Etsi kohteesta System Järjestelmä — substituted by {style} — korvattu tyylillä {style} — {n} characters from another font — {n} merkkiä toisesta fontista diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 04462dfef..a424b7e26 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2766,6 +2766,7 @@ digits Malayalam Malayalam Reset Transformation Réinitialiser la transformation Replace With Font Remplacer par la police Replace With Font From Remplacer par une police de + Find In Rechercher dans System Système Replaces the saved preset of that name. Remplace le paramètre prédéfini enregistré sous ce nom. Replace… Remplacer… diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 9dede4b5d..49cd602d7 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -3306,6 +3306,7 @@ mark-style Japanese Japán Reset Transformation Átalakítás visszaállítása Replace With Font Helyettesítő betűtípus Replace With Font From Helyettesítő betűtípus forrása + Find In Keresés helye System Rendszer Replace “{name}”, the file this document was opened from? Felülírod ezt a fájlt, amelyből a dokumentumot megnyitottad: „{name}”? Replaces the saved preset of that name. Felülírja az azonos nevű mentett előbeállítást. diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index dfb8ef46e..c02f16b52 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -3766,6 +3766,7 @@ effect Regular Reguler Find in Folder… Temukan di folder… Replace With Font Ganti dengan font Replace With Font From Ganti dengan font dari + Find In Cari di System Sistem — substituted by {style} — digantikan oleh {style} — {n} characters from another font — {n} karakter dari font lain diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 1e124363f..efd781f71 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2807,6 +2807,7 @@ digits Malayalam Malayalam Reset Transformation Reimposta trasformazione Replace With Font Sostituisci con font Replace With Font From Sostituisci con font da + Find In Cerca in System Sistema Replaces the saved preset of that name. Sostituisce il preset salvato con quel nome. Replace… Sostituisci… diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index c4040d3c5..eea083135 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2793,6 +2793,7 @@ mark-style Japanese 日本式 Reset Transformation 変形をリセット Replace With Font 置換するフォント Replace With Font From 置換するフォントの一覧 + Find In 検索範囲 System システム Replace it with another file, or ignore it: its images keep showing the preview saved with the document. 別のファイルに置き換えるか、無視してください。無視した場合、画像にはドキュメントに保存されたプレビューが表示され続けます。 Replaces the saved preset of that name. 同じ名前の保存済みプリセットを置き換えます。 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index c7b2cb935..af65f71b7 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -3559,6 +3559,7 @@ effect Regular ჩვეულებრივი Find in Folder… საქაღალდეში ძებნა… Replace With Font ჩანაცვლება შრიფტით Replace With Font From ჩანაცვლების შრიფტის წყარო + Find In ძებნის არე System სისტემა — substituted by {style} — ჩანაცვლებულია: {style} — {n} characters from another font — სხვა შრიფტის სიმბოლოები: {n} diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 4098a1134..6246149a2 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2782,6 +2782,7 @@ mark-style Japanese 일본식 Reset Transformation 변형 재설정 Replace With Font 바꿀 글꼴 Replace With Font From 바꿀 글꼴 목록 + Find In 찾을 위치 System 시스템 Replace it with another file, or ignore it: its images keep showing the preview saved with the document. 다른 파일로 바꾸거나 무시하세요. 무시하면 해당 이미지는 문서에 저장된 미리 보기로 계속 표시됩니다. Replaces the saved preset of that name. 같은 이름으로 저장된 사전 설정을 대체합니다. diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index b5affe48a..d3e8ee5a8 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3780,6 +3780,7 @@ effect Regular Regelmatig Find in Folder… Zoeken in map… Replace With Font Vervangen door lettertype Replace With Font From Vervangen door lettertype uit + Find In Zoeken in System Systeem — substituted by {style} — vervangen door {style} — {n} characters from another font — {n} tekens van een ander lettertype diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 82ef5d0f1..04545e4e5 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -3889,6 +3889,7 @@ effect Regular Normalny Find in Folder… Znajdź w folderze… Replace With Font Zastąp czcionką Replace With Font From Zastąp czcionką z + Find In Szukaj w System System — substituted by {style} — zastąpiona przez {style} — {n} characters from another font — znaki z innej czcionki: {n} diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 20895d73b..382fc086c 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2314,6 +2314,7 @@ digits Malayalam Malaiala Reset Transformation Redefinir Transformação Replace With Font Substituir Com Fonte Replace With Font From Substituir Com Fonte De + Find In Localizar em System Sistema Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Substitua por outro arquivo, ou ignore-o: suas imagens continuam mostrando a pré-visualização salva com o documento. Replaces the saved preset of that name. Substitui o predefinido salvo com esse nome. diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 1cb376142..f5980891e 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2802,6 +2802,7 @@ digits Malayalam Малаялам Reset Transformation Сбросить трансформацию Replace With Font Заменить шрифтом Replace With Font From Заменить шрифтом из + Find In Искать в System Система Replaces the saved preset of that name. Заменяет сохранённый набор с таким именем. Replace… Заменить… diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 55840c8bb..a0539f5cb 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2710,6 +2710,7 @@ digits Malayalam Malayalam Reset Transformation Återställ transformering Replace With Font Ersätt med typsnitt Replace With Font From Ersätt med typsnitt från + Find In Sök i System System Replaces the saved preset of that name. Ersätter den sparade förinställningen med samma namn. Replace… Ersätt… diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index a289160b5..4b605bb06 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2579,6 +2579,7 @@ digits Malayalam Malayalam Reset Transformation Dönüşümü Sıfırla Replace With Font Yazı Tipiyle Değiştir Replace With Font From Değiştirilecek Yazı Tipi Kaynağı + Find In Arama yeri System Sistem Replaces the saved preset of that name. Bu adın kayıtlı ön ayarını değiştirir. Replace… Değiştir… diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index c693f7ac0..78db714e4 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2799,6 +2799,7 @@ digits Malayalam Малаялам Reset Transformation Скинути трансформацію Replace With Font Замінити шрифтом Replace With Font From Замінити шрифтом з + Find In Шукати в System Система Replaces the saved preset of that name. Замінює збережений набір із таким ім’ям. Replace… Замінити… diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 7c366fd07..f309c0acc 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -3074,6 +3074,7 @@ mark-style Japanese Kiểu Nhật Reset Transformation Đặt lại biến đổi Replace With Font Thay thế bằng phông chữ Replace With Font From Thay thế bằng phông chữ từ + Find In Tìm trong System Hệ thống Replace “{name}”, the file this document was opened from? Thay thế “{name}”, tệp mà tài liệu này được mở từ đó? Replaces the saved preset of that name. Thay thế thiết lập sẵn đã lưu có tên đó. diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index a040da3c0..30603ef86 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2677,6 +2677,7 @@ digits Malayalam 马拉雅拉姆数字 Reset Transformation 重置变换 Replace With Font 替换为字体 Replace With Font From 替换字体来源 + Find In 查找范围 System 系统 Replaces the saved preset of that name. 替换同名已存预设。 Replace… 替换… diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 1eee83b7f..3971235fa 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3680,6 +3680,7 @@ digits Malayalam 馬來亞拉姆數字 Reset Transformation 重設變形 Replace With Font 取代為字體 Replace With Font From 取代字體來源 + Find In 尋找範圍 System 系統 Replaces the saved preset of that name. 取代同名的已儲存預設集。 Replace… 取代… diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 72b5e56f1..2970fd8cc 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -370,6 +370,9 @@ pub struct UiState { /// That popup's first text field is still to take the keyboard. #[serde(skip)] pub transient_focus: bool, + /// Type › Find Font…'s last Find In choice (`find_font::FIND_IN`), kept while the app runs. + #[serde(skip)] + pub find_font_in: String, pub control_bar: bool, /// The Control bar is docked to the bottom of the window instead of the top /// (`window.control.dock`). @@ -974,6 +977,7 @@ impl Default for UiState { open_panel: None, transient_panel: None, transient_focus: false, + find_font_in: String::new(), control_bar: false, control_bar_bottom: false, toolbar: true, diff --git a/docs/roadmap.md b/docs/roadmap.md index 68b15aa3d..a5866a1c8 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -42,6 +42,7 @@ M7 type: Touch Type moves a right-to-left character along its line and up from i | 2026-11-05 | minor | Selection Tools add-on: Select Linked, Embedded, Missing and Modified select the images and placed documents with that link status (Object › Plug-ins › Selection Tools and the Links panel menu; Shift adds), and Select by Link Status… lists them by name, status and format to pick from, optionally zooming to them; what is hidden, locked, on a template layer or outside the isolated group is left out and the status bar says how many (#5526, a VectorCraft addition); scores and estimates unchanged (estimated). | | 2026-11-05 | minor | Pencil and Paintbrush: a new stroke with Keep selected off leaves nothing selected, the earlier selection included, while Keep selected on selects only the new path (#5752); scores and estimates unchanged. | | 2026-11-05 | minor | Layers panel: a layer whose Layer Options › Print is off shows its name italic, as the reference application shows non-printing layers, and keeps it while the row is dragged (#5739); scores and estimates unchanged. | +| 2026-11-04 | minor | M7: Select Text by Font takes a scope: the document, chosen artboards or chosen layers; Find Font's Find In offers Document, Artboard (the active one) and Layers (the selected ones, else the current one) (#5529, a VectorCraft addition); estimates unchanged. | | 2026-11-04 | minor | M7: Find Font's Replace With Font lists family and style rows with a search, and the list and the document's font list scroll on their own (#4972); scores and estimates unchanged. | | 2026-11-04 | minor | Selection Tools add-on: Select by Artboards selects the objects overlapping, fully inside or fully outside several chosen artboards, from a dialog that lists, searches and multi-selects the artboards and fits one in the window on double-click (#5525, #5548, #5549, a VectorCraft addition); scores and estimates unchanged. | | 2026-11-04 | minor | M4: raster/PSD exports default to document resolution; CLI run/convert expose --ppi, matching the engine and MCP precedence. Scores and hours unchanged. | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 447221238..af0f12e59 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -59,6 +59,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | 2026-11-05 | minor | PDF import: a DeviceN gradient of a spot ink over process plates keeps the ink and the plates, and PDF export writes it as a DeviceN shading again (#3235); scores unchanged | | 2026-11-05 | minor | Optical kerning moves each pair part of the way toward the font's own stem spacing, with a minimum gap and tighter limits, and leaves ideographs, kana, fullwidth forms and dashes on the font's spacing, so text is no longer squeezed (#5336). Scores and estimates unchanged. | | 2026-11-05 | minor | Gradient panel: Delete Stop sits at the right end of the ramp, so it shows whenever the ramp does instead of under the stop fields, where a short panel hid it (#5403); scores and estimates unchanged | +| 2026-11-04 | minor | Record Select Text by Font's artboard and layer scope (#5529, a VectorCraft addition); scores and estimates unchanged. | | 2026-11-04 | minor | Type core: a Type-range Copy or Cut publishes styled art alongside plain text, so canvas Paste keeps font family, size and color after editing ends (#5636); scores and estimates unchanged. | | 2026-11-04 | minor | Selection Tools add-on: Select by Artboards selects the objects overlapping, fully inside or fully outside several chosen artboards, from a dialog that lists, searches and multi-selects the artboards and fits one in the window on double-click (#5525, #5548, #5549, a VectorCraft addition); scores and estimates unchanged. | | 2026-11-04 | minor | Raster/PSD defaults honor document resolution; CLI run/convert accept --ppi, matching MCP options.ppi precedence. Synthetic regressions added; scores and hours unchanged. | @@ -1020,6 +1021,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Manual kerning on inline graphics:** a manual kerning value on an inline graphic's character spaces it from the next character in reading order by the same distance as on a letter of its run's size, in left-to-right, right-to-left and vertical type; Auto and Optical kerning still apply no font pairs to it. This is VectorCraft's own rule. Test: `manual_kerning_on_an_inline_graphic_spaces_it_like_a_letter`. - **Recent font pickers (#3515):** The shared Character, Properties, Glyphs and Find Font family picker lists recently applied families first, once each, in most-recent order and limited by Preferences › Type › Number of Recent Fonts. Search and filters apply to both sections; unavailable families are omitted. Section headings cannot be selected or previewed. Confirmed character-range font edits update the same saved history; previews and failed edits do not. Requirement: [issue #3515](https://github.com/storytold/vectorcraft/issues/3515). - **Find Font: Replace With Font From:** a dropdown under Replace With Font chooses the list the replacement font comes from: Document lists the families the document uses, once each; Recent lists the recently applied families that are installed (fonts applied with Find Font's Change and Change All included), newest first, as many as Preferences › Type › Number of Recent Fonts allows; System, the default, lists the installed families available in the font menu. The dialog opens on System with Source Sans 3 chosen, or the font menu's first family when Source Sans 3 is not installed. Choosing a list keeps the chosen font when the list has it and it is installed; otherwise Document and Recent choose the list's first installed font, and System chooses Source Sans 3 or the font menu's first family. Change and Change All are unavailable when the list has no installed font (Recent before any font was applied) and when the chosen family is not installed or is not in the shown list. Behavior reference: [Find and replace fonts](https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/find-and-replace-fonts.html) (public documentation). Tests: `crates/ui-egui/src/find_font.rs`. +- **Select Text by Font: scope (a VectorCraft addition, #5529):** `select.font` takes `scope`: `"document"` (the default, as before), `{artboards: [index, …]}` or `{layers: [id, …]}` (sublayers included). On an artboard means what All on Active Artboard takes (the bounds overlap it or the center is on it; one shared test, `select::on_artboard`); hidden and locked text stays out as before; a symbol instance counts where the instance is. Type › Find Font…'s Find In dropdown passes the active artboard or the selected layers (else the current layer). Tests: `crates/engine/src/cmd/fonts.rs`, `crates/ui-egui/src/find_font.rs`, `crates/engine/tests/command_sweep.rs`. - **Mouse wheel over the font family field (#5253):** In the Character and Properties panels and the Control bar, turning the mouse wheel over the closed font family field applies the next family to the selected type (the previous one when turned up), keeping the style when the family has it, else its closest. Each notch is one undo step; leaving the field or Escape keeps the family, and Undo takes it back. The steps use the font menu's order at the first turn and keep it until the pointer leaves the field or the text selection changes, so recent-font updates do not reshuffle later turns. That order follows the menu's saved font filters but ignores its search text, puts recent families first, and stops at the first and last family. A turn with a modifier key is left to the panel and the canvas. Behavior reference: the [Illustrator CS6 reference](https://help.adobe.com/archive/en/illustrator/cs6/illustrator_reference.pdf#page=10) describes editing hovered dropdown controls with the mouse wheel; [Find and apply fonts](https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/find-and-apply-fonts.html) names the family field's places. Applying it to the font field, the order, the ends and the undo steps are VectorCraft's own rules. Tests: `crates/ui-egui/src/tests/font_menu.rs`, `crates/ui-egui/src/font_menu/tests.rs`, `crates/ui-egui/tests/font_wheel.rs`. - **Type:** Character size, leading, baseline shift and horizontal-scale controls account for object/group transforms (#1034): exact document-point sizes, mixed-value display and keyboard increments, range edits, resettable horizontal stretch, undo/redo and native save/reopen (`tests_typescale`, Character panel tests). Point size is the transformed em-height axis, independent of glyph ink bounds; affine rotation, reflection and shear are retained. Group scaling still scales text; preserving font sizes is a separate enhancement. Behaviour reference: [Transform text](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/transform-text.html) (public documentation). Text Wrap, Type on a Path effects (Rainbow/Skew/3D Ribbon/Stair Step/Gravity) and Options (Flip, Align to Path, Spacing; OK keeps the type where it is), type on a path's start, centre and end brackets (Selection and Direct Selection drag them to set where the type runs, slide it along its path and, across the path, flip it: #429), Character and Paragraph Styles (override-preserving redefine), paragraph attributes per paragraph (alignment, indents, spacing, hyphenation, hanging punctuation, tab stops, paragraph style, direction, mojikumi and leading model; the Paragraph, Tabs and Paragraph Styles panels act on the paragraphs the Type tool's selection or caret touches, Return continues the paragraph's style, threads and SVG keep them), Area Type Options (rows/columns/inset/first baseline, Text Flow by rows or by columns (#4978), vertical Align Top/Center/Bottom/Justify per row/column, reflowing non-rectangular frames and text wrap), threaded text across any closed shapes, Fit Headline, the Every-line Composer for justified and ragged text (paragraph-wide breaks for an even rag, Single-line per object from the Paragraph panel menu or `text.setFormat {composer}`). Area type resizes like Illustrator's type areas: a bounding-box handle drag (Selection tool) or Area Type Options Width/Height size the frame and the text reflows at its size (threads too); Direct Selection drags a frame corner or edge (`text.reshapeArea`), and an anchor of the path of type on a path outside perspective, the type reflowing along it (`text.reshapePath`, #3980). Point type still scales. Auto Size: the frame height follows the text after every edit, in the same undo step (rectangular frames of horizontal type in one row; columns get the least height that holds them; a height set by hand or threading turns it off), and the "Auto Size New Area Type" preference gives it to new area type. Shrink Text to Fit (beyond Illustrator): overflowing area type scales its sizes, leading and baseline shifts down by the largest factor (in 0.1 pt steps of the first run, down to a minimum percentage) that fits, at layout time so every export sees it. `text.areaOptions` and `document.inspect` report overflow and the shrink factor. The Selection tool shows the type widget beside selected type's bounding box (hollow on point type, filled on area type); double-clicking it converts between them (Type › Convert To Area Type / Convert To Point Type), keeping the text and its styles in place. Variable fonts list every named instance as a style of their family (Figtree Light to Black and their italics, Bahnschrift, Segoe UI Variable), and the instance chosen is shaped, drawn, outlined and measured at its axis settings; PDF and SVG embed it as that instance. The Character panel's Variable Font sliders set each axis (see Fonts). Beyond Illustrator: inline graphics (`text.insertInline`, Type ▸ Insert Inline Symbol) set a document symbol in a run of type as one character that flows, wraps and justifies like a glyph (InDesign-style inline objects, e.g. mana symbols in card rules text): sized to a multiple of the font size, centred on the cap height, opening up tall lines, following Redefine Symbol; a character of its run's size for Character Alignment and Top-to-Top leading, placed by the bidi reordering in right-to-left text (a neutral U+FFFC); drawn on the canvas, kept as live text plus a `` in SVG and as vector art in PDF; Backspace removes it whole. Hebrew and Arabic are laid out right to left with the Unicode bidirectional algorithm (embedded Latin and numbers keep their direction, lines put in visual order after wrapping) in point, area and path type, with caret, selection and arrow keys in visual order; each paragraph's direction comes from its first strong character unless Paragraph Direction sets it (Paragraph panel with Show Indic Options, `text.setFormat {direction}`), and new type aligns to the start of its direction. SVG export outlines right-to-left text. Arabic typefaces: builds made with a craft-fonts checkout that holds its Arabic families (Noto Sans Arabic, the Arabic fallback, plus Cairo, Tajawal, Almarai, IBM Plex Sans Arabic, Noto Kufi Arabic, Amiri, Alexandria, Fustat, Reem Kufi, Aref Ruqaa and Gulzar once craft-fonts adds them, storytold/craft-fonts#11; releases get them when their craft-fonts pin moves) list them in the font menus on desktop; the web build fetches them from beside the wasm (Noto Sans Arabic before start, so Arabic never shows as boxes, the rest in the background). Font Size and Leading are combo boxes in the Character and Properties panels (Font Size in the Control bar too, beside the Character link, font family and style): type, step with ↑/↓ or the spinner, scrub the label, or pick a preset (6–72 pt, shown in the type unit; Leading also Auto), each pick one undo step; Kerning and Tracking list -100…200; a size, leading, kerning or tracking the selected text doesn't share shows blank (#458). Type › Bold and Italic (beyond Illustrator, as page layout apps have them; Cmd+Shift+B / Cmd+Shift+I while the Type tool edits text) switch the selected type to its family's own Bold or Italic face and back, keeping its width (a Narrow face stays Narrow); a family without that face says so (#724). The commands that make or restyle type (and the MCP `add_text` tool) say in their result when area type does not fit its frame, and `add_text` takes Auto Size and Shrink Text to Fit (#1500, fixes #1471). Cmd+Shift+C (Align center) works on Windows and Linux too (#1940). A double-click on area type's bottom middle handle turns on Auto Size (#1940). Print and Print Preview warn about the area type and type on a path that prints without the text that doesn't fit, and give their ids. A tab stop in the Tabs panel drags from where the press landed, a stop just added included (#1487, fixes #1458). Area type in a non-rectangular shape moves a word too wide for the top lines down to the first line that holds it instead of breaking it (#1617, fixes #1615). Stylistic sets (ss01–ss20), stylistic and titling alternates, proportional and lining figures, and superscript and subscript glyphs can be turned on as OpenType features of a character style (`text.setStyle {features}`), and the OpenType panel keeps them (#1611, fixes #1607). Escape while typing ends the edit, keeps the type selected and switches back to the tool that was active before type editing began (kept while clicks move editing to other text), or to the Selection tool when there was none (#1802, #1686, #1656). Select › Same matches type on its characters' fill and stroke (#1746, #1737). `text.create`, `text.createInPath` and the MCP `add_text` tool take `justify`, so point type can be centred on or end at its point (#1752, #1748). The Character panel menu's Show Font Height Options shows a Set Font Height Reference dropdown under Font Size (`text.setFormat` or `text.setRangeStyle` `{fontHeightReference}`). With Em Box, the default, the font size is the size of the em square; with Cap Height, x-Height or ICF Box, the characters are set at the em size at which the font's OS/2 cap height, its x height or the height of its ideographic character face equals the font size. With a font that has no such value, or one outside 0.1 to 2 em, the size measures the em box. SVG, PDF and legacy `.ai` export and the CSS Properties rules write the em size the characters are drawn at, and DXF export writes 0.7 times that em size as each TEXT entity's height (group 40, the height of its capitals). Behavior reference: [Vary font height](https://web.archive.org/web/20251118174722/https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/vary-font-height.html). diff --git a/docs/type-parity.md b/docs/type-parity.md index d40ec6f76..4b48fd84b 100644 --- a/docs/type-parity.md +++ b/docs/type-parity.md @@ -34,7 +34,7 @@ against Illustrator's public documentation; **estimated** unless noted. | Character size and horizontal scale after object/group scaling | done | #1034: fields and setters compensate the stored affine; point size follows the transformed em-height axis, not the glyph ink bounds. Range edits, keyboard increments, leading and baseline shift use document points; rotation, reflection and shear remain editable. | | Preserve font sizes while scaling a group | missing | Separate enhancement; scaling still scales text with the other artwork. | | OpenType panel, Glyphs panel, alternates | done | Highlight Alternate Glyphs preference (#394, off by default) paints pink behind OpenType substitutions on screen when on (required shaping such as Arabic joining forms and the lam-alef ligature, including a lam-alef older fonts file under `liga`, is not highlighted; an optional feature that changes the ligature is); stylistic sets ss01–ss20, stylistic and titling alternates, proportional and lining figures, superscript and subscript are character-style features (`text.setStyle {features}`, #1607); the OpenType panel's Position menu sets superscript, subscript, numerator or denominator glyphs (`sups`, `subs`, `numr`, `dnom`). The OpenType panel's Stylistic Sets menu lists the selected font's `ss01`–`ss20` features, using a font-supplied name where available and `Set N` otherwise; several sets can be active, and selecting an active one removes it. For a mixed-font selection it uses the first selected character's font, a VectorCraft design choice ([public help](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/add-stylistic-sets-to-selected-text.html), [OpenType feature specification](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt)). The Glyphs panel highlights the character selected with the Type tool (a range's first character, else the one before the caret) and scrolls it into view as the selection moves (#4908; `panels::glyphs` tests); its Show menu lists Unicode ranges and Alternates for Current Selection, the selected character's alternate glyphs from the font's `GSUB` table, one of which a double-click draws for that character (`text.setGlyphAlternate`); the glyph-cell pop-up of alternates is not there yet | -| Font menu: samples, live preview, filters, favourites, recent families; font matching by any name; Find Font | done | #3515: recently applied families appear first, once each; missing families are omitted and the existing Number of Recent Fonts setting applies; #339; script filter: Japanese, Latin, Thai; the font scan on Linux also reads the folders fontconfig's configuration lists, such as TeX fonts under `/usr/share/texmf/fonts` (#1548, fixes #1529; not verified on Linux) Replace With Font lists family and style rows, one click setting both, with a search over them; its list scrolls on its own, apart from the document's font list (#4972). | +| Font menu: samples, live preview, filters, favourites, recent families; font matching by any name; Find Font | done | #3515: recently applied families appear first, once each; missing families are omitted and the existing Number of Recent Fonts setting applies; #339; script filter: Japanese, Latin, Thai; the font scan on Linux also reads the folders fontconfig's configuration lists, such as TeX fonts under `/usr/share/texmf/fonts` (#1548, fixes #1529; not verified on Linux); Select Text by Font takes a scope: the document, chosen artboards or chosen layers; Find Font's Find In offers Document, Artboard (the active one) and Layers (the selected ones, else the current one) (#5529, a VectorCraft addition); Replace With Font lists family and style rows, one click setting both, with a search over them; its list scrolls on its own, apart from the document's font list (#4972). | | Missing glyphs set in an installed font made for their script (Thai, Lao, Khmer, Myanmar first; then any font that has them) | done | Thai: Leelawadee UI, Tahoma, Thonburi, Sukhumvit Set, Noto Sans Thai, TLWG | | Full font-name matching (OpenType name ID 4) | done | #1258: loaded/cataloged faces and Missing Fonts searches; localized names and variable default faces; Windows font-service QA pending | | Variable fonts | done | named instances as styles; Character panel › Variable Font sets each axis (`fontAxes`); the font menu's Font Technology › Variable lists only variable font families, with the script, kind and ★ filters ([Find, apply, and adjust variable fonts](https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/find-apply-and-adjust-variable-fonts.html)); a family with a variable file beside static ones counts as variable. Evidence: `font_menu::tests::the_variable_filter_lists_only_variable_families`, `tests_axes::a_family_with_variation_axes_is_variable`, `tests_sysfonts::a_family_with_a_variable_file_beside_static_ones_is_variable` | @@ -87,6 +87,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha | 2026-11-06 | minor | Snap to Glyph › Glyph Bounds: each glyph's box gives four corners a drawn point lands on, also on a curved side (`B`), and its box and edges turn with rotated type (#5112); scores and estimates unchanged | | 2026-11-05 | minor | Character panel › Show Snap to Glyph Options scrolls the panel down to the Snap to Glyph section, which comes after every character attribute and could sit out of sight, and the section starts with a Snap to Glyph checkbox (View › Snap to Glyph) so its dimmed options can be turned on there (#5273); scores and estimates unchanged | | 2026-11-05 | minor | Release Type on a Path joins the Type Tools add-on: type on a path splits into point type and its path (#5532, beyond Illustrator). Scores and estimates unchanged. | +| 2026-11-04 | minor | Select Text by Font takes a scope: the document, chosen artboards or chosen layers; Find Font's Find In offers Document, Artboard (the active one) and Layers (the selected ones, else the current one) (#5529, a VectorCraft addition); scores and estimates unchanged. | | 2026-11-04 | minor | Typographer's Quotes: a lone typed `”` (U+201D) is set like a straight `"` where a quote opens (at the start, after a space), taking the document's opening quote, and stays `”` where one closes: the macOS Serbian Cyrillic layout has no straight quote and types `”` on Shift+2, which now gives „…” (#5247). `“ „` and the single quotes `‘ ’ ‚` stay as typed (that layout's apostrophe key types `’`, and `’90s` keeps its apostrophe); pasted text keeps its quotes; with the option off `”` stays as typed. Evidence: `docsetup::a_typed_closing_double_quote_opens_where_a_quote_opens`; scores unchanged | | 2026-11-04 | minor | A Type-range Copy or Cut publishes styled art alongside plain text, so a later canvas Paste keeps its character formatting after editing ends (#5636). Scores and estimates unchanged. | | 2026-11-03 | minor | Glyphs panel adds Alternates for Current Selection. A chosen alternate changes one character's drawn glyph while keeping its Unicode text; character-style capture does not copy that per-character choice. Scores and estimates unchanged. |