From 7a145576d4a6dbea9a00fb9affadaf16ef85bb16 Mon Sep 17 00:00:00 2001 From: HassanKhan Date: Sun, 8 Nov 2026 14:59:52 +0500 Subject: [PATCH 1/4] Part of #4936: text.spelling.check lists unknown words, text.spelling.suggest suggests The Edit > Spelling items have been placeholders with nothing behind them. This adds the engine half of Check Spelling on craft-proof, the shared spelling engine in craft-libs (at the revision the other craft-libs crates already pin), so no word list is copied here: - text.spelling.check {ids?} lists the words the dictionaries don't know in every visible, unlocked type object (or ids), each run checked in its Character panel Language (English when it has none; nothing in a language without a dictionary), as {id, start, end, word, lang}. start/end are byte offsets in the object's text, the ones text.editRange takes, so a word is changed with text.editRange as one undo step. At most 10,000 words, with `more`. - text.spelling.suggest {word, max?, lang?} gives the dictionary's suggestions, asked for one word at a time as craft-proof recommends. English comes from the public-domain Moby word list built into craft-proof. Its dependencies (log, miniz_oxide, toml, unicode-normalization) are already in the lockfile. The Check Spelling dialog, Auto Spell Check and Edit Custom Dictionary follow in later PRs. --- Cargo.lock | 12 ++++ Cargo.toml | 1 + crates/engine/Cargo.toml | 1 + crates/engine/src/cmd.rs | 2 + crates/engine/src/cmd/spelling.rs | 98 +++++++++++++++++++++++++++++ crates/engine/src/cmd/typemenu.rs | 4 +- crates/engine/src/tests.rs | 1 + crates/engine/src/tests/spelling.rs | 84 +++++++++++++++++++++++++ docs/mcp.md | 18 ++++++ docs/type-parity.md | 5 +- 10 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 crates/engine/src/cmd/spelling.rs create mode 100644 crates/engine/src/tests/spelling.rs diff --git a/Cargo.lock b/Cargo.lock index 72d591b91..de343a08b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1035,6 +1035,17 @@ dependencies = [ "winsafe", ] +[[package]] +name = "craft-proof" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=9be9a63782298ab59cd9b7a42ee9cba4f42a5ccb#9be9a63782298ab59cd9b7a42ee9cba4f42a5ccb" +dependencies = [ + "log", + "miniz_oxide 0.9.1", + "toml", + "unicode-normalization", +] + [[package]] name = "craft-ui" version = "0.1.0" @@ -6243,6 +6254,7 @@ dependencies = [ "cfb", "chrono", "craft-docpath", + "craft-proof", "crc32fast", "flate2", "hayro-jpeg2000", diff --git a/Cargo.toml b/Cargo.toml index a3e3e9890..8dba93d66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ craft-print = { git = "https://github.com/storytold/craft-libs", rev = "9be9a637 craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "9be9a63782298ab59cd9b7a42ee9cba4f42a5ccb" } craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "9be9a63782298ab59cd9b7a42ee9cba4f42a5ccb", features = ["serde"] } craft-keymap = { git = "https://github.com/storytold/craft-libs", rev = "9be9a63782298ab59cd9b7a42ee9cba4f42a5ccb" } +craft-proof = { git = "https://github.com/storytold/craft-libs", rev = "9be9a63782298ab59cd9b7a42ee9cba4f42a5ccb" } # Decides who draws the window frame (desktop vs. app) so the suite shares one native titlebar per # desktop. The `egui` feature adds `craft_window::decorate`. (craft-libs#217.) craft-window = { git = "https://github.com/storytold/craft-libs", rev = "9be9a63782298ab59cd9b7a42ee9cba4f42a5ccb", features = ["egui"] } diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index eddf966db..3cbc6068a 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -17,6 +17,7 @@ bundled-fonts = ["vectorcraft-text/bundled-fonts"] [dependencies] # Which paths stored in documents may be opened. craft-docpath = { workspace = true } +craft-proof = { workspace = true } vectorcraft-geom = { workspace = true } vectorcraft-color = { workspace = true } vectorcraft-doc = { workspace = true } diff --git a/crates/engine/src/cmd.rs b/crates/engine/src/cmd.rs index a26c75da2..77a6c9f9a 100644 --- a/crates/engine/src/cmd.rs +++ b/crates/engine/src/cmd.rs @@ -80,6 +80,7 @@ mod select; pub(crate) mod shaper; pub mod shortcutset; pub(crate) mod slices; +pub mod spelling; mod stroke; mod style; pub mod stylelib; @@ -317,6 +318,7 @@ pub fn command_specs() -> &'static [CommandSpec] { v.extend(expand::specs()); v.extend(attributes::specs()); v.extend(interactivity::specs()); + v.extend(spelling::specs()); v.extend(newart::specs()); v.extend(colortheme::specs()); v.extend(fileio::pdf::specs()); diff --git a/crates/engine/src/cmd/spelling.rs b/crates/engine/src/cmd/spelling.rs new file mode 100644 index 000000000..c7c88848f --- /dev/null +++ b/crates/engine/src/cmd/spelling.rs @@ -0,0 +1,98 @@ +//! Edit → Spelling: the words of the document's type that its dictionaries don't know +//! (`text.spelling.check`) and the words they suggest instead (`text.spelling.suggest`), checked +//! by craft-proof in each run's language +//! ([Check and correct spelling](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/check-and-correct-spelling.html)). +//! A word is changed with `text.editRange` over the bytes the check gives. + +use std::ops::Range; + +use craft_proof::{IssueKind, Options, ProofLang}; +use serde_json::{Value, json}; +use vectorcraft_doc::NodeKind; + +use super::menucmds::whole_or; +use super::typemenu::{all_texts, text_ids}; +use super::{CommandSpec, Result, Session, bad, cmd, has_doc, ids_param, str_param}; + +/// Most unknown words one check lists; past it the result says `more`. +pub const MAX_WORDS: usize = 10_000; + +/// Most suggestions one word gets. +pub const MAX_SUGGESTIONS: u64 = 20; + +/// Longest word suggestions are looked for, in bytes: the search costs more with the length. +const MAX_WORD_LEN: usize = 128; + +pub fn specs() -> Vec { + vec![ + cmd!( + query "text.spelling.check", + "Check Spelling", + [], + None, + "{ids?: [id…] (type objects, or groups holding them; default: every visible, unlocked type object)} the words the dictionaries don't know, each in its run's language (English when it has none; none in a language without a dictionary), in paint order → {words: [{id, start: byte, end: byte, word, lang}…], more: bool (more than 10000 words, the rest left out)}. text.editRange {id, start, end, insert} changes one", + has_doc, + check + ), + cmd!( + query "text.spelling.suggest", + "Spelling Suggestions", + [], + None, + "{word, max?: 1..20 (default 5), lang?: language tag (default English)} the words the dictionary of lang suggests instead of word, best first → {suggestions: [word…]}", + has_doc, + suggest + ), + ] +} + +fn check(s: &mut Session, p: &Value) -> Result { + let st = s.doc()?; + let ids = match ids_param(p, "ids") { + Some(v) => text_ids(&st.doc, &v), + None => all_texts(&st.doc), + }; + let opts = Options::default(); + let mut words = vec![]; + let mut more = false; + 'texts: 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; + } + for issue in craft_proof::check_text_with(&text, &spans, &opts, false, false) { + if issue.kind != IssueKind::Spelling { + continue; + } + let Some(word) = text.get(issue.start..issue.end) else { continue }; + if words.len() == MAX_WORDS { + more = true; + break 'texts; + } + words.push(json!({ "id": id.0, "start": issue.start, "end": issue.end, "word": word, "lang": issue.lang.tag() })); + } + } + Ok(json!({ "words": words, "more": more })) +} + +fn suggest(_s: &mut Session, p: &Value) -> Result { + const C: &str = "text.spelling.suggest"; + let word = str_param(p, "word").map(str::trim).filter(|w| !w.is_empty()).ok_or_else(|| bad(C, "missing `word`"))?; + let max = whole_or(p, "max", 5, C)?; + if !(1..=MAX_SUGGESTIONS).contains(&max) { + return Err(bad(C, format!("`max` must be 1 to {MAX_SUGGESTIONS}"))); + } + if word.len() > MAX_WORD_LEN { + return Ok(json!({ "suggestions": [] })); + } + let lang = ProofLang::from_tag(str_param(p, "lang")); + // `max` is at most MAX_SUGGESTIONS, so it fits. + let suggestions = craft_proof::suggest_in(word, usize::try_from(max).unwrap_or(5), lang); + Ok(json!({ "suggestions": suggestions })) +} diff --git a/crates/engine/src/cmd/typemenu.rs b/crates/engine/src/cmd/typemenu.rs index 83dfd925d..c1ae77b92 100644 --- a/crates/engine/src/cmd/typemenu.rs +++ b/crates/engine/src/cmd/typemenu.rs @@ -713,7 +713,9 @@ fn find_all_between(hay: &str, needle: &str, match_case: bool, whole_word: bool, /// The text objects Find and Replace and Find Next reach: on visible, unlocked layers, and not /// inside a hidden or locked group, which hides or locks what it holds as a layer does (#5900). -fn all_texts(d: &Document) -> Vec { +/// The visible, unlocked type objects of visible, unlocked layers, off locked artboards, in paint +/// order: the text Find and Replace and Check Spelling go through. +pub(crate) fn all_texts(d: &Document) -> Vec { fn visit(n: &Node, locks: &LockedArtboards<'_>, v: &mut Vec) { if !n.visible || n.locked { return; diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index b9e247ed9..69450b1de 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -154,6 +154,7 @@ mod simplify_part; mod slices; mod smartguides; mod smooth_path; +mod spelling; mod store; mod strokegeom; mod strokegradient; diff --git a/crates/engine/src/tests/spelling.rs b/crates/engine/src/tests/spelling.rs new file mode 100644 index 000000000..a6025b8a1 --- /dev/null +++ b/crates/engine/src/tests/spelling.rs @@ -0,0 +1,84 @@ +//! Edit → Spelling (`text.spelling.check`, `text.spelling.suggest`): the unknown words of the +//! document's type, at the bytes `text.editRange` changes, each in its run's language. + +use serde_json::{Value, json}; + +use crate::*; + +fn session() -> Session { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap(); + s +} + +fn run(s: &mut Session, id: &str, p: Value) -> Value { + s.execute(id, &p).unwrap() +} + +fn text(s: &mut Session, y: f64, t: &str) -> NodeId { + NodeId(run(s, "text.create", json!({"x": 10, "y": y, "text": t}))["id"].as_u64().unwrap()) +} + +/// The unknown words a check lists: (id, start, end, word). +fn unknown(s: &mut Session, p: Value) -> Vec<(u64, u64, u64, String)> { + let r = run(s, "text.spelling.check", p); + assert_eq!(r["more"], false); + r["words"] + .as_array() + .unwrap() + .iter() + .map(|w| (w["id"].as_u64().unwrap(), w["start"].as_u64().unwrap(), w["end"].as_u64().unwrap(), w["word"].as_str().unwrap().to_string())) + .collect() +} + +#[test] +fn check_lists_unknown_words_at_the_bytes_edit_range_changes() { + let mut s = session(); + // "é" is two bytes, so the unknown word starts at byte 14, not character 13. + let t = text(&mut s, 50.0, "café drawing qzxvw tools"); + assert_eq!(unknown(&mut s, json!({})), [(t.0, 14, 19, "qzxvw".to_string())]); + let w = &run(&mut s, "text.spelling.check", json!({}))["words"][0]; + assert_eq!(w["lang"], "en", "text with no language is checked in English"); + // Change: the bytes the check gave, as one undo step. + run(&mut s, "text.editRange", json!({"id": t.0, "start": 14, "end": 19, "insert": "vector"})); + assert_eq!(unknown(&mut s, json!({})), []); + run(&mut s, "edit.undo", json!({})); + assert_eq!(unknown(&mut s, json!({})).len(), 1, "undo brings the word back"); +} + +#[test] +fn check_goes_through_visible_unlocked_type_in_paint_order_or_the_ids_given() { + let mut s = session(); + let a = text(&mut s, 50.0, "first qzxvw"); + let b = text(&mut s, 100.0, "second wvxzq"); + let hidden = text(&mut s, 150.0, "hidden qqzzx"); + let locked = text(&mut s, 200.0, "locked zzqqx"); + run(&mut s, "object.hide", json!({"ids": [hidden.0]})); + run(&mut s, "object.lock", json!({"ids": [locked.0]})); + let ids: Vec = unknown(&mut s, json!({})).iter().map(|w| w.0).collect(); + assert_eq!(ids, [a.0, b.0], "hidden and locked type are left out"); + assert_eq!(unknown(&mut s, json!({"ids": [b.0]})), [(b.0, 7, 12, "wvxzq".to_string())]); +} + +#[test] +fn a_language_without_a_dictionary_marks_nothing() { + let mut s = session(); + let t = text(&mut s, 50.0, "qzxvw"); + run(&mut s, "text.setFormat", json!({"ids": [t.0], "language": "German"})); + assert_eq!(unknown(&mut s, json!({})), [], "German has no spelling dictionary in craft-proof"); +} + +#[test] +fn suggest_offers_dictionary_words() { + let mut s = session(); + let r = run(&mut s, "text.spelling.suggest", json!({"word": "drawng"})); + let got: Vec<&str> = r["suggestions"].as_array().unwrap().iter().map(|w| w.as_str().unwrap()).collect(); + assert!(got.len() <= 5, "five at most by default: {got:?}"); + assert!(got.contains(&"drawing"), "{got:?}"); + assert_eq!(run(&mut s, "text.spelling.suggest", json!({"word": "drawng", "max": 1}))["suggestions"].as_array().unwrap().len(), 1); + assert!(s.execute("text.spelling.suggest", &json!({})).is_err(), "a word is required"); + assert!(s.execute("text.spelling.suggest", &json!({"word": " "})).is_err(), "a blank word is refused"); + for max in [json!(0), json!(21), json!(-1), json!("3")] { + assert!(s.execute("text.spelling.suggest", &json!({"word": "drawng", "max": max})).is_err(), "max {max}"); + } +} diff --git a/docs/mcp.md b/docs/mcp.md index 9220b1c62..c20665d82 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -5464,6 +5464,24 @@ returns the new value. ``` +## Spelling + +`text.spelling.check {ids?}` lists the words of the document's type that its dictionaries don't know: every +visible, unlocked type object in paint order, or those in `ids` (and in the groups `ids` names). Each run is checked +in its Character panel Language (English when it has none) by craft-proof; a language without a spelling dictionary +marks nothing. Words in capitals, words with digits and web addresses are taken as correct. The result is +`{words: [{id, start, end, word, lang}], more}`: `start` and `end` are byte offsets in the type object's text, the +ones `text.editRange` takes, so `text.editRange {id, start, end, insert}` changes a word as one undo step. At most +10,000 words are listed; `more` is `true` when there were more. `text.spelling.suggest {word, max?, lang?}` gives +the words the dictionary of `lang` (default English) suggests instead of `word`, best first, at most `max` (1 to 20, +default 5). Both only read the document. + +```json +{"name":"run_command","arguments":{"command":"text.spelling.check","params":{}}} +{"name":"run_command","arguments":{"command":"text.spelling.suggest","params":{"word":"drawng","lang":"en"}}} +``` + + ## Justification options `text.setFormat` sets the Paragraph panel menu's Justification… options of the selected type (or `ids`, or the diff --git a/docs/type-parity.md b/docs/type-parity.md index 010c74aa5..3572fc69a 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-07 · **Change:** minor (Indian-language hyphenation review and license allowlist, #4947; Bulgarian among the languages, with its quote marks and the fonts' Bulgarian forms, #5945; previously minor: Show Snap to Glyph Options scrolls to the options, with a Snap to Glyph checkbox, #5273; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: Indian-language hyphenation review and license allowlist, #4947; Bulgarian among the languages, with its quote marks and the fonts' Bulgarian forms, #5945; previously minor: Show Snap to Glyph Options scrolls to the options, with a Snap to Glyph checkbox, #5273; scores and estimates unchanged) · **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 | | Hidden characters (Show Hidden Characters) | done | `type.hiddenCharacters` | -| Spell check (Check Spelling, Auto Spell Check, custom dictionary) | missing | menu stubs; needs an openly licensed dictionary | +| 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 (English from the public-domain Moby word list built into it; other languages from installed Hunspell dictionaries). The Check Spelling dialog, Auto Spell Check and Edit Custom Dictionary remain (the Edit › Spelling items are still off). Evidence: `crates/engine/src/tests/spelling.rs` | | 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 (`windows_alt_codes` tests; not yet tried on a Windows machine). `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 | @@ -79,6 +79,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha | Date | Change | Summary | |---|---|---| +| 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. Scores and estimates unchanged. | | 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. | | 2026-11-06 | minor | Alt + numeric-keypad codes (`Alt+0176` → °) type their character at the caret on Windows, and `type.insert` inserts a character by `code` or `unicode` (#5108); scores and estimates unchanged | | 2026-11-06 | minor | Snap to Glyph › Glyph Bounds: each glyph's box gives four corners a drawn point lands on, also on a curved side (`B`), and its box and edges turn with rotated type (#5112); scores and estimates unchanged | From f71952730f6fb38706df95199dfd23e2d26cc5c0 Mon Sep 17 00:00:00 2001 From: HassanKhan Date: Sun, 8 Nov 2026 15:07:37 +0500 Subject: [PATCH 2/4] Contributors: Hassan1305's name for the About window --- contributors/people.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contributors/people.toml b/contributors/people.toml index 69c412f8b..420177ffb 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -220,3 +220,8 @@ real_name = "Sven Lafebre" [people.kylekanouse] real_name = "Kyle Kanouse" sync_github_name = true + +[people.Hassan1305] +real_name = "Hassan Nawaz Khan" +display_name = "Hassan Nawaz Khan" +sync_github_name = false From 1e52ef587d6510d295792ebde67b3fb65371a366 Mon Sep 17 00:00:00 2001 From: HassanKhan Date: Sun, 8 Nov 2026 21:01:59 +0500 Subject: [PATCH 3/4] Part of #4936: refuse malformed ids in text.spelling.check; dictionary-independent test; docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - text.spelling.check checks a given `ids` as edit.findReplace does (checked_ids_param): a non-array, a negative, non-numeric or unknown id is an error instead of a check of the whole document. Null still means every visible, unlocked type object. - The byte-offset test puts an em dash, not "café", before the unknown word, so it passes whichever dictionaries craft-proof is built with (CI builds with craft-i18n's, where "café" is unknown). - Docs: the spell check engine in target-app-parity.md (Type advanced rows and evidence), gaps.md (G4), roadmap.md (M7) and the Language per character rows of type-parity.md, with status lines and revision history. --- crates/engine/src/cmd/spelling.rs | 13 ++++++++----- crates/engine/src/tests/spelling.rs | 13 +++++++++---- docs/gaps.md | 6 ++++-- docs/roadmap.md | 9 +++++---- docs/target-app-parity.md | 19 ++++++++++++------- docs/type-parity.md | 6 +++--- 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/crates/engine/src/cmd/spelling.rs b/crates/engine/src/cmd/spelling.rs index c7c88848f..6c7ae2e2c 100644 --- a/crates/engine/src/cmd/spelling.rs +++ b/crates/engine/src/cmd/spelling.rs @@ -12,7 +12,7 @@ use vectorcraft_doc::NodeKind; use super::menucmds::whole_or; use super::typemenu::{all_texts, text_ids}; -use super::{CommandSpec, Result, Session, bad, cmd, has_doc, ids_param, str_param}; +use super::{CommandSpec, Result, Session, bad, checked_ids_param, cmd, has_doc, str_param}; /// Most unknown words one check lists; past it the result says `more`. pub const MAX_WORDS: usize = 10_000; @@ -47,11 +47,14 @@ pub fn specs() -> Vec { } fn check(s: &mut Session, p: &Value) -> Result { - let st = s.doc()?; - let ids = match ids_param(p, "ids") { - Some(v) => text_ids(&st.doc, &v), - None => all_texts(&st.doc), + const C: &str = "text.spelling.check"; + // Given `ids` are checked like Find and Replace's, so a malformed one is an error rather than + // a check of the whole document. + let ids = match p.get("ids").filter(|v| !v.is_null()) { + Some(_) => text_ids(&s.doc()?.doc, &checked_ids_param(s, p, "ids", C)?), + None => all_texts(&s.doc()?.doc), }; + let st = s.doc()?; let opts = Options::default(); let mut words = vec![]; let mut more = false; diff --git a/crates/engine/src/tests/spelling.rs b/crates/engine/src/tests/spelling.rs index a6025b8a1..6757e576d 100644 --- a/crates/engine/src/tests/spelling.rs +++ b/crates/engine/src/tests/spelling.rs @@ -34,13 +34,14 @@ fn unknown(s: &mut Session, p: Value) -> Vec<(u64, u64, u64, String)> { #[test] fn check_lists_unknown_words_at_the_bytes_edit_range_changes() { let mut s = session(); - // "é" is two bytes, so the unknown word starts at byte 14, not character 13. - let t = text(&mut s, 50.0, "café drawing qzxvw tools"); - assert_eq!(unknown(&mut s, json!({})), [(t.0, 14, 19, "qzxvw".to_string())]); + // "—" is three bytes, so the unknown word starts at byte 12, not character 10. Punctuation, + // not a word, so the result doesn't depend on which dictionary is built in. + let t = text(&mut s, 50.0, "drawing — qzxvw tools"); + assert_eq!(unknown(&mut s, json!({})), [(t.0, 12, 17, "qzxvw".to_string())]); let w = &run(&mut s, "text.spelling.check", json!({}))["words"][0]; assert_eq!(w["lang"], "en", "text with no language is checked in English"); // Change: the bytes the check gave, as one undo step. - run(&mut s, "text.editRange", json!({"id": t.0, "start": 14, "end": 19, "insert": "vector"})); + run(&mut s, "text.editRange", json!({"id": t.0, "start": 12, "end": 17, "insert": "vector"})); assert_eq!(unknown(&mut s, json!({})), []); run(&mut s, "edit.undo", json!({})); assert_eq!(unknown(&mut s, json!({})).len(), 1, "undo brings the word back"); @@ -58,6 +59,10 @@ fn check_goes_through_visible_unlocked_type_in_paint_order_or_the_ids_given() { let ids: Vec = unknown(&mut s, json!({})).iter().map(|w| w.0).collect(); assert_eq!(ids, [a.0, b.0], "hidden and locked type are left out"); assert_eq!(unknown(&mut s, json!({"ids": [b.0]})), [(b.0, 7, 12, "wvxzq".to_string())]); + assert_eq!(unknown(&mut s, json!({"ids": null})).len(), 2, "null ids is the default"); + for ids in [json!(b.0), json!("all"), json!({"id": b.0}), json!([-1]), json!(["x"]), json!([999_999])] { + assert!(s.execute("text.spelling.check", &json!({"ids": ids})).is_err(), "malformed ids {ids} is refused"); + } } #[test] diff --git a/docs/gaps.md b/docs/gaps.md index dc6971cf5..957483279 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Gaps -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (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-08 · **Change:** minor (spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: 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 @@ -427,7 +427,8 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall own pixels until a document takes them in; estimate unchanged. - **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 with an open dictionary, + 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 Check Spelling dialog, Auto Spell Check and the custom dictionary 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. @@ -818,6 +819,7 @@ trap, and the defaults (0.25 pt, 100%, 40%) are VectorCraft's own, the pages giv | Date | Change | Summary | |---|---|---| +| 2026-11-08 | minor | G4 type: the spell check engine is done (`text.spelling.check`, `text.spelling.suggest` on craft-proof, #4936); the Check Spelling dialog, Auto Spell Check and the custom dictionary remain; scores and estimates unchanged. | | 2026-11-06 | minor | Hindi (`hi`): 7,877-row machine-assisted catalog, zero/one singular plurals, desktop font fallback and optional Devanagari craft-fonts fallback; all 55 translation tests pass; native review and a web font remain; scores and estimates unchanged | | 2026-11-05 | minor | G15: nearby hits on plain long strokes and outlines check their candidate segments directly; a 200,000-anchor middle hit took about 11–13 ms before and 1.0–1.4 ms after in paired runs. An inside-bounds miss fell from 18.7 ms to 1.6 ms. Estimate unchanged; idle budget run still open. | | 2026-11-05 | minor | G15: an image zoomed in on is separated, colorized or proofed only for the tiles in view (#4155); 100% zoom on that file 8.6 s → 0.05 s. Estimate unchanged. | diff --git a/docs/roadmap.md b/docs/roadmap.md index d01c002ae..badfb19a3 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-07 · **Change:** minor (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-08 · **Change:** minor (spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: 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 @@ -127,9 +127,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) | 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; tab leaders, spell check, vertical type pending | 45–60 | -| M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | 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; tab leaders, spell check, vertical type pending | 45–60 | -| M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | 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; 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 (#4936; the dialog, Auto Spell Check and the custom dictionary remain); 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; 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 (#4936; the dialog, Auto Spell Check and the custom dictionary remain); 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; 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 (#4936; the dialog, Auto Spell Check and the custom dictionary remain); 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; 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 | @@ -180,6 +180,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-11-08 | minor | M7: spell check engine, `text.spelling.check` and `text.spelling.suggest` on craft-proof (#4936); the dialog, Auto Spell Check and the custom dictionary remain; scores and estimates unchanged. | | 2026-11-06 | minor | M2: a shape just drawn moves by its centre point (Show Center) and turns from the rotate zone just outside a bounding-box corner with its tool still active, as the Selection tool's gestures do (#5477); estimates unchanged. | | 2026-11-06 | minor | Hindi (`hi`): 7,877-row machine-assisted catalog, zero/one singular plurals, desktop font fallback and optional Devanagari craft-fonts fallback; all 55 translation tests pass; native review and a web font remain; scores and estimates unchanged | | 2026-11-04 | minor | New Document: the units chosen in Preset Details belong to the chosen preset; Recent and Saved cards keep their own units; scores and estimates unchanged. | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 31dc47a26..44dd719af 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-07 · **Change:** minor (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) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: 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) ## Feature submission evidence @@ -187,15 +187,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, 3D Classic, Turntable views, 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) | -| Type advanced (CJK, spelling, Touch Type, Retype) | 4 | 50% | CJK vertical composition (ruby, mojikumi), variable font axes, tab leaders, spell check, 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 done, #4936; dialog, Auto Spell Check and custom dictionary remain), Touch Type, Retype | 20–28 | estimated; upright fallback for fullwidth `!` / `?` regression-tested; score and hours unchanged | | Symbols, blends, envelopes, Repeat, perspective | 5 | 87% | 30.7's Blend panel with ease in/out, on-canvas Direct Selection of parts inside dynamic symbol instances and their `.ai` round trip, more built-in symbol libraries, envelope Anti-Alias/Preserve Shape; Symbol Options › Registration, Export Type (Graphic / Movie Clip) and 9-slice guides (drawn and draggable during symbol editing, #5280) and Symbol Type (dynamic symbols keep per-instance appearance linked, #4938) are done | 4–8 | 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; scores and hours unchanged | -| Type advanced (CJK, spelling, Touch Type, Retype) | 4 | 50% | CJK vertical composition (ruby, mojikumi), tab leaders, spell check, 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 done, #4936; dialog, Auto Spell Check and custom dictionary remain), Touch Type, Retype | 20–28 | estimated; upright fallback for fullwidth `!` / `?` regression-tested; score and hours unchanged | | Symbols, blends, envelopes, Repeat, perspective | 5 | 87% | 30.7's Blend panel with ease in/out, on-canvas Direct Selection of parts inside dynamic symbol instances and their `.ai` round trip, symbol libraries, Start Global Edit, envelope Anti-Alias/Preserve Shape; Symbol Options › Registration, Export Type (Graphic / Movie Clip) and 9-slice guides (drawn and draggable during symbol editing, #5280) and Symbol Type (dynamic symbols keep per-instance appearance linked, #4938) are done | 4–8 | 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; scores and hours unchanged | | 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, 3D Classic, Turntable views, 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, 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, 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 done, #4936; dialog, Auto Spell Check and custom dictionary 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 done, #4936; dialog, Auto Spell Check and custom dictionary 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 | 87% | 30.7's Blend panel with ease in/out, on-canvas Direct Selection of parts inside dynamic symbol instances and their `.ai` round trip, more built-in symbol libraries, envelope Anti-Alias/Preserve Shape; Symbol Options › Registration and Export Type (Graphic / Movie Clip) and Symbol Type (dynamic symbols keep per-instance appearance linked, #4938) are done | 4–8 | 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 | @@ -458,10 +458,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, 3D Classic 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 (open dictionary), 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 is done, #4936: the Check Spelling dialog, Auto Spell Check and the custom dictionary 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, 3D Classic 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 (open dictionary), 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 is done, #4936: the Check Spelling dialog, Auto Spell Check and the custom dictionary 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 and Paragraph Direction (the Middle Eastern composers, editable bidirectional SVG text, a split caret where directions meet); 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% | more built-in symbol libraries (original), dynamic symbols, envelope Anti-Alias/Preserve Shape output and warp point editing, brush interpolation in blends (M8 fidelity pass done); Symbol Options › Registration and Export Type 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 | @@ -1769,6 +1769,10 @@ While a corner widget is pressed or dragged with the Selection or Direct Selecti A double-click on a Repeat (Radial, Grid or Mirror) with the Selection tool enters isolation mode on it ([isolate objects](https://helpx.adobe.com/illustrator/desktop/manage-objects/select-objects/isolate-objects.html): a double-click with the Selection tool isolates an object; [repeat patterns overview](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-patterns/repeat-patterns-overview.html): every instance follows edits to the original object); there the source art is selected and edited as usual (a double-click on type in it switches to the Type tool on that text) and every copy follows, with the repeat's settings kept (#5054). Test: `tests::repeat_edit::a_double_click_on_a_repeat_isolates_it_to_edit_its_text`. +## Spell check engine (2026-11-08) + +Edit › Spelling has the commands its Check Spelling dialog will use, as the public documentation describes checking the document's text, suggesting replacements and changing a word ([Check and correct spelling](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/check-and-correct-spelling.html)). `text.spelling.check` lists the words the dictionaries don't know in every visible, unlocked type object, or in the ones given by `ids`, in paint order, each run checked in its Character panel Language (English when it has none; nothing in a language without a dictionary). Each word comes with its object and byte range, which `text.editRange` changes as one undo step. `text.spelling.suggest` gives one word's suggestions. Both come from craft-proof in [storytold/craft-libs](https://github.com/storytold/craft-libs), with English from the public-domain Moby word list (#4936; `tests/spelling.rs`). The Check Spelling dialog, Auto Spell Check and Edit Custom Dictionary remain, so the Edit › Spelling menu items are still placeholders. + ## 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). @@ -1785,6 +1789,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s | Date | Change | Summary | |---|---|---| +| 2026-11-08 | minor | Type advanced: spell check engine, `text.spelling.check` lists unknown words per run language at `text.editRange` byte offsets and `text.spelling.suggest` gives suggestions, from craft-proof (#4936); evidence added; the dialog, Auto Spell Check and the custom dictionary remain; scores and hours unchanged. | | 2026-11-06 | minor | Direct Selection: with Snap to Grid, a dragged straight segment lands its anchors on the grid (#3835); evidence added; scores unchanged. | | 2026-11-06 | minor | Snap to Glyph › Glyph Bounds: the corners of each glyph's box are points a drawn point lands on, also on a curved side (`B`), and the box turns with rotated type, its edges then Angular Guides (#5112; [Snap to Glyph options](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/snap-to-glyph-options.html)); scores and estimates unchanged. | | 2026-11-06 | minor | Drop Shadow: a swatch beside Color opens the Color Picker over the dialog, as the other effect colour fields do (#5364); correction: the #5365 entry listed Drop Shadow, whose Color is drawn outside the shared grid and had no swatch; scores unchanged. | diff --git a/docs/type-parity.md b/docs/type-parity.md index 3572fc69a..8872308ee 100644 --- a/docs/type-parity.md +++ b/docs/type-parity.md @@ -66,8 +66,8 @@ against Illustrator's public documentation; **estimated** unless noted. | CJK: manual tate-chu-yoko | done | Character panel menu › Tate-chu-yoko (`text.setFormat` / `text.setRangeStyle {tateChuYoko}`), with Show East Asian Options: marked characters stand side by side across one em of a vertical column, squeezed to the em when wider; two or three half-width digits still do so without the mark; horizontal type keeps the mark and does not move them. Tate-chu-yoko Settings… (`ui.tateChuYokoSettings`, `{tcyUpDown, tcyLeftRight}` in points) moves a marked block up or down and right or left as one piece. Evidence: `tests_vertical::manual_tate_chu_yoko_sets_marked_characters_across_one_em`, `tests_vertical::tate_chu_yoko_settings_move_a_marked_block_up_down_left_and_right`, `typecmd::tate_chu_yoko_is_a_character_attribute_saved_only_when_on`, `typecmd::tate_chu_yoko_settings_are_character_attributes_saved_only_when_set`, `dialogs::tate_chu_yoko::tests` | | CJK: Standard Vertical Roman Alignment | done | Character panel menu › Standard Vertical Roman Alignment (`text.setFormat` / `text.setRangeStyle {standardVerticalRoman}`), with Show East Asian Options, off by default: in vertical type the marked half-width characters (Latin letters, digits and other characters that would lie on their side) stand upright one below the other, each in its upright cell; spaces keep their width and tate-chu-yoko blocks stay across the column; horizontal type keeps the mark and does not change. Character Style Options has its own checkbox for it. Evidence: `tests_vertical::standard_vertical_roman_alignment_stands_half_width_characters_upright`, `typecmd::standard_vertical_roman_is_a_character_attribute_saved_only_when_on` | | CJK: ruby | partial | ruby (ふりがな) for live text (`type.ruby`): a reading over a byte range, above (right of the column in vertical type) or below, start/centre/end alignment, size, overhang or squeeze; saved natively, laid out with its line growing, drawn on the canvas, in SVG and PDF (as outlines where ruby is present), in EPS, EMF/WMF and legacy `.ai` (legacy `.ai` writes type with ruby as outlines), and kept by Create Outlines, Expand Appearance and flattening, each glyph where the layout puts it (`typecmd::ruby_tests::create_outlines_keeps_the_ruby_where_it_is_laid_out`) | #633 | -| Language per character (Character panel › Language, character styles), text shaped in it | done | `text.setFormat` / `text.setRangeStyle {language}` by name or BCP 47 tag, one of Document Setup's languages (Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu added, #4943; Assamese and Sanskrit added, #4947; Serbian: Cyrillic, Serbian: Latin and Croatian); the shaper gets the tag, so the font's language forms apply (OpenType `locl`: Turkish keeps `fi` apart and the dot on its i, #4940); new type takes the document's language; saved as the tag only when set, carried by character styles (#4946). English and eleven Indian languages hyphenate by run language; Sanskrit automatic hyphenation awaits allowlisted patterns (#4947). Not yet: spelling by it, `.ai`/PDF import of a text's language, paragraph styles. Evidence: `features::tests::language_picks_the_fonts_forms`, `features::tests::serbian_gets_the_fonts_serbian_forms`, `typecmd::area_tests::language_is_a_character_attribute_saved_only_when_set`, `tests::typo::indic_language_runs_hyphenate_with_both_composers`, `hyphen::tests::sanskrit_without_allowlisted_patterns_uses_only_exceptions` | -| Language per character (Character panel › Language, character styles), text shaped in it | done | `text.setFormat` / `text.setRangeStyle {language}` by name or BCP 47 tag, one of Document Setup's languages (Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu added, #4943; Serbian: Cyrillic, Serbian: Latin and Croatian; Bulgarian, #5945); the shaper gets the tag, so the font's language forms apply (OpenType `locl`: Turkish keeps `fi` apart and the dot on its i, #4940); new type takes the document's language; saved as the tag only when set, carried by character styles (#4946). Not yet: hyphenation and spelling by it, `.ai`/PDF import of a text's language, paragraph styles. Evidence: `features::tests::language_picks_the_fonts_forms`, `features::tests::serbian_gets_the_fonts_serbian_forms`, `typecmd::area_tests::language_is_a_character_attribute_saved_only_when_set` | +| Language per character (Character panel › Language, character styles), text shaped in it | done | `text.setFormat` / `text.setRangeStyle {language}` by name or BCP 47 tag, one of Document Setup's languages (Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu added, #4943; Assamese and Sanskrit added, #4947; Serbian: Cyrillic, Serbian: Latin and Croatian); the shaper gets the tag, so the font's language forms apply (OpenType `locl`: Turkish keeps `fi` apart and the dot on its i, #4940); new type takes the document's language; saved as the tag only when set, carried by character styles (#4946). English and eleven Indian languages hyphenate by run language; Sanskrit automatic hyphenation awaits allowlisted patterns (#4947). Not yet: spelling by it, `.ai`/PDF import of a text's language, paragraph styles. Evidence: `features::tests::language_picks_the_fonts_forms`, `features::tests::serbian_gets_the_fonts_serbian_forms`, `typecmd::area_tests::language_is_a_character_attribute_saved_only_when_set`, `tests::typo::indic_language_runs_hyphenate_with_both_composers`, `hyphen::tests::sanskrit_without_allowlisted_patterns_uses_only_exceptions`; spelling is checked in each run's Language (`text.spelling.check`, #4936) | +| Language per character (Character panel › Language, character styles), text shaped in it | done | `text.setFormat` / `text.setRangeStyle {language}` by name or BCP 47 tag, one of Document Setup's languages (Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu added, #4943; Serbian: Cyrillic, Serbian: Latin and Croatian; Bulgarian, #5945); the shaper gets the tag, so the font's language forms apply (OpenType `locl`: Turkish keeps `fi` apart and the dot on its i, #4940); new type takes the document's language; saved as the tag only when set, carried by character styles (#4946). Not yet: hyphenation and spelling by it, `.ai`/PDF import of a text's language, paragraph styles. Evidence: `features::tests::language_picks_the_fonts_forms`, `features::tests::serbian_gets_the_fonts_serbian_forms`, `typecmd::area_tests::language_is_a_character_attribute_saved_only_when_set`; spelling is checked in each run's Language (`text.spelling.check`, #4936) | | No Break | done | Character panel menu (`text.setFormat` / `text.setRangeStyle {noBreak}`): the marked characters stay on one line, including across a space; a run that does not fit after earlier text moves together, and a run longer than the line stays together. Evidence: `layout::wrapping_tests::no_break_keeps_marked_characters_on_one_line`, `tests::no_break_keeps_marked_words_on_one_line`, `typecmd::no_break_is_a_character_attribute_saved_only_when_on` | | Inline symbols in text | beyond Illustrator | not scored | | Align Stroke (inside, outside) on live type's character stroke | beyond Illustrator | not scored; `stroke.set {align}` and `text.setRangeStyle {strokeOptions: {align}}`; saved natively, drawn around the glyphs on the canvas and in PDF, EPS and SVG (live text, stroke on the glyph outlines); visual bounds grow with it (#2294) | @@ -79,7 +79,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha | Date | Change | Summary | |---|---|---| -| 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. 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. Scores and estimates unchanged. | | 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. | | 2026-11-06 | minor | Alt + numeric-keypad codes (`Alt+0176` → °) type their character at the caret on Windows, and `type.insert` inserts a character by `code` or `unicode` (#5108); scores and estimates unchanged | | 2026-11-06 | minor | Snap to Glyph › Glyph Bounds: each glyph's box gives four corners a drawn point lands on, also on a curved side (`B`), and its box and edges turn with rotated type (#5112); scores and estimates unchanged | From fc2a27d1192d6f6124663d2d83bc57e6ecb4bc80 Mon Sep 17 00:00:00 2001 From: HassanKhan Date: Sun, 8 Nov 2026 21:53:42 +0500 Subject: [PATCH 4/4] Part of #4936: check spelling per paragraph, given ids in paint order; dictionary sources - text.spelling.check gives craft-proof one paragraph at a time, as check_text_with expects: its language detection reads a paragraph, so a Vietnamese paragraph no longer leaves the English ones after it unchecked, and the check stops at the paragraph where the 10,000-word list fills up instead of proofing the rest of the text first. - Given `ids` are checked in paint order, as the result promises, not in the order they were passed. - Docs: English words come from the first dictionary craft-proof finds (dictionaries folder Hunspell, SCOWL from craft-i18n, system Hunspell), with the Moby word list as the last fallback, not from Moby alone. - Doc comments on the command functions and test helpers. --- crates/engine/src/cmd/spelling.rs | 53 ++++++++++++++++++++++------- crates/engine/src/tests/spelling.rs | 16 +++++++++ docs/mcp.md | 6 ++-- docs/target-app-parity.md | 2 +- docs/type-parity.md | 2 +- 5 files changed, 63 insertions(+), 16 deletions(-) diff --git a/crates/engine/src/cmd/spelling.rs b/crates/engine/src/cmd/spelling.rs index 6c7ae2e2c..26c34381a 100644 --- a/crates/engine/src/cmd/spelling.rs +++ b/crates/engine/src/cmd/spelling.rs @@ -8,7 +8,7 @@ use std::ops::Range; use craft_proof::{IssueKind, Options, ProofLang}; use serde_json::{Value, json}; -use vectorcraft_doc::NodeKind; +use vectorcraft_doc::{Document, NodeId, NodeKind}; use super::menucmds::whole_or; use super::typemenu::{all_texts, text_ids}; @@ -23,6 +23,7 @@ pub const MAX_SUGGESTIONS: u64 = 20; /// Longest word suggestions are looked for, in bytes: the search costs more with the length. const MAX_WORD_LEN: usize = 128; +/// The Edit › Spelling commands. pub fn specs() -> Vec { vec![ cmd!( @@ -30,7 +31,7 @@ pub fn specs() -> Vec { "Check Spelling", [], None, - "{ids?: [id…] (type objects, or groups holding them; default: every visible, unlocked type object)} the words the dictionaries don't know, each in its run's language (English when it has none; none in a language without a dictionary), in paint order → {words: [{id, start: byte, end: byte, word, lang}…], more: bool (more than 10000 words, the rest left out)}. text.editRange {id, start, end, insert} changes one", + "{ids?: [id…] (type objects, or groups holding them; default: every visible, unlocked type object)} the words the dictionaries don't know, each in its run's language (English when it has none; none in a language without a dictionary), each paragraph on its own, in paint order (given ids too) → {words: [{id, start: byte, end: byte, word, lang}…], more: bool (more than 10000 words, the rest left out)}. text.editRange {id, start, end, insert} changes one", has_doc, check ), @@ -46,12 +47,14 @@ pub fn specs() -> Vec { ] } +/// `text.spelling.check`: the unknown words of the given type objects (or every visible, unlocked +/// one), in paint order, each paragraph checked on its own, at most [`MAX_WORDS`] of them. fn check(s: &mut Session, p: &Value) -> Result { const C: &str = "text.spelling.check"; // Given `ids` are checked like Find and Replace's, so a malformed one is an error rather than // a check of the whole document. let ids = match p.get("ids").filter(|v| !v.is_null()) { - Some(_) => text_ids(&s.doc()?.doc, &checked_ids_param(s, p, "ids", C)?), + Some(_) => in_paint_order(&s.doc()?.doc, text_ids(&s.doc()?.doc, &checked_ids_param(s, p, "ids", C)?)), None => all_texts(&s.doc()?.doc), }; let st = s.doc()?; @@ -69,21 +72,47 @@ fn check(s: &mut Session, p: &Value) -> Result { spans.push((at..end, ProofLang::from_tag(r.style.language.as_deref()))); at = end; } - for issue in craft_proof::check_text_with(&text, &spans, &opts, false, false) { - if issue.kind != IssueKind::Spelling { - continue; + // 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 words.len() == MAX_WORDS { + more = true; + break 'texts; + } + words.push(json!({ "id": id.0, "start": start, "end": end, "word": word, "lang": issue.lang.tag() })); } - let Some(word) = text.get(issue.start..issue.end) else { continue }; - if words.len() == MAX_WORDS { - more = true; - break 'texts; - } - words.push(json!({ "id": id.0, "start": issue.start, "end": issue.end, "word": word, "lang": issue.lang.tag() })); + // Past the paragraph and its `\n`. + para_start = para_end + 1; } } Ok(json!({ "words": words, "more": more })) } +/// `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 { + let paths = d.index_paths(ids.iter().copied()); + ids.sort_by(|a, b| paths.get(a).cmp(&paths.get(b))); + ids +} + +/// `text.spelling.suggest`: up to `max` of the words the dictionary of `lang` suggests for `word`. fn suggest(_s: &mut Session, p: &Value) -> Result { const C: &str = "text.spelling.suggest"; let word = str_param(p, "word").map(str::trim).filter(|w| !w.is_empty()).ok_or_else(|| bad(C, "missing `word`"))?; diff --git a/crates/engine/src/tests/spelling.rs b/crates/engine/src/tests/spelling.rs index 6757e576d..fd03c1817 100644 --- a/crates/engine/src/tests/spelling.rs +++ b/crates/engine/src/tests/spelling.rs @@ -5,16 +5,19 @@ use serde_json::{Value, json}; use crate::*; +/// A session with a new 400 × 400 document. fn session() -> Session { let mut s = Session::new(); s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap(); s } +/// Runs a command that must succeed and gives its result. fn run(s: &mut Session, id: &str, p: Value) -> Value { s.execute(id, &p).unwrap() } +/// New point type at (10, `y`) holding `t`. fn text(s: &mut Session, y: f64, t: &str) -> NodeId { NodeId(run(s, "text.create", json!({"x": 10, "y": y, "text": t}))["id"].as_u64().unwrap()) } @@ -60,6 +63,8 @@ fn check_goes_through_visible_unlocked_type_in_paint_order_or_the_ids_given() { assert_eq!(ids, [a.0, b.0], "hidden and locked type are left out"); assert_eq!(unknown(&mut s, json!({"ids": [b.0]})), [(b.0, 7, 12, "wvxzq".to_string())]); assert_eq!(unknown(&mut s, json!({"ids": null})).len(), 2, "null ids is the default"); + let both = [(a.0, 6, 11, "qzxvw".to_string()), (b.0, 7, 12, "wvxzq".to_string())]; + assert_eq!(unknown(&mut s, json!({"ids": [b.0, a.0]})), both, "given ids are listed in paint order too"); for ids in [json!(b.0), json!("all"), json!({"id": b.0}), json!([-1]), json!(["x"]), json!([999_999])] { assert!(s.execute("text.spelling.check", &json!({"ids": ids})).is_err(), "malformed ids {ids} is refused"); } @@ -87,3 +92,14 @@ fn suggest_offers_dictionary_words() { assert!(s.execute("text.spelling.suggest", &json!({"word": "drawng", "max": max})).is_err(), "max {max}"); } } + +#[test] +fn each_paragraph_is_checked_on_its_own_at_the_bytes_of_the_whole_text() { + let mut s = session(); + // Read as one block, the Vietnamese first paragraph would make the whole text Vietnamese, + // which has no dictionary, and the English typo below it would go unmarked. + let vi = "Cảm ơn những người bạn"; + let t = text(&mut s, 50.0, &format!("{vi}\ndrawing qzxvw tools")); + let at = vi.len() + 1 + "drawing ".len(); + assert_eq!(unknown(&mut s, json!({})), [(t.0, at as u64, at as u64 + 5, "qzxvw".to_string())]); +} diff --git a/docs/mcp.md b/docs/mcp.md index c20665d82..c7da439c2 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -5468,8 +5468,10 @@ returns the new value. `text.spelling.check {ids?}` lists the words of the document's type that its dictionaries don't know: every visible, unlocked type object in paint order, or those in `ids` (and in the groups `ids` names). Each run is checked -in its Character panel Language (English when it has none) by craft-proof; a language without a spelling dictionary -marks nothing. Words in capitals, words with digits and web addresses are taken as correct. The result is +in its Character panel Language (English when it has none) by craft-proof, one paragraph at a time; a language without +a spelling dictionary marks nothing. English words come from the first dictionary craft-proof finds: a Hunspell +dictionary in the user's dictionaries folder, the SCOWL dictionary for that English built in from craft-i18n, a +system Hunspell dictionary, and last the public-domain Moby word list built into craft-proof. Words in capitals, words with digits and web addresses are taken as correct. The result is `{words: [{id, start, end, word, lang}], more}`: `start` and `end` are byte offsets in the type object's text, the ones `text.editRange` takes, so `text.editRange {id, start, end, insert}` changes a word as one undo step. At most 10,000 words are listed; `more` is `true` when there were more. `text.spelling.suggest {word, max?, lang?}` gives diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 64e7fcf0b..f57c72aa3 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1771,7 +1771,7 @@ A double-click on a Repeat (Radial, Grid or Mirror) with the Selection tool ente ## Spell check engine (2026-11-08) -Edit › Spelling has the commands its Check Spelling dialog will use, as the public documentation describes checking the document's text, suggesting replacements and changing a word ([Check and correct spelling](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/check-and-correct-spelling.html)). `text.spelling.check` lists the words the dictionaries don't know in every visible, unlocked type object, or in the ones given by `ids`, in paint order, each run checked in its Character panel Language (English when it has none; nothing in a language without a dictionary). Each word comes with its object and byte range, which `text.editRange` changes as one undo step. `text.spelling.suggest` gives one word's suggestions. Both come from craft-proof in [storytold/craft-libs](https://github.com/storytold/craft-libs), with English from the public-domain Moby word list (#4936; `tests/spelling.rs`). The Check Spelling dialog, Auto Spell Check and Edit Custom Dictionary remain, so the Edit › Spelling menu items are still placeholders. +Edit › Spelling has the commands its Check Spelling dialog will use, as the public documentation describes checking the document's text, suggesting replacements and changing a word ([Check and correct spelling](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/check-and-correct-spelling.html)). `text.spelling.check` lists the words the dictionaries don't know in every visible, unlocked type object, or in the ones given by `ids`, in paint order, each run checked in its Character panel Language (English when it has none; nothing in a language without a dictionary). Each word comes with its object and byte range, which `text.editRange` changes as one undo step. `text.spelling.suggest` gives one word's suggestions. Both come from craft-proof in [storytold/craft-libs](https://github.com/storytold/craft-libs), which checks each paragraph on its own. English words come from the first dictionary craft-proof finds: a Hunspell dictionary in the user's dictionaries folder, the SCOWL dictionary for that English built in from craft-i18n, a system Hunspell dictionary, and last the public-domain Moby word list built into craft-proof (#4936; `tests/spelling.rs`). The Check Spelling dialog, Auto Spell Check and Edit Custom Dictionary remain, so the Edit › Spelling menu items are still placeholders. ## Control bar: Paragraph section while type is selected (2026-11-06) diff --git a/docs/type-parity.md b/docs/type-parity.md index 8872308ee..36f53584c 100644 --- a/docs/type-parity.md +++ b/docs/type-parity.md @@ -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 | | 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 (English from the public-domain Moby word list built into it; other languages from installed Hunspell dictionaries). The Check Spelling dialog, Auto Spell Check and Edit Custom Dictionary 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 Check Spelling dialog, Auto Spell Check and Edit Custom Dictionary remain (the Edit › Spelling items are still off). Evidence: `crates/engine/src/tests/spelling.rs` | | 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 (`windows_alt_codes` tests; not yet tried on a Windows machine). `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 |