diff --git a/crates/engine/src/cmd/spelling.rs b/crates/engine/src/cmd/spelling.rs index c3c84770c..8ae2a49c2 100644 --- a/crates/engine/src/cmd/spelling.rs +++ b/crates/engine/src/cmd/spelling.rs @@ -7,11 +7,13 @@ //! `removeWord`, `changeWord`), which every document's check takes as right; Ignore All //! (`text.spelling.ignoreAll`) skips a word in the active document only. +use std::collections::BTreeSet; use std::ops::Range; +use std::sync::atomic::{AtomicU64, Ordering}; use craft_proof::{IssueKind, Options, ProofLang}; use serde_json::{Value, json}; -use vectorcraft_doc::{Document, NodeId, NodeKind}; +use vectorcraft_doc::{Document, NodeId, NodeKind, TextObject}; use super::menucmds::whole_or; use super::typemenu::{all_texts, text_ids}; @@ -106,56 +108,96 @@ fn check(s: &mut Session, p: &Value) -> Result { None => all_texts(&s.doc()?.doc), }; let st = s.doc()?; - let ignored = &st.spelling_ignored; - let opts = Options::default(); let mut words = vec![]; let mut more = false; - 'texts: for id in ids { + for id in ids { let Some(NodeKind::Text(t)) = st.doc.node(id).map(|n| &n.kind) else { continue }; let text = t.plain_text(); - // Each run in its own language, at its bytes of the plain text. - let mut spans: Vec<(Range, ProofLang)> = Vec::with_capacity(t.runs.len()); - let mut at = 0; - for r in &t.runs { - let end = at + r.text.len(); - spans.push((at..end, ProofLang::from_tag(r.style.language.as_deref()))); - at = end; + let (found, cut) = misspellings(t, &st.spelling_ignored, MAX_WORDS - words.len()); + for m in found { + let Some(word) = text.get(m.range.clone()) else { continue }; + words.push(json!({ "id": id.0, "start": m.range.start, "end": m.range.end, "word": word, "lang": m.lang.tag() })); } - // craft-proof checks one paragraph at a time (its language detection reads the - // paragraph), and the check stops at the paragraph where the list fills up rather than - // proofing the rest of the text. - let mut para_start = 0; - for para in text.split('\n') { - let para_end = para_start + para.len(); - let para_spans: Vec<(Range, ProofLang)> = spans - .iter() - .filter_map(|(r, lang)| { - let (a, b) = (r.start.max(para_start), r.end.min(para_end)); - (a < b).then(|| (a - para_start..b - para_start, *lang)) - }) - .collect(); - for issue in craft_proof::check_text_with(para, ¶_spans, &opts, false, false) { - if issue.kind != IssueKind::Spelling { - continue; - } - let (start, end) = (para_start + issue.start, para_start + issue.end); - let Some(word) = text.get(start..end) else { continue }; - if craft_proof::custom_word(word).is_some_and(|w| ignored.contains(&w)) { - continue; - } - if words.len() == MAX_WORDS { - more = true; - break 'texts; - } - words.push(json!({ "id": id.0, "start": start, "end": end, "word": word, "lang": issue.lang.tag() })); - } - // Past the paragraph and its `\n`. - para_start = para_end + 1; + if cut { + more = true; + break; } } Ok(json!({ "words": words, "more": more })) } +/// One word of a type object its dictionaries don't know ([`misspellings`]). +#[derive(Clone, Debug, PartialEq)] +pub struct Misspelling { + /// The word's bytes in the object's [`TextObject::plain_text`]. + pub range: Range, + /// The language it was checked in. + pub lang: ProofLang, +} + +/// The words of `t` its dictionaries don't know, in text order: each run checked in its +/// language, each paragraph on its own, leaving out the words in `ignored` (a document's Ignore +/// All words). At most `max` of them; the flag says the list was cut short there. What +/// `text.spelling.check` lists for one object, and what Auto Spell Check underlines. +pub fn misspellings(t: &TextObject, ignored: &BTreeSet, max: usize) -> (Vec, bool) { + let text = t.plain_text(); + // Each run in its own language, at its bytes of the plain text. + let mut spans: Vec<(Range, ProofLang)> = Vec::with_capacity(t.runs.len()); + let mut at = 0; + for r in &t.runs { + let end = at + r.text.len(); + spans.push((at..end, ProofLang::from_tag(r.style.language.as_deref()))); + at = end; + } + let opts = Options::default(); + let mut found = vec![]; + // craft-proof checks one paragraph at a time (its language detection reads the paragraph), + // and the check stops at the paragraph where the list fills up rather than proofing the rest + // of the text. + let mut para_start = 0; + for para in text.split('\n') { + let para_end = para_start + para.len(); + let para_spans: Vec<(Range, ProofLang)> = spans + .iter() + .filter_map(|(r, lang)| { + let (a, b) = (r.start.max(para_start), r.end.min(para_end)); + (a < b).then(|| (a - para_start..b - para_start, *lang)) + }) + .collect(); + for issue in craft_proof::check_text_with(para, ¶_spans, &opts, false, false) { + if issue.kind != IssueKind::Spelling { + continue; + } + let (start, end) = (para_start + issue.start, para_start + issue.end); + let Some(word) = text.get(start..end) else { continue }; + if craft_proof::custom_word(word).is_some_and(|w| ignored.contains(&w)) { + continue; + } + if found.len() == max { + return (found, true); + } + found.push(Misspelling { range: start..end, lang: issue.lang }); + } + // Past the paragraph and its `\n`. + para_start = para_end + 1; + } + (found, false) +} + +/// Goes up each time the custom dictionary may have changed: an add, delete or change (saved or +/// not, since a save takes in the words another window saved), and a dictionary file attached. +static DICTIONARY_GENERATION: AtomicU64 = AtomicU64::new(0); + +/// The custom dictionary's generation: what [`misspellings`] found before it last changed may be +/// out of date (Auto Spell Check checks the type again). +pub fn dictionary_generation() -> u64 { + DICTIONARY_GENERATION.load(Ordering::Relaxed) +} + +fn dictionary_changed() { + DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed); +} + /// `ids` sorted back to front, as the document paints them: by their index paths, which are in /// paint order. fn in_paint_order(d: &Document, mut ids: Vec) -> Vec { @@ -246,7 +288,9 @@ fn add_word(s: &mut Session, p: &Value) -> Result { let word = word_param(p, "word", C)?; let added = !craft_proof::user_dictionary().contains(&word); craft_proof::add_word(&word); - save_dictionary(s, &[], C).inspect_err(|_| { + let saved = save_dictionary(s, &[], C); + dictionary_changed(); + saved.inspect_err(|_| { // Only this command's word: words the save took in from the file stay. if added { craft_proof::remove_word(&word); @@ -263,7 +307,9 @@ fn remove_word(s: &mut Session, p: &Value) -> Result { if !craft_proof::remove_word(&word) { return Err(bad(C, format!("`{word}` isn't in the custom dictionary"))); } - save_dictionary(s, std::slice::from_ref(&word), C).inspect_err(|_| craft_proof::add_word(&word))?; + let saved = save_dictionary(s, std::slice::from_ref(&word), C); + dictionary_changed(); + saved.inspect_err(|_| craft_proof::add_word(&word))?; Ok(json!({ "word": word })) } @@ -276,13 +322,17 @@ fn change_word(s: &mut Session, p: &Value) -> Result { return Err(bad(C, format!("`{word}` isn't in the custom dictionary"))); } if word == to { - save_dictionary(s, &[], C)?; + let saved = save_dictionary(s, &[], C); + dictionary_changed(); + saved?; return Ok(json!({ "word": to })); } let to_is_new = !craft_proof::user_dictionary().contains(&to); craft_proof::remove_word(&word); craft_proof::add_word(&to); - save_dictionary(s, std::slice::from_ref(&word), C).inspect_err(|_| { + let saved = save_dictionary(s, std::slice::from_ref(&word), C); + dictionary_changed(); + saved.inspect_err(|_| { if to_is_new { craft_proof::remove_word(&to); retake_file_words(s); @@ -315,5 +365,6 @@ impl Session { Err(why) => log::warn!("not reading the custom dictionary {}: {why}; it is left as it is", path.display()), } self.dictionary_file = Some(path); + dictionary_changed(); } } diff --git a/crates/engine/src/tests/spelling.rs b/crates/engine/src/tests/spelling.rs index 3005cae2f..0dfc9fab5 100644 --- a/crates/engine/src/tests/spelling.rs +++ b/crates/engine/src/tests/spelling.rs @@ -270,3 +270,43 @@ fn a_word_another_window_saved_stays_when_this_save_fails() { assert!(in_dictionary(&mut s, "zvkqxw"), "the word in the file stays, though this Add was taken back"); run(&mut s, "text.spelling.removeWord", json!({"word": "zvkqxw"})); } + +/// `misspellings` finds in one type object what the check lists for it, stops at the most it is +/// asked for and says so, and leaves out the Ignore All words it is given. +#[test] +fn misspellings_of_one_object_are_what_the_check_lists() { + use crate::cmd::spelling::misspellings; + let mut s = session(); + let id = text(&mut s, 50.0, "the Vqzrtwx cat\nsat Wzqrvtx Xqrzvtw"); + let listed: Vec<_> = unknown(&mut s, json!({"ids": [id.0]})).into_iter().map(|(_, a, b, _)| a as usize..b as usize).collect(); + let st = s.doc().unwrap(); + let Some(vectorcraft_doc::NodeKind::Text(t)) = st.doc.node(id).map(|n| n.kind.clone()) else { panic!("type") }; + let none = Default::default(); + let (found, cut) = misspellings(&t, &none, 10); + assert_eq!((found.iter().map(|m| m.range.clone()).collect::>(), cut), (listed.clone(), false)); + assert!(found.iter().all(|m| m.lang.tag() == "en"), "{found:?}"); + let (first, cut) = misspellings(&t, &none, 2); + assert_eq!((first.iter().map(|m| m.range.clone()).collect::>(), cut), (listed[..2].to_vec(), true)); + let ignored = std::collections::BTreeSet::from(["wzqrvtx".to_string()]); + let (found, _) = misspellings(&t, &ignored, 10); + assert_eq!(found.iter().map(|m| m.range.clone()).collect::>(), [listed[0].clone(), listed[2].clone()]); +} + +/// Each change to the custom dictionary moves its generation on, whether it is saved or not, so +/// what Auto Spell Check found before is checked again. +#[test] +fn custom_dictionary_changes_move_its_generation_on() { + use crate::cmd::spelling::dictionary_generation; + let mut s = session(); + let mut last = dictionary_generation(); + let mut moved = |s: &mut Session, id: &str, p: Value| { + let _ = s.execute(id, &p); + let now = dictionary_generation(); + let on = now > last; + last = now; + on + }; + assert!(moved(&mut s, "text.spelling.addWord", json!({"word": "Gqzvrtx"})), "add"); + assert!(moved(&mut s, "text.spelling.changeWord", json!({"word": "gqzvrtx", "to": "hqzvrtx"})), "change"); + assert!(moved(&mut s, "text.spelling.removeWord", json!({"word": "hqzvrtx"})), "delete"); +} diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index d02c59728..49775e241 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -81,6 +81,7 @@ impl VectorcraftApp { print_tiling: None, live_paint_gaps: None, hidden_chars: Default::default(), + spelling: Default::default(), selection_marks: Default::default(), selection_box: None, selection_bounds: None, diff --git a/crates/ui-egui/src/caches.rs b/crates/ui-egui/src/caches.rs index a9211eccf..4badbd628 100644 --- a/crates/ui-egui/src/caches.rs +++ b/crates/ui-egui/src/caches.rs @@ -45,6 +45,9 @@ pub struct CanvasCache { /// The marks Type → Show Hidden Characters drew on each text object in the last frame. A frame /// that does not draw them drops them ([`canvas::show`]). pub hidden_chars: canvas::HiddenChars, + /// The words Edit › Spelling › Auto Spell Check underlined on each text object in the last + /// frame; dropped while it is off. + pub spelling: canvas::SpellingMarks, /// The selected objects' outlines, anchors and handles as drawn in the last frame. A frame that /// does not draw them drops them ([`canvas::show`]). pub selection_marks: canvas::SelectionMarks, diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 8ab3cc068..69ba945b9 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -69,12 +69,12 @@ pub(crate) use raster::{color_image, color_image_owned, render_job}; pub use selection_marks::SelectionMarks; use task_bar::task_bar; pub(crate) use task_bar::{has_colors, task_bar_rect}; -pub use tool_overlays::HiddenChars; #[cfg(test)] use tool_overlays::{DIAL_RADIUS, HiddenMarks}; +pub use tool_overlays::{HiddenChars, SpellingMarks}; use tool_overlays::{ grid_repeat_overlay, hidden_chars_overlay, live_paint_gap_overlays, mirror_axis_overlay, nine_slice_overlay, original_path_overlay, - print_tiling_overlay, rotate_view_dial, shaper_widget_overlay, slice_overlay, thread_overlays, + print_tiling_overlay, rotate_view_dial, shaper_widget_overlay, slice_overlay, spelling_overlay, thread_overlays, }; pub(crate) use view::animated_zoom; pub use view::fit; @@ -1108,6 +1108,13 @@ fn selection_visuals( hidden_chars_overlay(app, painter, xf); } } + // Edit › Spelling › Auto Spell Check: shown with the edges hidden too, as it marks the type + // rather than the selection. + if app.ui.view.auto_spell_check && !revolve_edit { + spelling_overlay(app, painter, xf); + } else { + app.canvas.spelling = SpellingMarks::default(); + } overlays } diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index 8aa810928..df8e53de1 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -10727,3 +10727,100 @@ fn mac_cursor_headless(ctx: &egui::Context, input: egui::RawInput, draw: impl Fn output.textures_delta.clear(); output } + +/// The shapes Edit › Spelling › Auto Spell Check draws in one frame, seen through `xf`. +fn spelling_lines(app: &mut VectorcraftApp, ctx: &egui::Context, xf: &Xf) -> Vec { + let raw = egui::RawInput { screen_rect: Some(xf.rect), ..Default::default() }; + let mut out = ctx.run_ui(raw, |ui| spelling_overlay(app, ui.painter(), xf)); + out.textures_delta.clear(); + out.shapes.into_iter().map(|c| c.shape).collect() +} + +/// A document with one point type object of `text` at the top left, and a view of it. +fn spelling_doc(text: &str) -> (VectorcraftApp, u64, Xf) { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + let id = app.session.execute("text.create", &json!({"x": 20, "y": 60, "text": text})).unwrap()["id"].as_u64().unwrap(); + let xf = Xf { rect: egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0)), zoom: 1.0, center: Point::new(200.0, 150.0), rot: 0.0 }; + (app, id, xf) +} + +/// Auto Spell Check draws one wavy line under each unknown word, under its glyphs, and none under +/// the words the dictionaries know. +#[test] +fn auto_spell_check_underlines_the_unknown_words() { + let ctx = egui::Context::default(); + let (mut app, _, xf) = spelling_doc("the cat sat"); + assert!(spelling_lines(&mut app, &ctx, &xf).is_empty(), "every word is known"); + let (mut app, id, xf) = spelling_doc("the Qzvbtrw sat\nZvqtrbx"); + let lines = spelling_lines(&mut app, &ctx, &xf); + assert_eq!(lines.len(), 2, "one line under each unknown word: {lines:?}"); + // The first line runs under the second word: below the type's top and within its width. + let bounds = app.session.active().unwrap().doc.node(vectorcraft_doc::NodeId(id)).unwrap().geometric_bounds().unwrap(); + let on_screen = xf.rect_to_screen(bounds); + let Shape::Path(first) = &lines[0] else { panic!("{:?}", lines[0]) }; + let xs = || first.points.iter().map(|p| p.x); + let (left, right) = (xs().fold(f32::MAX, f32::min), xs().fold(f32::MIN, f32::max)); + assert!(left > on_screen.left() + 5.0 && right < on_screen.right() - 5.0, "under the middle word: {left}..{right} in {on_screen:?}"); + assert!(first.points.iter().all(|p| p.y > on_screen.top() && p.y < on_screen.center().y + on_screen.height()), "{first:?}"); + assert!(first.points.windows(2).any(|w| w[0].y != w[1].y), "wavy"); +} + +/// The lines follow the type and the dictionaries: an edit, Ignore All and Add to Dictionary each +/// change what is underlined in the next frame, and a frame with no change checks no type again. +#[test] +fn auto_spell_check_follows_edits_ignore_all_and_the_dictionary() { + let ctx = egui::Context::default(); + let (mut app, id, xf) = spelling_doc("Wqzxvtr and Pzxqvrt"); + let kept = |app: &VectorcraftApp| app.canvas.spelling.lines[&vectorcraft_doc::NodeId(id)].1.clone(); + assert_eq!(spelling_lines(&mut app, &ctx, &xf).len(), 2); + // Tests running alongside add fonts and words, which checks all type again: frames are drawn + // until neither changed between two of them. + let again = (0..100).any(|_| { + let (inputs, before) = (app.canvas.spelling.inputs.clone(), kept(&app)); + spelling_lines(&mut app, &ctx, &xf); + app.canvas.spelling.inputs == inputs && std::sync::Arc::ptr_eq(&kept(&app), &before) + }); + assert!(again, "a frame keeps the lines of the frame before"); + app.session.execute("text.setText", &json!({"id": id, "text": "Wqzxvtr and Pzxqvrt or Qxzvwtr"})).unwrap(); + assert_eq!(spelling_lines(&mut app, &ctx, &xf).len(), 3, "the edit is checked"); + app.session.execute("text.spelling.ignoreAll", &json!({"word": "wqzxvtr"})).unwrap(); + assert_eq!(spelling_lines(&mut app, &ctx, &xf).len(), 2, "Ignore All"); + app.session.execute("text.spelling.addWord", &json!({"word": "Pzxqvrt"})).unwrap(); + let after_add = spelling_lines(&mut app, &ctx, &xf).len(); + app.session.execute("text.spelling.removeWord", &json!({"word": "pzxqvrt"})).unwrap(); + assert_eq!(after_add, 1, "Add to Dictionary"); + assert_eq!(spelling_lines(&mut app, &ctx, &xf).len(), 2, "deleted from the dictionary again"); +} + +/// Type with an effect shown, and hidden type, has no lines: the helpx page leaves type with +/// effects out of Auto Spell Check. +#[test] +fn auto_spell_check_leaves_out_type_with_effects_and_hidden_type() { + let ctx = egui::Context::default(); + let (mut app, id, xf) = spelling_doc("Xqzvtrw"); + assert_eq!(spelling_lines(&mut app, &ctx, &xf).len(), 1); + app.session.execute("effect.apply", &json!({"ids": [id], "effect": "distort.roughen", "params": {"size": 1}})).unwrap(); + assert!(spelling_lines(&mut app, &ctx, &xf).is_empty(), "an effect"); + app.session.execute("edit.undo", &json!({})).unwrap(); + assert_eq!(spelling_lines(&mut app, &ctx, &xf).len(), 1, "undone"); + app.session.execute("object.hide", &json!({"ids": [id]})).unwrap(); + assert!(spelling_lines(&mut app, &ctx, &xf).is_empty(), "hidden"); +} + +/// Edit › Spelling › Auto Spell Check is a toggle, off at first: on, the canvas draws the lines; +/// off again, it draws none and keeps no text node. +#[test] +fn auto_spell_check_is_a_toggle_of_the_canvas_lines() { + let ctx = egui::Context::default(); + let (mut app, _, _) = spelling_doc("Yqzvtrx"); + let red = |s: &Shape| matches!(s, Shape::Path(p) if p.stroke.color == egui::epaint::ColorMode::Solid(Color32::from_rgb(0xe0, 0x20, 0x20))); + let count = |app: &mut VectorcraftApp| shapes(app, &ctx).iter().filter(|s| red(s)).count(); + assert!(!app.ui.view.auto_spell_check, "off at first"); + assert_eq!(count(&mut app), 0); + assert_eq!(app.run("edit.autoSpellCheck", json!({})).unwrap(), json!(true)); + assert_eq!(count(&mut app), 1, "on"); + assert_eq!(app.run("edit.autoSpellCheck", json!({})).unwrap(), json!(false)); + assert_eq!(count(&mut app), 0, "off"); + assert!(app.canvas.spelling.lines.is_empty(), "no node is kept"); +} diff --git a/crates/ui-egui/src/canvas/tool_overlays.rs b/crates/ui-egui/src/canvas/tool_overlays.rs index 7b88244f6..b90af09a4 100644 --- a/crates/ui-egui/src/canvas/tool_overlays.rs +++ b/crates/ui-egui/src/canvas/tool_overlays.rs @@ -105,6 +105,134 @@ impl HiddenMarks { } } +/// Edit › Spelling › Auto Spell Check: a red wavy line under each word of the visible type its +/// dictionaries don't know ([`vectorcraft_engine::cmd::spelling::misspellings`], what +/// `text.spelling.check` lists), in the run's language, without the active document's Ignore All +/// words. Type with effects isn't underlined +/// ([Check and correct spelling](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/check-and-correct-spelling.html)). +/// Each text object's lines are kept from one frame to the next ([`SpellingMarks`]). +pub(super) fn spelling_overlay(app: &mut VectorcraftApp, p: &egui::Painter, xf: &Xf) { + let kept = &mut app.canvas.spelling; + let Some(st) = app.session.active() else { + *kept = SpellingMarks::default(); + return; + }; + let clip = p.clip_rect(); + // `last` holds the lines of the last frame; the objects this frame doesn't draw are dropped. + let mut last = std::mem::take(&mut kept.lines); + let inputs = SpellingInputs { + fonts: vectorcraft_text::FontDb::global().generation(), + hyphenation: vectorcraft_text::hyphenation_exceptions(), + dictionary: vectorcraft_engine::cmd::spelling::dictionary_generation(), + doc: st.uid, + // Ignore All only adds words while the document is open. + ignored: st.spelling_ignored.len(), + }; + if kept.inputs.as_ref() != Some(&inputs) { + last.clear(); + kept.inputs = Some(inputs); + } + let stroke = Stroke::new(1.0, Color32::from_rgb(0xe0, 0x20, 0x20)); + // Depth first in paint order, leaving out hidden objects and what is inside them. + fn walk(nodes: &[std::sync::Arc], f: &mut impl FnMut(&std::sync::Arc)) { + for n in nodes.iter().filter(|n| n.visible) { + f(n); + if let Some(children) = n.children() { + walk(children, f); + } + } + } + walk(&st.doc.layers, &mut |n| { + let NodeKind::Text(tx) = &n.kind else { return }; + // An effect on the type or on one of its fills or strokes, shown (the Appearance panel's + // eye): the helpx page's "Auto spell check isn't available for text with effects applied". + let shown = |fx: &[vectorcraft_doc::Effect]| fx.iter().any(|e| e.visible); + if shown(&n.appearance.effects) + || n.appearance.items.iter().any(|i| shown(i.effects())) + || n.geometric_bounds().is_none_or(|b| !egui::Rect::from_points(&xf.quad(b)).intersects(clip)) + { + return; + } + let lines = match last.remove(&n.id) { + Some((was, lines)) if std::sync::Arc::ptr_eq(&was, n) => lines, + _ => std::sync::Arc::new(underlines(tx, &st.spelling_ignored)), + }; + for &(a, b) in lines.iter() { + wavy_line(p, xf.to_screen(a), xf.to_screen(b), stroke); + } + kept.lines.insert(n.id, (n.clone(), lines)); + }); +} + +/// The most words underlined in one text object: past it the rest of the object isn't checked, +/// so a long story of unknown words costs one check's worth. +const MAX_UNDERLINED: usize = 1_000; + +/// The document-space segments under the unknown words of `tx`: for each word, the bottom edge of +/// each piece of it on a line, from the start of the line to its end. +fn underlines(tx: &vectorcraft_doc::TextObject, ignored: &std::collections::BTreeSet) -> Vec<(Point, Point)> { + let (words, _) = vectorcraft_engine::cmd::spelling::misspellings(tx, ignored, MAX_UNDERLINED); + if words.is_empty() { + return vec![]; + } + let lay = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), tx); + words + .iter() + .flat_map(|w| vectorcraft_text::selection_quads(&lay, w.range.start, w.range.end)) + // Clockwise from the top left: q[3] and q[2] are the bottom left and right. + .map(|q| (tx.xf * q[3], tx.xf * q[2])) + .collect() +} + +/// A wavy line from `a` to `b` on screen, waves 4 pixels long and 1.5 high. +fn wavy_line(p: &egui::Painter, a: Pos2, b: Pos2, stroke: Stroke) { + const STEP: f32 = 2.0; + const HIGH: f32 = 1.5; + let d = b - a; + let len = d.length(); + if !len.is_finite() || len < 1.0 { + return; + } + let along = d / len; + // Down the page from the line, whichever way the type runs. + let across = vec2(-along.y, along.x); + // At most 2000 waves on one line, however far the view is zoomed in. + let n = ((len / STEP) as usize).clamp(1, 4_000); + let step = len / n as f32; + let points: Vec = (0..=n).map(|i| a + along * (step * i as f32) + across * if i % 2 == 0 { 0.0 } else { HIGH }).collect(); + p.add(Shape::line(points, stroke)); +} + +/// What Auto Spell Check's kept lines depend on beyond the text object itself: when any of it +/// changes, all type is checked again. +#[derive(Clone, Debug, PartialEq)] +pub(super) struct SpellingInputs { + /// The fonts' generation ([`vectorcraft_text::FontDb::generation`]). + fonts: u64, + /// The hyphenation exceptions, which move line breaks. + hyphenation: String, + /// The custom dictionary's generation + /// ([`vectorcraft_engine::cmd::spelling::dictionary_generation`]). + dictionary: u64, + /// The document the lines are of. + doc: u64, + /// How many Ignore All words the document has. + ignored: usize, +} + +/// The lines Auto Spell Check drew under each text object in the last frame +/// ([`crate::CanvasCache::spelling`]). Each is kept with the object it was found in: an edited +/// object is a new `Arc` and is checked again. +#[derive(Default)] +pub struct SpellingMarks { + pub(super) inputs: Option, + /// Each text object drawn, by id: the node it was checked in and its lines in document space. + pub(super) lines: std::collections::HashMap, Underlines)>, +} + +/// One text object's Auto Spell Check lines: the document-space ends of each. +pub(super) type Underlines = std::sync::Arc>; + /// View › Show Text Threads: for each thread with a selected frame, a line in its first frame's /// layer colour from each frame's out port to the next frame's in port, both showing the arrow of a /// threaded port, where the tools draw the ports of selected type ([`vectorcraft_tools::text`]). diff --git a/crates/ui-egui/src/menus/run.rs b/crates/ui-egui/src/menus/run.rs index 8821679e8..9ec98faae 100644 --- a/crates/ui-egui/src/menus/run.rs +++ b/crates/ui-egui/src/menus/run.rs @@ -267,6 +267,7 @@ fn run_ui_part3(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option flag(&mut app.ui.view.text_threads), "type.hiddenCharacters" => flag(&mut app.ui.view.hidden_chars), + "edit.autoSpellCheck" => flag(&mut app.ui.view.auto_spell_check), "type.bold" => crate::panels::character::toggle_face(app, Face::Bold), "type.italic" => crate::panels::character::toggle_face(app, Face::Italic), "type.highlightFontName" => crate::panels::character::highlight(app, crate::panels::character::Field::FontName), diff --git a/crates/ui-egui/src/menus/state.rs b/crates/ui-egui/src/menus/state.rs index c0a0c04ba..1941504e5 100644 --- a/crates/ui-egui/src/menus/state.rs +++ b/crates/ui-egui/src/menus/state.rs @@ -51,6 +51,7 @@ pub fn checked(app: &VectorcraftApp, id: &str, p: &Value) -> Option { selected_text(true).is_some_and(|t| p.get("effect").and_then(Value::as_str) == Some(t.path_effect.id())) } "view.smartGuides" => v.smart_guides, + "edit.autoSpellCheck" => v.auto_spell_check, "view.grid" => v.grid, "view.snapToGrid" => v.snap_to_grid, "view.snapToPoint" => v.snap_to_point, diff --git a/crates/ui-egui/src/menus/tree.rs b/crates/ui-egui/src/menus/tree.rs index fdea9664b..c51f36e7d 100644 --- a/crates/ui-egui/src/menus/tree.rs +++ b/crates/ui-egui/src/menus/tree.rs @@ -97,7 +97,10 @@ pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec)> { Sep, cp("Find and Replace…", "edit.findReplace", json!({"find": "", "replace": "", "matchCase": false, "wholeWord": false})), cp("Find Next", "edit.findNext", json!({"find": ""})), - sub("Spelling", vec![todo("Auto Spell Check"), todos("Check Spelling…", "Cmd+I"), todo("Edit Custom Dictionary…")]), + sub( + "Spelling", + vec![c("Auto Spell Check", "edit.autoSpellCheck"), todos("Check Spelling…", "Cmd+I"), todo("Edit Custom Dictionary…")], + ), Sep, sub( "Edit Colors", diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index 930808b64..3b314f833 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -162,6 +162,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ ("app.home", "Home", "", "{} shows the Home screen (new file presets, Open) over the open documents; choosing a document tab returns to it"), ("edit.preferences", "Preferences…", "Cmd+K", "{category?} open Preferences (engine: prefs.get / prefs.set / prefs.list)"), ("edit.keyboardShortcuts", "Keyboard Shortcuts…", "Cmd+Alt+Shift+K", "{}"), + ( + "edit.autoSpellCheck", + "Auto Spell Check", + "", + "{} toggle a red underline under the words of the document's type its dictionaries don't know (text.spelling.check lists them), kept up to date as the type changes; type with effects isn't underlined → whether Auto Spell Check is on", + ), ("shortcuts.set", "Set Keyboard Shortcut", "", "{id: command id or tool:, shortcut: \"Cmd+Shift+K\" | \"\" (none) | null (default), force?}"), ("shortcuts.list", "List Keyboard Shortcuts", "", "{query?} → [{id, label, group, shortcut, default, overridden}]"), ("shortcuts.conflicts", "Keyboard Shortcut Conflicts", "", "{} → [{shortcut, ids}]"), diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 95ffcd9cd..64574e52d 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -267,6 +267,10 @@ pub struct ViewFlags { pub live_paint_gaps: bool, /// Type → Show Hidden Characters. pub hidden_chars: bool, + /// Edit › Spelling › Auto Spell Check: a red underline under the words of the type its + /// dictionaries don't know. + #[serde(default)] + pub auto_spell_check: bool, /// View → Hide/Show Template: template layers drawn (dimmed) or left out of the canvas. #[serde(default = "yes")] pub templates: bool, @@ -294,6 +298,7 @@ impl Default for ViewFlags { gradient_annotator: true, live_paint_gaps: false, hidden_chars: false, + auto_spell_check: false, templates: true, } } diff --git a/docs/gaps.md b/docs/gaps.md index a3b17ea0d..a62d829f3 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Gaps -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; G5: built-in UI Frames symbol library with default 9-slice; estimate 11–21 h; previously minor: G5: dynamic symbol part on-canvas takes opacity, blend, gradient and pattern; docs Overlap/auto-corners sync; estimate 12–22 h; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; previously minor: Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; previously minor: G5 docs: Art brush Overlap, Pattern brush auto corners and Global Edit Shift-click leave-out moved from Missing to Done (code already shipped); previously minor: custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: EPS import: embedded fonts (composite, Type 42, CID-keyed, Type 1, CFF FontSet) draw their glyphs; spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; G9: EPS gradients drawn as abutting strips import as one gradient, without seams; Tools stays beside the canvas with left-side panels; new gradients follow document color mode; Selection QoL: marquee modes and visible feedback, #5313; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Indian-language hyphenation review and license allowlist, #4947; opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (Auto Spell Check: `edit.autoSpellCheck` underlines the unknown words, #4936; previously minor: PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; G5: built-in UI Frames symbol library with default 9-slice; estimate 11–21 h; previously minor: G5: dynamic symbol part on-canvas takes opacity, blend, gradient and pattern; docs Overlap/auto-corners sync; estimate 12–22 h; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; previously minor: Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; previously minor: G5 docs: Art brush Overlap, Pattern brush auto corners and Global Edit Shift-click leave-out moved from Missing to Done (code already shipped); previously minor: custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: EPS import: embedded fonts (composite, Type 42, CID-keyed, Type 1, CFF FontSet) draw their glyphs; spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; G9: EPS gradients drawn as abutting strips import as one gradient, without seams; Tools stays beside the canvas with left-side panels; new gradients follow document color mode; Selection QoL: marquee modes and visible feedback, #5313; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Indian-language hyphenation review and license allowlist, #4947; opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -447,7 +447,7 @@ macOS cursor delivery (#2292) is narrowed to a native host adapter using the exi - **Missing:** CJK vertical composition (mojikumi sets and dialog, kinsoku settings #633, manual tate-chu-yoko), Middle Eastern features beyond bidi, digit types, Character Direction and Kashidas (Middle Eastern composers, a split caret), spell check (its engine is done, #4936: `text.spelling.check` and - `text.spelling.suggest` on craft-proof; the custom dictionary and Ignore All commands; the Check Spelling and Edit Custom Dictionary dialogs and Auto Spell Check remain), + `text.spelling.suggest` on craft-proof; the custom dictionary and Ignore All commands; Auto Spell Check, `edit.autoSpellCheck`; the Check Spelling and Edit Custom Dictionary dialogs and the right-click Spelling menu remain), Touch Type, Retype, Snap to Glyph; optional preservation of font sizes during group scaling (#1034 enhancement). Itemized in [type-parity.md](type-parity.md). - **Impact:** professional typography and Japanese/Chinese publishing. @@ -851,6 +851,7 @@ trap, and the defaults (0.25 pt, 100%, 40%) are VectorCraft's own, the pages giv | Date | Change | Summary | |---|---|---| +| 2026-11-09 | minor | G4 type: Auto Spell Check (`edit.autoSpellCheck`) underlines the unknown words of the visible type, #4936; the Check Spelling and Edit Custom Dictionary dialogs and the right-click Spelling menu remain; scores and estimates unchanged. | | 2026-11-09 | minor | G5: built-in UI Frames symbol library (Panel, Button, Bar with default 9-slice); Missing trimmed; estimate 12–22 → 11–21 h. | | 2026-11-09 | minor | G5: Direct Selection of a dynamic instance part takes opacity, blend, gradient and pattern fill/stroke; Art brush Overlap and Pattern auto corners documented Done; estimate 15–25 → 12–22 h. | | 2026-11-09 | minor | G5: Art brush Overlap, Pattern brush auto corners and Global Edit Shift-click leave-out documented as Done (already in code); Missing trimmed; estimate unchanged. | diff --git a/docs/mcp.md b/docs/mcp.md index fb9b3cfc5..8f605eee0 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -5933,6 +5933,10 @@ any case, while the document is open. It is not saved and not an undo step. {"name":"run_command","arguments":{"command":"text.spelling.ignoreAll","params":{"word":"Zqvwkx"}}} ``` +In the app, Edit › Spelling › Auto Spell Check (the UI command `edit.autoSpellCheck`, which returns whether it is now +on) underlines on the canvas the words `text.spelling.check` lists, in the visible type without effects, and keeps the +lines up to date as the type, the custom dictionary and the Ignore All words change. + ## Justification options diff --git a/docs/roadmap.md b/docs/roadmap.md index 323495de3..d23133073 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap: milestones and what's next -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: EPS import: embedded fonts (composite, Type 42, CID-keyed, Type 1, CFF FontSet) draw their glyphs; spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; M4/M7: `.ai`/EPS type keeps its tab stops, and right-aligned lines with tabs lay out as the file's page, #6147; M4/M7: `.ai`/EPS type from the text document lays its lines out closer to the file's page, within limits, #6137; numeric fields step on wheel hover without taking keyboard focus, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Selection QoL: marquee modes and visible feedback, #5313; SVG CSS classes survive export and repeated import in all four styling modes (#2480); current-main integration retains event attributes and scripts; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Indian-language hyphenation review and license allowlist, #4947; Hindi catalog and contributor loanword preferences; full wording review in progress; Effect dialogs: a colour swatch beside a hex colour opens the Color Picker, #5365; shape tools move the selection from its centre point and turn it from the zone outside a box corner, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (Auto Spell Check: `edit.autoSpellCheck` underlines the unknown words, #4936; previously minor: PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: EPS import: embedded fonts (composite, Type 42, CID-keyed, Type 1, CFF FontSet) draw their glyphs; spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; M4/M7: `.ai`/EPS type keeps its tab stops, and right-aligned lines with tabs lay out as the file's page, #6147; M4/M7: `.ai`/EPS type from the text document lays its lines out closer to the file's page, within limits, #6137; numeric fields step on wheel hover without taking keyboard focus, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Selection QoL: marquee modes and visible feedback, #5313; SVG CSS classes survive export and repeated import in all four styling modes (#2480); current-main integration retains event attributes and scripts; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Indian-language hyphenation review and license allowlist, #4947; Hindi catalog and contributor loanword preferences; full wording review in progress; Effect dialogs: a colour swatch beside a hex colour opens the Color Picker, #5365; shape tools move the selection from its centre point and turn it from the zone outside a box corner, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -137,9 +137,9 @@ native wording review, layout checks and a web Devanagari font remain. [Notes](l | M3 | Paint & appearance (swatches, color, gradient, stroke, appearance, transparency, styles) | ✅ done (M3.7–M3.98): swatches, groups, libraries, tints, Lab spots and Registration; Eyedropper styles highlighted text only (atomic transparency rejection); Eyedropper reads visible locked and outside-isolation sources (#1251); Color Picker, Color panel, Color Guide, Color Themes, Edit Colors and Recolor Artwork (screen-bounded Edit chip list, #3416); gradients that follow every transform, the annotator, linked stops, freeform gradients, focal points, gradients on strokes and Expand; visible strokes when paint is added to inherited None/zero-width art (#1253); stroke geometry, arrowheads, dashes fitted to corners, stroke on type, width profiles and the Width tool (Shift-double-click enables adjoining adjustment in Width Point Edit), Scale Strokes and preview bounds; Appearance targeting, container appearance, target circles, Expand Appearance; knockout, isolation, blend accuracy, CMYK blending, overprint, Attributes, masks and clipping, Flatten Transparency; graphic styles, links and libraries; CSS library export preserves fractional linear and radial gradient stop positions. Left: freeform gradients in SVG/PDF, mixed spot/process gradients in SVG and PDF gradients mixing a spot color with RGB or Lab stops export as stops or process colors; PDF export writes dithered gradient fills with spot stops, and Perceptual spot gradients with RGB or Lab stops other than white or more than 4,096 stops on their curve, as images in process colors; confirm the Scale Strokes & Effects default | 4–8 | | M5 | Performance | 🟡 background render + caches + MT done; `vectorcraft-cli bench` and `vectorcraft-cli perf` (budget suite); opaque solid-fill knockout paths draw once in hard-edge/supersampled raster export, with analytical-AA compositing unchanged; file format v2 opens 3× faster (50k paths: 722 → 244 ms); raster effects (glows, shadows, blur, feather) no longer force the whole frame single-threaded (filtered offscreen per effect, verified equal to the single-threaded reference); Craquelure/Mosaic Tiles/Stained Glass distance searches optimized with pixel-invariance checks and the `texture_drag_perf` probe (#1257); effect-heavy demos need a clean-machine benchmark; dirty-region rendering, GPU backend spike pending | 15–25 | | M6 | Path operations (Pathfinder, Shape Builder, offset…) | ✅ mostly done (live compound shapes: Make, Alt-click a Shape Mode, member modes, Release, Expand (#4522); single-result Pathfinder names and SVG IDs retained (#1293); Divide open-cutting-path geometry, appearance and history verified (#1307; existing segment cap remains); Simplify preview/counts and rewritten-path component selection verified (#1272; the slider opens at the Auto-Simplify level; a Direct Selection part of a path simplifies on its own); Compound appearance and member fill-rule edits verified (#1197); Boolean precision on almost-horizontal edges fixed and the property tests made deterministic; Shape Builder gap detection, large-offset bug open); Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog | 3–6 | -| M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | Spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof and the custom dictionary and Ignore All commands (#4936; the dialogs and Auto Spell Check remain); Right-aligned area type sets the text after a line's last tab against the right edge, the text before it at its stops (#6147); A line a soft return ends is justified as a wrapped line is (#6137); Indian-language pattern hyphenation preserves syllables in both composers (#4947); 🟡 Hanging punctuation keeps its published amounts under Glyph Scaling, and Standard burasagari counts a hung line start as room; Fullwidth `!` / `?` keep an upright fallback in vertical type; Document-point character controls after object/group scaling (#1034; optional font-size preservation remains separate), Character/Paragraph Styles (explicit Auto-leading resets and local leading overrides), per-paragraph paragraph attributes, mixed paragraph alignment indication (headless regressions, full integration CI and visual review pass), Area Type Options (with Width/Height, Auto Size and Shrink Text to Fit; overflow reported to agents and vertical Align), type areas resized by their handles and reshaped with Direct Selection, threaded text, Fit Headline, Glyphs, OpenType panel (including font-defined Stylistic Sets), Find Font (Document, Recent and System replacement sources, #5217), installed system fonts in every font menu (fonts registered with Windows outside its font folders, the XDG and Flatpak host font folders on Linux, and fonts installed while the app runs, listed when it comes back to the front; searchable; the Character and Properties menus show each family's sample of the selected text, preview it live under the pointer or ↑/↓, show recently applied families first (#3515), filter by script and kind, list a family's styles, and star favourites; the Glyphs panel and Find Font's Replace With use the same menu without the live preview) and found by name on open/import (full-name aliases and Missing Fonts searches verified, #1258; Windows font-service QA pending), variable fonts' named instances as styles, CJK names in the UI, input methods (marked text in place, candidate window at the caret; Japanese IME), Text Wrap (offset, invert, both sides of an object; follows edits), Type on a Path effects and Options with draggable start/centre/end brackets (slide, flip), tab stops + Tabs panel (Above Text floats it over the selected text, its ruler at the canvas zoom so stops line up with the columns, #729), inline symbols in text (beyond parity), Hebrew/Arabic bidirectional text, Paragraph Direction, Every-line/Single-line Composer per object (ragged text too) done; paint-only Arabic harakat keep joining with GDEF-mark ownership and Insert Kashidas tatweels on the base letter's paint (`tests::harakat`); tab leaders, spell check, vertical type pending | 45–60 | -| M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | Spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof and the custom dictionary and Ignore All commands (#4936; the dialogs and Auto Spell Check remain); Right-aligned area type sets the text after a line's last tab against the right edge, the text before it at its stops (#6147); A line a soft return ends is justified as a wrapped line is (#6137); Indian-language pattern hyphenation preserves syllables in both composers (#4947); 🟡 Fullwidth `!` / `?` keep an upright fallback in vertical type; Hyphenation… and Justification… per paragraph (#2310, #2755), Auto Leading also driving the Character panel's Leading, the Alt+↑/↓ leading nudge and Fill with Placeholder Text; Document-point character controls after object/group scaling (#1034; optional font-size preservation remains separate), Character/Paragraph Styles (explicit Auto-leading resets and local leading overrides), per-paragraph paragraph attributes, mixed paragraph alignment indication (headless regressions, full integration CI and visual review pass), Area Type Options (with Width/Height, Auto Size and Shrink Text to Fit; overflow reported to agents and vertical Align), type areas resized by their handles and reshaped with Direct Selection, threaded text, Fit Headline, Glyphs, OpenType panel, Find Font (Document, Recent and System replacement sources, #5217), installed system fonts in every font menu (fonts registered with Windows outside its font folders, the XDG and Flatpak host font folders on Linux, and fonts installed while the app runs, listed when it comes back to the front; searchable; the Character and Properties menus show each family's sample of the selected text, preview it live under the pointer or ↑/↓, filter by script and kind, list a family's styles, and star favourites; the Glyphs panel and Find Font's Replace With use the same menu without the live preview) and found by name on open/import (full-name aliases and Missing Fonts searches verified, #1258; Windows font-service QA pending), variable fonts' named instances as styles, CJK names in the UI, input methods (marked text in place, candidate window at the caret; Japanese IME), Text Wrap (offset, invert, both sides of an object; follows edits), Type on a Path effects and Options with draggable start/centre/end brackets (slide, flip), tab stops + Tabs panel (Above Text floats it over the selected text, its ruler at the canvas zoom so stops line up with the columns, #729), inline symbols in text (beyond parity), Hebrew/Arabic bidirectional text, Paragraph Direction, Every-line/Single-line Composer per object (ragged text too) done; paint-only Arabic harakat keep joining with GDEF-mark ownership and Insert Kashidas tatweels on the base letter's paint (`tests::harakat`); tab leaders, spell check, vertical type pending | 45–60 | -| M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | Spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof and the custom dictionary and Ignore All commands (#4936; the dialogs and Auto Spell Check remain); Right-aligned area type sets the text after a line's last tab against the right edge, the text before it at its stops (#6147); A line a soft return ends is justified as a wrapped line is (#6137); Indian-language pattern hyphenation preserves syllables in both composers (#4947); 🟡 Fullwidth `!` / `?` keep an upright fallback in vertical type; Document-point character controls after object/group scaling (#1034; optional font-size preservation remains separate), Character/Paragraph Styles (explicit Auto-leading resets and local leading overrides), per-paragraph paragraph attributes, mixed paragraph alignment indication (headless regressions, full integration CI and visual review pass), Area Type Options (with Width/Height, Auto Size and Shrink Text to Fit; overflow reported to agents and vertical Align), type areas resized by their handles and reshaped with Direct Selection, threaded text, Fit Headline, Glyphs, OpenType panel, Find Font (Document, Recent and System replacement sources, #5217), installed system fonts in every font menu (fonts registered with Windows outside its font folders, the XDG and Flatpak host font folders on Linux, and fonts installed while the app runs, listed when it comes back to the front; searchable; the Character and Properties menus show each family's sample of the selected text, preview it live under the pointer or ↑/↓, show recently applied families first (#3515), filter by script and kind, list a family's styles, and star favourites; the Glyphs panel and Find Font's Replace With use the same menu without the live preview) and found by name on open/import (full-name aliases and Missing Fonts searches verified, #1258; Windows font-service QA pending), variable fonts' named instances as styles, CJK names in the UI, input methods (marked text in place, candidate window at the caret; Japanese IME), Text Wrap (offset, invert, both sides of an object; follows edits), Type on a Path effects and Options with draggable start/centre/end brackets (slide, flip), tab stops + Tabs panel (Above Text floats it over the selected text, its ruler at the canvas zoom so stops line up with the columns, #729), inline symbols in text (beyond parity), Hebrew/Arabic bidirectional text, Paragraph Direction, Every-line/Single-line Composer per object (ragged text too) done; paint-only Arabic harakat keep joining with GDEF-mark ownership and Insert Kashidas tatweels on the base letter's paint (`tests::harakat`); tab leaders, spell check, vertical type pending | 45–60 | +| M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | Spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof and the custom dictionary and Ignore All commands and Auto Spell Check (#4936; the dialogs and the right-click Spelling menu remain); Right-aligned area type sets the text after a line's last tab against the right edge, the text before it at its stops (#6147); A line a soft return ends is justified as a wrapped line is (#6137); Indian-language pattern hyphenation preserves syllables in both composers (#4947); 🟡 Hanging punctuation keeps its published amounts under Glyph Scaling, and Standard burasagari counts a hung line start as room; Fullwidth `!` / `?` keep an upright fallback in vertical type; Document-point character controls after object/group scaling (#1034; optional font-size preservation remains separate), Character/Paragraph Styles (explicit Auto-leading resets and local leading overrides), per-paragraph paragraph attributes, mixed paragraph alignment indication (headless regressions, full integration CI and visual review pass), Area Type Options (with Width/Height, Auto Size and Shrink Text to Fit; overflow reported to agents and vertical Align), type areas resized by their handles and reshaped with Direct Selection, threaded text, Fit Headline, Glyphs, OpenType panel (including font-defined Stylistic Sets), Find Font (Document, Recent and System replacement sources, #5217), installed system fonts in every font menu (fonts registered with Windows outside its font folders, the XDG and Flatpak host font folders on Linux, and fonts installed while the app runs, listed when it comes back to the front; searchable; the Character and Properties menus show each family's sample of the selected text, preview it live under the pointer or ↑/↓, show recently applied families first (#3515), filter by script and kind, list a family's styles, and star favourites; the Glyphs panel and Find Font's Replace With use the same menu without the live preview) and found by name on open/import (full-name aliases and Missing Fonts searches verified, #1258; Windows font-service QA pending), variable fonts' named instances as styles, CJK names in the UI, input methods (marked text in place, candidate window at the caret; Japanese IME), Text Wrap (offset, invert, both sides of an object; follows edits), Type on a Path effects and Options with draggable start/centre/end brackets (slide, flip), tab stops + Tabs panel (Above Text floats it over the selected text, its ruler at the canvas zoom so stops line up with the columns, #729), inline symbols in text (beyond parity), Hebrew/Arabic bidirectional text, Paragraph Direction, Every-line/Single-line Composer per object (ragged text too) done; paint-only Arabic harakat keep joining with GDEF-mark ownership and Insert Kashidas tatweels on the base letter's paint (`tests::harakat`); tab leaders, spell check, vertical type pending | 45–60 | +| M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | Spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof and the custom dictionary and Ignore All commands and Auto Spell Check (#4936; the dialogs and the right-click Spelling menu remain); Right-aligned area type sets the text after a line's last tab against the right edge, the text before it at its stops (#6147); A line a soft return ends is justified as a wrapped line is (#6137); Indian-language pattern hyphenation preserves syllables in both composers (#4947); 🟡 Fullwidth `!` / `?` keep an upright fallback in vertical type; Hyphenation… and Justification… per paragraph (#2310, #2755), Auto Leading also driving the Character panel's Leading, the Alt+↑/↓ leading nudge and Fill with Placeholder Text; Document-point character controls after object/group scaling (#1034; optional font-size preservation remains separate), Character/Paragraph Styles (explicit Auto-leading resets and local leading overrides), per-paragraph paragraph attributes, mixed paragraph alignment indication (headless regressions, full integration CI and visual review pass), Area Type Options (with Width/Height, Auto Size and Shrink Text to Fit; overflow reported to agents and vertical Align), type areas resized by their handles and reshaped with Direct Selection, threaded text, Fit Headline, Glyphs, OpenType panel, Find Font (Document, Recent and System replacement sources, #5217), installed system fonts in every font menu (fonts registered with Windows outside its font folders, the XDG and Flatpak host font folders on Linux, and fonts installed while the app runs, listed when it comes back to the front; searchable; the Character and Properties menus show each family's sample of the selected text, preview it live under the pointer or ↑/↓, filter by script and kind, list a family's styles, and star favourites; the Glyphs panel and Find Font's Replace With use the same menu without the live preview) and found by name on open/import (full-name aliases and Missing Fonts searches verified, #1258; Windows font-service QA pending), variable fonts' named instances as styles, CJK names in the UI, input methods (marked text in place, candidate window at the caret; Japanese IME), Text Wrap (offset, invert, both sides of an object; follows edits), Type on a Path effects and Options with draggable start/centre/end brackets (slide, flip), tab stops + Tabs panel (Above Text floats it over the selected text, its ruler at the canvas zoom so stops line up with the columns, #729), inline symbols in text (beyond parity), Hebrew/Arabic bidirectional text, Paragraph Direction, Every-line/Single-line Composer per object (ragged text too) done; paint-only Arabic harakat keep joining with GDEF-mark ownership and Insert Kashidas tatweels on the base letter's paint (`tests::harakat`); tab leaders, spell check, vertical type pending | 45–60 | +| M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | Spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof and the custom dictionary and Ignore All commands and Auto Spell Check (#4936; the dialogs and the right-click Spelling menu remain); Right-aligned area type sets the text after a line's last tab against the right edge, the text before it at its stops (#6147); A line a soft return ends is justified as a wrapped line is (#6137); Indian-language pattern hyphenation preserves syllables in both composers (#4947); 🟡 Fullwidth `!` / `?` keep an upright fallback in vertical type; Document-point character controls after object/group scaling (#1034; optional font-size preservation remains separate), Character/Paragraph Styles (explicit Auto-leading resets and local leading overrides), per-paragraph paragraph attributes, mixed paragraph alignment indication (headless regressions, full integration CI and visual review pass), Area Type Options (with Width/Height, Auto Size and Shrink Text to Fit; overflow reported to agents and vertical Align), type areas resized by their handles and reshaped with Direct Selection, threaded text, Fit Headline, Glyphs, OpenType panel, Find Font (Document, Recent and System replacement sources, #5217), installed system fonts in every font menu (fonts registered with Windows outside its font folders, the XDG and Flatpak host font folders on Linux, and fonts installed while the app runs, listed when it comes back to the front; searchable; the Character and Properties menus show each family's sample of the selected text, preview it live under the pointer or ↑/↓, show recently applied families first (#3515), filter by script and kind, list a family's styles, and star favourites; the Glyphs panel and Find Font's Replace With use the same menu without the live preview) and found by name on open/import (full-name aliases and Missing Fonts searches verified, #1258; Windows font-service QA pending), variable fonts' named instances as styles, CJK names in the UI, input methods (marked text in place, candidate window at the caret; Japanese IME), Text Wrap (offset, invert, both sides of an object; follows edits), Type on a Path effects and Options with draggable start/centre/end brackets (slide, flip), tab stops + Tabs panel (Above Text floats it over the selected text, its ruler at the canvas zoom so stops line up with the columns, #729), inline symbols in text (beyond parity), Hebrew/Arabic bidirectional text, Paragraph Direction, Every-line/Single-line Composer per object (ragged text too) done; paint-only Arabic harakat keep joining with GDEF-mark ownership and Insert Kashidas tatweels on the base letter's paint (`tests::harakat`); tab leaders, spell check, vertical type pending | 45–60 | | M8 | Transform & distort (Puppet Warp, Liquify tools, Envelopes, Blends, Perspective Grid) | ✅ done (M8.1–M8.20 on 2026-10-06, fidelity pass): Liquify Tool Options with one shared brush, pen pressure, hold-to-apply, a safe scope and incremental strokes; tool options that last; Puppet Warp rotation, multi-select, Control bar and a rest-shape session that follows Undo; Width tool on compound paths; Envelope dialogs with preview, Reset with Warp/Mesh, full Envelope Options, type/images/symbols/appearance/gradients/patterns distorted on canvas and in every export, warps that keep their frame, Edit Contents, mesh handles and Control bar; Blend Options, anchor-targeted Blend tool, editable spine, keys picked on the canvas, faithful interpolation and knockout; Anchor Point spine conversion/handle editing and selection priority (#2544), with #2425 Shift reshape preserved; Perspective Grid with Define Grid, presets, lock/snap/rulers, widgets, the 1–4 plane switch, movable planes, Perspective Selection scale/copy/perpendicular/Transform Again, and type and symbols truly projected with Edit Text. Left: Anti-Alias and Preserve Shape output, warp-envelope point editing, brush interpolation in blends, Free Transform/Scale/Reflect along a rotated box | 2–4 | | M9 | Live effects (+ 3D & Materials) | 🟡 2D effects done incl. Effect → Pathfinder, Color Adjustments and Document Raster Effects Settings; PDF export warns when the pixel cap lowers raster-effect resolution; Artistic › Colored Pencil, Cutout, Dry Brush, Film Grain, Fresco, Neon Glow, Paint Daubs, Palette Knife, Poster Edges, Rough Pastels, Smudge Stick, Sponge, Underpainting and Watercolor and Sketch › Halftone Pattern, Bas Relief, Charcoal, Conté Crayon, Photocopy, Stamp, Torn Edges and Water Paper done (57/57 Photoshop filters); Revolve with automatic smooth vector shading, default-on circular caps and continuous rims, Extrude & Bevel, Inflate and Rotate with one to ten colored directional lights and four original lighting presets; SVG Filters, remaining raster effects and 3D materials and shadows pending | 81–130 | | M10 | Brushes, symbols, patterns, Repeat | 🟡 pattern swatches (5 tile types, Pattern Options, editing mode, SVG ``/PDF export) and live Repeat (radial/grid/mirror) done; interactive selected mirror axis implemented (focused and native checks passed; full CI blocked by an unrelated text test); symbol libraries done (Window › Symbol Libraries, a generated Basic Shapes library, User Defined, Other Library…, Save Symbols); brush libraries done (Window › Brush Libraries, four generated libraries, User Defined, Other Library…, Save Brushes); symbol editing shows draggable 9-slice guides (#5280), with guides outside the artwork still unsupported; brushes/symbols in progress | 23–37 | @@ -190,6 +190,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-11-09 | minor | M7: Auto Spell Check (#4936); the dialogs and the right-click Spelling menu remain; scores and estimates unchanged. | | 2026-11-09 | minor | PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged. Decoded stream and placement regressions; reader ActualText (#6165), styled clusters and external viewer validation remain separate. | | 2026-11-09 | minor | Interim macOS canvas cursor delivery (#2292): existing artwork in aligned 1×/2× AppKit representations, visible system fallback and host-owned restoration. Headless routing, bitmap and ownership coverage; native timing, menus, focus/handback, mixed displays, Spotlight and full-screen Spaces remain unverified. Scores and estimates unchanged. | | 2026-11-09 | minor | M7: the custom dictionary commands and Ignore All (#4936); the dialogs and Auto Spell Check remain; scores and estimates unchanged. | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 1c11ada4b..2effc1142 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; G5: UI Frames built-in symbol library with default 9-slice; bundled content ~32%; previously minor: G5: dynamic symbol part on-canvas opacity/blend/gradient/pattern; strokes remaining no longer lists Overlap/auto corners; symbols ~88%; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; previously minor: harakat paint fallback clarified; previously minor: current Guides feature-area rows consolidated using existing ruler evidence; scores and estimates unchanged; previously minor: Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; previously minor: G5 docs: Art brush Overlap and Pattern brush auto corners removed from strokes remaining; symbols remaining lists dynamic on-canvas beyond solid and guides outside the art; previously minor: custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: EPS import: embedded fonts (composite, Type 42, CID-keyed, Type 1, CFF FontSet) draw their glyphs; spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; `.ai`/EPS type keeps its tab stops, and right-aligned lines with tabs lay out as the file's page, #6147; `.ai`/EPS type from the text document lays its lines out closer to the file's page, within limits, #6137; `.ai` and EPS import: older Art brush objects read; numeric fields step on wheel hover without taking keyboard focus, and a panel wheel turn cannot seed a canvas zoom glide, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Tools stays beside the canvas with left-side panels; new gradients follow document color mode; Selection QoL: marquee modes and visible feedback, #5313; SVG CSS classes survive export and repeated import in all four styling modes (#2480); current-main integration retains event attributes and scripts; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026; Units › East Asian Type acts, with Q and Ha units; scores unchanged) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (Auto Spell Check: `edit.autoSpellCheck` underlines the unknown words, #4936; previously minor: PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; G5: UI Frames built-in symbol library with default 9-slice; bundled content ~32%; previously minor: G5: dynamic symbol part on-canvas opacity/blend/gradient/pattern; strokes remaining no longer lists Overlap/auto corners; symbols ~88%; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; previously minor: harakat paint fallback clarified; previously minor: current Guides feature-area rows consolidated using existing ruler evidence; scores and estimates unchanged; previously minor: Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; previously minor: G5 docs: Art brush Overlap and Pattern brush auto corners removed from strokes remaining; symbols remaining lists dynamic on-canvas beyond solid and guides outside the art; previously minor: custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: EPS import: embedded fonts (composite, Type 42, CID-keyed, Type 1, CFF FontSet) draw their glyphs; spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; `.ai`/EPS type keeps its tab stops, and right-aligned lines with tabs lay out as the file's page, #6147; `.ai`/EPS type from the text document lays its lines out closer to the file's page, within limits, #6137; `.ai` and EPS import: older Art brush objects read; numeric fields step on wheel hover without taking keyboard focus, and a panel wheel turn cannot seed a canvas zoom glide, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Tools stays beside the canvas with left-side panels; new gradients follow document color mode; Selection QoL: marquee modes and visible feedback, #5313; SVG CSS classes survive export and repeated import in all four styling modes (#2480); current-main integration retains event attributes and scripts; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026; Units › East Asian Type acts, with Q and Ha units; scores unchanged) ## Feature submission evidence @@ -212,15 +212,15 @@ dominate; 3D, graphs and automation are specialist). They sum to 105. Feature de | Appearance, transparency, graphic styles, masks | 5 | 97% | raster effect reach in preview bounds | 1–2 | estimated; Transform typing, sliders, preview, and undo are covered in `crates/ui-egui/src/panels/tests_appearance.rs` | | 3D and Materials (and Turntable) | 4 | ~25% | live Revolve with automatic smooth vector shading and default-on continuous circular caps, Extrude & Bevel (twist, taper, caps, five bevel shapes), Inflate and Rotate with Position presets (colored Inflate uses smooth membrane lighting; white-light Inflate retains its existing shading; Extrude & Bevel and Rotate retain planar shading; one to ten colored lights, shared neutral ambient, painter visibility); materials, light presets, shadows, ray tracing, the rest of 3D Classic (Revolve, Rotate, Map Art), Turntable views, textures, map art and shading in 3D export | 30–55 | estimated (Extrude & Bevel, Inflate and Rotate added 2026-10-12) | | Type core | 9 | 80% | Auto-leading style resets are implemented; Hyphenation… and Justification… options are done (#2310, #2755), and a paragraph's Auto Leading also sets the Character panel's Leading, the Alt+↑/↓ leading nudge and Fill with Placeholder Text; remaining: Optical Margin Alignment, Middle Eastern features beyond bidi layout, optional group font-size preservation; transformed character controls fixed (#1034), see [type-parity.md](type-parity.md) | 14–19 | estimated; eleven-language pattern hyphenation in both composers, with grapheme and shaping-cluster protection (#4947, `hyphen::tests` and `layout::wrapping_tests`); Sanskrit patterns pending an allowlisted source; recent-font groups in font pickers (#3515); full-name font matches regression-tested (#1258); mixed paragraph alignment indication implemented; headless regressions, full integration CI and visual review pass; localized legacy family-name fallback with loaded/catalog regressions (#2208); Fractional Widths per type object regression-tested; color emoji fonts with bitmap glyphs (`sbix`, `CBDT`) draw and export; threading ports: every selected area type and type on a path shows its in and out ports (empty, arrow, red "+"), and the Selection tool threads from either, the in port into a frame before it, and a double-click on a threaded port breaks the thread there (`threads::tests::break_splits_the_thread_and_the_story_flows_into_the_first_part`, `select::tests::a_double_click_on_a_threaded_port_breaks_the_thread`, `canvas::tests::a_double_click_on_a_threaded_out_port_breaks_the_thread`) (#2572; `select::tests::a_click_on_the_in_port_threads_into_a_frame_before_it`, `text::tests::selected_type_shows_its_ports_in_each_state`, `threads::tests::add_before_threads_the_story_from_the_new_frame_into_the_clicked_one`, live screenshot); the Glyphs panel highlights and scrolls to the character selected with the Type tool (#4908, `panels::glyphs` tests); the OpenType panel lists only `ss01`–`ss20` sets present in the selected font and uses its custom names when supplied; Type-range desktop Copy publishes styled art and keeps font family, size and color on canvas Paste after editing (#5636); paint-only Arabic harakat keep joining with conditional GDEF-mark ownership and Insert Kashidas base-letter paint (`tests::harakat`) | -| Type advanced (CJK, spelling, Touch Type, Retype) | 4 | 50% | CJK vertical composition (ruby, mojikumi), variable font axes, tab leaders, spell check (engine and custom dictionary done, #4936; the dialogs and Auto Spell Check remain), Touch Type, Retype | 20–28 | estimated; upright fallback for fullwidth `!` / `?` regression-tested; score and hours unchanged | +| Type advanced (CJK, spelling, Touch Type, Retype) | 4 | 50% | CJK vertical composition (ruby, mojikumi), variable font axes, tab leaders, spell check (engine, custom dictionary and Auto Spell Check done, #4936; the dialogs and the right-click Spelling menu remain), Touch Type, Retype | 20–28 | estimated; upright fallback for fullwidth `!` / `?` regression-tested; score and hours unchanged | | Symbols, blends, envelopes, Repeat, perspective | 5 | 88% | remaining: dynamic symbols' `.ai` round trip, further built-in symbol libraries (Basic Shapes and UI Frames with default 9-slice are done), envelope Anti-Alias/Preserve Shape, 9-slice guides outside the art; Symbol Options › Registration, Export Type (Graphic / Movie Clip) and 9-slice guides (drawn and draggable during symbol editing, #5280) and Symbol Type (dynamic on-canvas fill/stroke including gradient/pattern, opacity and blend, #4938) are done | 4–7 | estimated; Blend panel Type and Ramp control step easing with a separate color Shift (#2539); Blend Options retains spacing and orientation; polynomial curves are original approximations; interactive selected mirror axis implemented; bounding-box handles take priority over the axis line (focused regression and native checks passed; full CI blocked by an unrelated text test); #2544 spine Anchor Point conversion and picking regression-tested | -| Type advanced (CJK, spelling, Touch Type, Retype) | 4 | 50% | CJK vertical composition (ruby, mojikumi), tab leaders, spell check (engine and custom dictionary done, #4936; the dialogs and Auto Spell Check remain), Touch Type, Retype | 20–28 | estimated; upright fallback for fullwidth `!` / `?` regression-tested; score and hours unchanged | +| Type advanced (CJK, spelling, Touch Type, Retype) | 4 | 50% | CJK vertical composition (ruby, mojikumi), tab leaders, spell check (engine, custom dictionary and Auto Spell Check done, #4936; the dialogs and the right-click Spelling menu remain), Touch Type, Retype | 20–28 | estimated; upright fallback for fullwidth `!` / `?` regression-tested; score and hours unchanged | | Symbols, blends, envelopes, Repeat, perspective | 5 | 88% | remaining: dynamic symbols' `.ai` round trip, further built-in symbol libraries (Basic Shapes and UI Frames with default 9-slice are done), envelope Anti-Alias/Preserve Shape, 9-slice guides outside the art; Symbol Options › Registration, Export Type (Graphic / Movie Clip) and 9-slice guides (drawn and draggable during symbol editing, #5280) and Symbol Type (dynamic on-canvas fill/stroke including gradient/pattern, opacity and blend, #4938) are done | 4–7 | estimated; Blend panel Type and Ramp control step easing with a separate color Shift (#2539); Blend Options retains spacing and orientation; polynomial curves are original approximations; interactive selected mirror axis implemented; bounding-box handles take priority over the axis line (focused regression and native checks passed; full CI blocked by an unrelated text test); #2544 spine Anchor Point conversion and picking regression-tested | | Image Trace, graphs, image tools | 3 | 75% | graph Column designs, Image Trace Create Strokes depth (Create Object Mosaic is done; junction-returning piece survival has source-generated regressions in `image-trace-quality.md`; simpler contour walk, identical output; an edge's sub-pixel shift is the same from either side; notches read only their own piece; collapsed loops keep few segments, so Paths is monotone on the code-drawn sweep) | 5–8 | estimated (raised from 72%); quick presets reveal adjustment controls, Properties reopens them; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views (scores/hours unchanged) | | Live vector effects | 5 | 85% | Outline Object, SVG Filters | 4–8 | measured count 49/50 (Pathfinder 13/13 with Trap), depth estimated; Warp Options controls verified by focused checks and original-image review | | 3D and Materials (and Turntable) | 4 | ~25% | live Revolve with automatic smooth vector shading and default-on continuous circular caps, Extrude & Bevel (twist, taper, caps, five bevel shapes), Inflate and Rotate with Position presets (colored Inflate uses smooth membrane lighting; white-light Inflate retains its existing shading; Extrude & Bevel and Rotate retain planar shading; one to ten colored lights, shared neutral ambient, original lighting presets, painter visibility); materials, shadows, ray tracing, the rest of 3D Classic (Revolve, Rotate, Map Art), Turntable views, textures, map art and shading in 3D export | 30–55 | estimated (Extrude & Bevel, Inflate and Rotate added 2026-10-12) | -| Type advanced (CJK, spelling, Touch Type, Retype) | 4 | 50% | CJK vertical composition (mojikumi), variable font axes, tab leaders, spell check (engine and custom dictionary done, #4936; the dialogs and Auto Spell Check remain), Touch Type, Retype | 20–28 | estimated; No Break keeps marked characters on one line; upright fallback for fullwidth `!` / `?` regression-tested; score and hours unchanged | -| Type advanced (CJK, spelling, Touch Type, Retype) | 4 | 50% | CJK vertical composition (mojikumi), tab leaders, spell check (engine and custom dictionary done, #4936; the dialogs and Auto Spell Check remain), Touch Type, Retype | 20–28 | estimated; No Break keeps marked characters on one line; upright fallback for fullwidth `!` / `?` regression-tested; score and hours unchanged | +| Type advanced (CJK, spelling, Touch Type, Retype) | 4 | 50% | CJK vertical composition (mojikumi), variable font axes, tab leaders, spell check (engine, custom dictionary and Auto Spell Check done, #4936; the dialogs and the right-click Spelling menu remain), Touch Type, Retype | 20–28 | estimated; No Break keeps marked characters on one line; upright fallback for fullwidth `!` / `?` regression-tested; score and hours unchanged | +| Type advanced (CJK, spelling, Touch Type, Retype) | 4 | 50% | CJK vertical composition (mojikumi), tab leaders, spell check (engine, custom dictionary and Auto Spell Check done, #4936; the dialogs and the right-click Spelling menu remain), Touch Type, Retype | 20–28 | estimated; No Break keeps marked characters on one line; upright fallback for fullwidth `!` / `?` regression-tested; score and hours unchanged | | Raster effects (Effect Gallery) | 4 | 52% | the Effect Gallery's filter thumbnails and in-dialog preview area ([Filter Gallery overview](https://helpx.adobe.com/photoshop/desktop/effects-filters/get-started-with-filters/filter-gallery.html)) | 1–2 | estimated (raised from 49%: the Effect Gallery shipped, #2883, six categories, per-filter settings, ordered stack, live canvas preview, one undo step, `dialogs::effect_gallery::tests::stack_ok_is_one_undo_step`, `stack_preview_is_one_interaction`); measured count 57/57 (Artistic complete with Colored Pencil, Fresco, Paint Daubs, Palette Knife, Rough Pastels and Underpainting; Sketch complete with Torn Edges and Conté Crayon, `pixel::tests_sketch::conte_crayon_*`); texture redraw distances optimized (#1257), pixels verified; depth estimated | | Symbols, blends, envelopes, Repeat, perspective | 5 | 88% | remaining: dynamic symbols' `.ai` round trip, further built-in symbol libraries (Basic Shapes and UI Frames with default 9-slice are done), envelope Anti-Alias/Preserve Shape, 9-slice guides outside the art; Symbol Options › Registration, Export Type and Symbol Type (dynamic on-canvas fill/stroke including gradient/pattern, opacity and blend, #4938) and on-canvas 9-slice guides (#5280) are done | 4–7 | estimated | | Image Trace, graphs, image tools | 3 | 75% | graph Column designs, Image Trace Create Strokes depth (Create Object Mosaic is done; junction-returning piece survival has source-generated regressions in `image-trace-quality.md`; large images trace with a warning; Auto Color keeps rare distinct colours) | 5–8 | estimated (raised from 72%); quick presets reveal adjustment controls, Properties reopens them | @@ -481,10 +481,10 @@ about 1–1.5 agent-hours each, so the other rows (estimated on the older scale) | Appearance, transparency, graphic styles, masks | 5 | 98% | raster effect reach in preview bounds (flattener presets apply in PDF, EPS and print) | 1–2 | | Live vector effects | 5 | 85% | Outline Object, Pathfinder Hard/Soft Mix and Trap (since done: #3061, #3063, #3785), SVG Filters, Transform Patterns and Scale Strokes & Effects on a fill's or stroke's own Transform effect | 6–10 | | 3D and Materials | 4 | ~25% | Initial Revolve, Extrude & Bevel, Inflate and Rotate, with simple lighting; materials, light presets, shadows, ray tracing, Revolve (Classic), Rotate (Classic), Map Art and more complete visibility remain | 25–45 | -| Type advanced | 4 | 50% | CJK composition for vertical type (ruby, mojikumi beyond Line-end Punctuation Half Width — the other sets (and their paragraph-start bracket and indent rules), custom kinsoku sets (beyond Hard, Soft and None) and the Mojikumi Settings dialog —, manual tate-chu-yoko), tab leaders, spell check (its engine and custom dictionary commands are done, #4936: the Check Spelling and Edit Custom Dictionary dialogs and Auto Spell Check remain), Touch Type, Retype; vertical point/area/path type and Type Orientation have initial support (beyond parity, not scored: inline symbols in text) | 20–28 | +| Type advanced | 4 | 50% | CJK composition for vertical type (ruby, mojikumi beyond Line-end Punctuation Half Width — the other sets (and their paragraph-start bracket and indent rules), custom kinsoku sets (beyond Hard, Soft and None) and the Mojikumi Settings dialog —, manual tate-chu-yoko), tab leaders, spell check (its engine, custom dictionary commands and Auto Spell Check are done, #4936: the Check Spelling and Edit Custom Dictionary dialogs and the right-click Spelling menu remain), Touch Type, Retype; vertical point/area/path type and Type Orientation have initial support (beyond parity, not scored: inline symbols in text) | 20–28 | | Raster effects (Effect Gallery, Document Raster Effects Settings) | 4 | 35% | ~36 Photoshop-style filters (Artistic, Brush Strokes, Sketch, Stylize) and Texturizer's Load Texture (since done, see the area table above, as is the Effect Gallery, #2883); Document Raster Effects Settings, raster effects in PDF, the pixel-filter pipeline, Radial Blur, Smart Blur (Normal, Edge Only and Overlay Edge), Unsharp Mask, Glowing Edges, Distort (Diffuse Glow, Glass, Ocean Ripple), Pixelate (Color Halftone, Crystallize, Mezzotint, Pointillize), Texture (Craquelure, Grain, Mosaic Tiles, Patchwork, Stained Glass, Texturizer) and Video (De-Interlace, NTSC Colors) are done | 8–14 | | 3D and Materials | 4 | ~25% | Initial Revolve, Extrude & Bevel, Inflate and Rotate, with simple lighting and four original lighting presets; materials, shadows, ray tracing, Revolve (Classic), Rotate (Classic), Map Art and more complete visibility remain | 25–45 | -| Type advanced | 4 | 50% | CJK composition for vertical type (mojikumi beyond Line-end Punctuation Half Width — the other sets (and their paragraph-start bracket and indent rules), custom kinsoku sets (beyond Hard, Soft and None) and the Mojikumi Settings dialog —, manual tate-chu-yoko is done, its up/down settings remain; No Break is done), tab leaders, spell check (its engine and custom dictionary commands are done, #4936: the Check Spelling and Edit Custom Dictionary dialogs and Auto Spell Check remain), Touch Type, Retype; vertical point/area/path type and Type Orientation have initial support (beyond parity, not scored: inline symbols in text) | 20–28 | +| Type advanced | 4 | 50% | CJK composition for vertical type (mojikumi beyond Line-end Punctuation Half Width — the other sets (and their paragraph-start bracket and indent rules), custom kinsoku sets (beyond Hard, Soft and None) and the Mojikumi Settings dialog —, manual tate-chu-yoko is done, its up/down settings remain; No Break is done), tab leaders, spell check (its engine, custom dictionary commands and Auto Spell Check are done, #4936: the Check Spelling and Edit Custom Dictionary dialogs and the right-click Spelling menu remain), Touch Type, Retype; vertical point/area/path type and Type Orientation have initial support (beyond parity, not scored: inline symbols in text) | 20–28 | | Type core | 9 | 80% | Middle Eastern features beyond bidi layout, Paragraph Direction, digit types and Kashidas (the Middle Eastern composers, editable bidirectional SVG text, a split caret where directions meet); paint-only harakat keep joining with conditional GDEF-mark colour ownership, but fonts without GDEF mark classes and some AAT/`morx` ligatures can still leave marks painted as their letters; Every-line Composer fidelity for justified and ragged text (available per object; mixed type sizes in a paragraph still break single-line in non-rectangular area type and with text wrap), Optical Margin Alignment, paragraph spacing limit for vertically justified area type, hidden characters, paragraph attributes in the Type tool's copy and paste (the characters keep their styles, the paragraphs take the destination's), mixed-value display in the Paragraph panel for a selection across differing paragraphs, partial selection of type-area anchors by marquee, optional font-size preservation during group scaling (scaled character controls fixed in #1034). Hyphenation and Justification options, Fractional Widths, System Layout, styled Type-range canvas Paste (#5636), and pattern hyphenation for eleven Indian languages with grapheme/cluster protection (#4947) are done; Sanskrit automatic patterns remain pending an allowlisted source | 14–19 | | Symbols, blends, envelopes, Repeat, perspective | 5 | 90% | further built-in symbol libraries (Basic Shapes and UI Frames with default 9-slice are done), dynamic symbols' `.ai` round trip, envelope Anti-Alias/Preserve Shape output and warp point editing, brush interpolation in blends (M8 fidelity pass done); Symbol Options › Registration and Export Type and on-canvas dynamic part paint (gradient/pattern, opacity, blend) are done; symbol editing shows draggable 9-slice guides (#5280), while guides outside the artwork remain unsupported | 4–7 | | Image Trace, graphs, image tools | 3 | 72% | graph Column designs (Graph Design, Marker designs and series colours set with Group Selection are done), Create Object Mosaic, per-feature stroke/fill choice and varying widths in Image Trace's Create Strokes (Vector Halftone and the Image Trace View modes, Palette and Create Strokes are done) | 6–10 | @@ -1886,6 +1886,8 @@ Edit › Spelling has the commands its Check Spelling dialog will use, as the pu The custom dictionary and Ignore All followed (2026-11-09), as the same page describes them: Add puts a word in the custom dictionary, which Edit Custom Dictionary adds to, deletes from and changes, and Ignore All skips a word in the current document (#4936). `text.spelling.addWord`, `removeWord` and `changeWord` keep the dictionary in craft-proof, which every language's check reads, and the desktop app saves it to `Custom Dictionary.txt` beside its preferences (none in runs without preferences); `text.spelling.dictionary` lists it. `text.spelling.ignoreAll` keeps a word for the active document while it is open, not saved and not undoable (`tests/spelling.rs`). The dialogs remain. +Auto Spell Check followed (2026-11-09): the same page has Edit › Spelling › Auto Spell Check underline potential spelling errors with a red line as you type, and says it isn't available for text with effects applied (#4936). `edit.autoSpellCheck` turns it on and off (off at first, saved with the view settings); the canvas draws a red wavy line under each word `text.spelling.check` would list in the visible type, leaves out type with an effect shown on it or on its fills and strokes, and checks a type object again when it is edited, when the custom dictionary changes and when Ignore All adds a word (`ui-egui/src/canvas/tests.rs`, `auto_spell_check_*`; `tests/spelling.rs`). The right-click Spelling menu on an underlined word (suggestions, Ignore All, Add to Dictionary) and the dialogs remain. + ## Control bar: Paragraph section while type is selected (2026-11-06) With type selected, the Control bar shows a Paragraph: link and the Align left, Align center and Align right buttons after its Character controls, as the public documentation has the Control panel format paragraphs while the Type tool is in use, with a way to the Paragraph panel ([Paragraph panel overview](https://helpx.adobe.com/illustrator/using/formatting-paragraphs.html)), and its alignment buttons align text as the Paragraph panel's do ([Align text](https://helpx.adobe.com/illustrator/using/align-text.html)). A button aligns the selected type (`text.setStyle {justify}`, one undo step) and the shared alignment shows selected; the link opens the Paragraph panel in a popover under it, as Character: opens the Character panel. Keeping to the three alignments that don't justify is our choice, for the bar's room; the popover has all seven. Short of room the bar leaves controls out from its right end: the inline X/Y/W/H fields, the Distribute and Align buttons, area type's vertical alignment, then these buttons and last the link, keeping room for the Transform link; on a 1440 pt bar with one point type object, the buttons show (pinned by a headless test, #5372). @@ -1902,6 +1904,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s | Date | Change | Summary | |---|---|---| +| 2026-11-09 | minor | Type advanced: Auto Spell Check (`edit.autoSpellCheck`), #4936; evidence added; the dialogs and the right-click Spelling menu remain; scores and hours unchanged. | | 2026-11-09 | minor | Bundled content: built-in UI Frames symbol library (Panel, Button, Bar with default 9-slice); symbol libraries evidence updated; ~30% → ~32%; more built-in symbol libraries remain. | | 2026-11-09 | minor | Symbols: on-canvas dynamic part appearance takes gradient/pattern fill and stroke and opacity/blend; evidence updated; area ~87% → ~88%; strokes remaining no longer lists Overlap/auto corners. | | 2026-11-09 | minor | Strokes/brushes: Art brush Overlap and Pattern brush auto corners removed from remaining (evidence already covered them); symbols remaining clarified for dynamic on-canvas beyond solid fill/stroke; scores and hours unchanged. | diff --git a/docs/type-parity.md b/docs/type-parity.md index 4ca8d65b1..daec5c927 100644 --- a/docs/type-parity.md +++ b/docs/type-parity.md @@ -1,6 +1,6 @@ # Type parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; paint-only Arabic harakat keep joining with GDEF-mark ownership and Insert Kashidas base-letter paint; scores and estimates unchanged; custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: right-aligned area type sets the text after a line's last tab against the right edge; a soft return in justified type no longer keeps its line from being justified; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (Auto Spell Check: `edit.autoSpellCheck` underlines the unknown words, #4936; previously minor: PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; paint-only Arabic harakat keep joining with GDEF-mark ownership and Insert Kashidas base-letter paint; scores and estimates unchanged; custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: right-aligned area type sets the text after a line's last tab against the right edge; a soft return in justified type no longer keeps its line from being justified; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen) · **Target:** Adobe Illustrator 2026 (30.x) Type is Illustrator's largest single area by use (weight 13 of 105 across "Type core" and "Type advanced" in [target-app-parity.md](target-app-parity.md)). This checklist lists the features one by one. Status is from the @@ -47,7 +47,7 @@ against Illustrator's public documentation; **estimated** unless noted. | Hyphenation… and Justification… options | done | Eleven Indian languages use attributed patterns and preserve syllables in both composers (#4947). Hyphenation… (`ui.hyphenation`, `text.setFormat`) sets each paragraph's Words Longer Than, After First, Before Last, Hyphen Limit, Hyphenation Zone (Single-line Composer only) and Hyphenate Capitalized Words, and paragraph styles carry them (#2310). Justification… (`ui.justification`, `text.setFormat`, #2755) sets Word Spacing, Letter Spacing and Glyph Scaling (Minimum, Desired, Maximum), Auto Leading and Single Word Justification; Desired applies to every line, justified lines and the Every-line Composer use the ranges. Vertical type isn't glyph scaled; type on a path doesn't use them. Auto Leading also sets the Leading the Character panel shows for Auto type, the leading Alt+↑/↓ (`type.step`) steps from, per paragraph, and the lines Fill with Placeholder Text counts. Evidence: `crates/text/src/tests/justify.rs`, `dialogs::justification::tests`, `tests_prefs::auto_leading_steps_and_fills_from_the_paragraph_auto_leading`, `panels::character` test `leading_field_shows_the_paragraph_auto_leading` | | Tab stops and leaders; Tabs panel | partial | Tabs panel and stops with leaders in the model (`text.tabs.set`); a stop drags from where the press landed (#1458); leaders fill the tab's gap, repeated on a grid so they line up from line to line (#1877); Ctrl-dragging a stop (Command on macOS) moves all the stops together; in right-aligned area type the text before a line's last tab sits at its stops, from the left edge, and the text after it against the right edge, as Illustrator's own layout in a user's `.ai` sets a price list (evidence: `tests::right_aligned_area_type_sets_the_text_after_its_last_tab_against_the_edge`; centred and justified lines are still aligned as a whole) | | Hidden characters (Show Hidden Characters) | done | `type.hiddenCharacters` | -| Spell check (Check Spelling, Auto Spell Check, custom dictionary) | partial | the engine (#4936): `text.spelling.check` lists the unknown words of the document's type, each run in its Language, at the bytes `text.editRange` changes, and `text.spelling.suggest` gives suggestions, both from craft-proof, each paragraph on its own (English from the first dictionary it finds: a Hunspell dictionary in the user's dictionaries folder, the SCOWL dictionary built in from craft-i18n, a system Hunspell dictionary, and last the public-domain Moby word list built into it; other languages from the dictionaries craft-proof finds for them). The custom dictionary commands are done: `text.spelling.addWord`, `removeWord` and `changeWord` (Add, and Edit Custom Dictionary's Add, Delete and Change), and `text.spelling.dictionary` lists the words, saved to `Custom Dictionary.txt` beside the preferences; Ignore All: `text.spelling.ignoreAll` skips a word in the active document while it is open. The Check Spelling dialog, the Edit Custom Dictionary dialog and Auto Spell Check remain (the Edit › Spelling items are still off). Evidence: `crates/engine/src/tests/spelling.rs`. | +| Spell check (Check Spelling, Auto Spell Check, custom dictionary) | partial | the engine (#4936): `text.spelling.check` lists the unknown words of the document's type, each run in its Language, at the bytes `text.editRange` changes, and `text.spelling.suggest` gives suggestions, both from craft-proof, each paragraph on its own (English from the first dictionary it finds: a Hunspell dictionary in the user's dictionaries folder, the SCOWL dictionary built in from craft-i18n, a system Hunspell dictionary, and last the public-domain Moby word list built into it; other languages from the dictionaries craft-proof finds for them). The custom dictionary commands are done: `text.spelling.addWord`, `removeWord` and `changeWord` (Add, and Edit Custom Dictionary's Add, Delete and Change), and `text.spelling.dictionary` lists the words, saved to `Custom Dictionary.txt` beside the preferences; Ignore All: `text.spelling.ignoreAll` skips a word in the active document while it is open. Auto Spell Check is done: Edit › Spelling › Auto Spell Check (`edit.autoSpellCheck`, off at first, saved with the view settings) draws a red wavy line under the unknown words of the visible type, leaves out type with effects, and follows edits, the custom dictionary and Ignore All. The Check Spelling and Edit Custom Dictionary dialogs and the right-click Spelling menu (suggestions, Ignore All, Add to Dictionary) remain. Evidence: `crates/engine/src/tests/spelling.rs`, `crates/ui-egui/src/canvas/tests.rs` (`auto_spell_check_*`). | | Smart Punctuation, Change Case, Find & Replace | done | | | Alt + numeric-keypad codes while typing (Windows: `Alt+0176` → °) | partial | #5108: on Windows the character Windows posts when Alt comes up after keypad digits is typed at the caret like any other, in the typing's undo step, and the keypad digits typed with Alt run no shortcut. With NumLock off the keypad keys (Insert, End, arrows, Clear, Home, Page Up/Down without the extended-key flag) count as digits too and don't move the caret (`windows_alt_codes` unit tests; neither NumLock on nor off has been tried on a Windows machine yet). `type.insert {code}` (Windows Alt+0 code, Windows-1252 for 128–159) and `{unicode}` (hex code point) insert a character by its code (`menucmds::insert_a_character_by_its_code`). macOS and Linux have no such system convention and egui reports a keypad digit as the top-row digit, so there it does nothing; a floating panel window doesn't receive the character | | Caret: the arrows and Delete step over whole clusters (Thai consonant with its marks, combining accents, emoji); Backspace removes one character | done | in text with right-to-left characters the arrows move in visual order and stop on both sides of a character drawn with no width, such as a zero width non-joiner | @@ -81,6 +81,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha | Date | Change | Summary | |---|---|---| +| 2026-11-09 | minor | Auto Spell Check (#4936): Edit › Spelling › Auto Spell Check (`edit.autoSpellCheck`) underlines the unknown words of the visible type, without type with effects; the Check Spelling and Edit Custom Dictionary dialogs and the right-click Spelling menu remain. Scores and estimates unchanged. | | 2026-11-09 | minor | PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged. Decoded stream and placement regressions; reader ActualText (#6165), styled clusters and external viewer validation remain separate. | | 2026-11-09 | minor | Custom dictionary and Ignore All (#4936): `text.spelling.addWord`, `removeWord`, `changeWord` and `dictionary`, saved beside the preferences, and `text.spelling.ignoreAll` for the active document; the Check Spelling and Edit Custom Dictionary dialogs and Auto Spell Check remain. Scores and estimates unchanged. | | 2026-11-08 | minor | Spell check engine (#4936): `text.spelling.check` lists unknown words per run language at `text.editRange` byte offsets and `text.spelling.suggest` gives suggestions, from craft-proof; the dialog, Auto Spell Check and the custom dictionary remain. Spelling noted in the Language per character rows, which no longer list it as not yet done; their hyphenation note names the languages that still lack it. Scores and estimates unchanged. |