diff --git a/crates/text/src/fontdb.rs b/crates/text/src/fontdb.rs index 3411745d..d1c90716 100644 --- a/crates/text/src/fontdb.rs +++ b/crates/text/src/fontdb.rs @@ -330,7 +330,7 @@ pub struct FontDb { /// Where the system font scan looks. #[cfg_attr(target_arch = "wasm32", allow(dead_code))] font_paths: FontPaths, - /// The installed fonts' other names (localized, legacy, PostScript) by normalized name. + /// The installed fonts' other names (localized, legacy, full, PostScript) by normalized name. aliases: RwLock>>, /// Set once the font folders have been scanned. Lookups by family name wait for the first /// scan, so what they find never depends on what ran before them. @@ -499,13 +499,14 @@ fn all_names(font: &skrifa::FontRef<'_>, id: StringId) -> Vec { /// The names a face answers to besides its (English) family and style, normalized ([`norm`]): /// its family in every language (typographic, else legacy: `ヒラギノ角ゴシック`), its style likewise /// (`ミディアム`), its legacy family names (`Hiragino Sans W3`) with their legacy styles, paired per -/// language record (the Mac and Windows records of a face differ), and its PostScript names -/// (`HiraginoSans-W3`, as PDF and `.ai` files name fonts). +/// language record (the Mac and Windows records of a face differ), its full names (name ID 4), +/// and its PostScript names (`HiraginoSans-W3`, as PDF and `.ai` files name fonts). #[derive(Clone, Debug, Default)] struct FaceKeys { families: Vec, styles: Vec, legacy: Vec<(String, String)>, + full_names: Vec, postscript: Vec, } @@ -538,14 +539,15 @@ impl FaceKeys { .collect() }; let mut postscript: Vec = all_names(font, StringId::POSTSCRIPT_NAME).iter().map(|n| norm(n)).collect(); - for v in [&mut families, &mut styles, &mut postscript] { + let mut full_names: Vec = all_names(font, StringId::FULL_NAME).iter().map(|n| norm(n)).collect(); + for v in [&mut families, &mut styles, &mut full_names, &mut postscript] { v.retain(|k| !k.is_empty()); v.sort(); v.dedup(); } legacy.sort(); legacy.dedup(); - FaceKeys { families, styles, legacy, postscript } + FaceKeys { families, styles, legacy, full_names, postscript } } } @@ -704,7 +706,7 @@ impl FontTraits { struct Alias { /// The family's own name. family: String, - /// The face the name names, for legacy family names and PostScript names. + /// The face the name names, for legacy family names, full names and PostScript names. style: Option, /// A legacy family name's legacy style (`Regular` beside `Hiragino Sans W3`). paired: Option, @@ -720,7 +722,12 @@ impl Alias { .iter() .map(|(f, s)| (f.clone(), Alias { family: family.to_string(), style: Some(style.to_string()), paired: Some(s.clone()) })), ); - v.extend(keys.postscript.iter().map(|k| (k.clone(), Alias { family: family.to_string(), style: Some(style.to_string()), paired: None }))); + v.extend( + keys.postscript + .iter() + .chain(&keys.full_names) + .map(|k| (k.clone(), Alias { family: family.to_string(), style: Some(style.to_string()), paired: None })), + ); v } } @@ -833,7 +840,8 @@ fn named_instances(f: &skrifa::FontRef<'_>, family: &str, style: &str, keys: &mu v.sort(); v.dedup(); } - let keys = FaceKeys { families: keys.families.clone(), styles, legacy: vec![], postscript }; + // Name ID 4 describes the default face, not every named instance of its variable font. + let keys = FaceKeys { families: keys.families.clone(), styles, legacy: vec![], full_names: vec![], postscript }; let variations = tags.iter().zip(&coords).map(|(t, v)| (t.to_be_bytes(), *v)).collect(); out.push(FaceStyle { family: family.to_string(), @@ -1069,6 +1077,7 @@ impl WantedFonts { let names = std::iter::once(f.family.to_ascii_lowercase()) .chain(f.keys.families.iter().cloned()) .chain(f.keys.legacy.iter().map(|(family, _)| family.clone())) + .chain(f.keys.full_names.iter().cloned()) .chain(f.keys.postscript.iter().cloned()); for n in names { candidates.extend(self.by_name.get(&n).into_iter().flatten()); @@ -1111,9 +1120,13 @@ fn provides(w: &WantedFont, faces: &[FaceStyle]) -> bool { if let Some(f) = faces.iter().find(|f| f.keys.families.contains(&key)) { return styled(&f.family); } - // A legacy family with its legacy style, or a PostScript name, names its face whatever style - // is asked for. - if faces.iter().any(|f| f.keys.legacy.iter().any(|(family, style)| *family == key && *style == sk) || f.keys.postscript.contains(&key)) { + // A legacy family with its legacy style, a full name or a PostScript name names its face + // whatever style is asked for. + if faces.iter().any(|f| { + f.keys.legacy.iter().any(|(family, style)| *family == key && *style == sk) + || f.keys.full_names.contains(&key) + || f.keys.postscript.contains(&key) + }) { return true; } // A legacy family with another style: that style of the family. @@ -1727,9 +1740,9 @@ impl FontDb { /// The family's own name and the style for a name given in a document: any of a family's /// names (`ヒラギノ角ゴシック` is Hiragino Sans), a legacy family name (`Hiragino Sans W6` with its - /// legacy style names the W6 face) or a PostScript name (`HiraginoSans-W6`, as PDF and `.ai` - /// files name fonts). Case and anything but letters and digits are ignored. A name nothing - /// answers to comes back as it is. + /// legacy style names the W6 face), a full name (name ID 4) or a PostScript name + /// (`HiraginoSans-W6`, as PDF and `.ai` files name fonts). Case and anything but letters and + /// digits are ignored. An unknown name is returned unchanged. pub fn canonical(&self, family: &str, style: &str) -> (String, String) { if self.is_loaded(family) || self.read_catalog().contains_key(&family.to_ascii_lowercase()) { return (family.to_string(), style.to_string()); @@ -1741,8 +1754,8 @@ impl FontDb { found.extend(v.iter().cloned()); } let ns = norm(style); - // A family name keeps the style asked for; a legacy pair names its face; a PostScript name - // its face whatever style is asked for. + // A family name keeps the style asked for; a legacy pair names its face; a full or + // PostScript name its face whatever style is asked for. if let Some(a) = found.iter().find(|a| a.style.is_none()) { return (a.family.clone(), style.to_string()); } diff --git a/crates/text/src/tests_sysfonts.rs b/crates/text/src/tests_sysfonts.rs index 5fb0cee4..009813ca 100644 --- a/crates/text/src/tests_sysfonts.rs +++ b/crates/text/src/tests_sysfonts.rs @@ -589,3 +589,50 @@ fn thai_falls_back_to_a_font_made_for_thai() { assert!(first.is_some(), "Thai drawn in {:?}; {thai} is installed", thai_glyphs.iter().map(family).collect::>()); assert!(thai_glyphs.iter().all(|g| g.gid != 0), "no .notdef"); } + +/// #1258: full names can differ from the PostScript name, and name one particular face. +#[test] +fn full_names_resolve_faces_and_missing_font_searches() { + let data = named(&[ + (3, EN, 1, "Alias Script"), + (3, EN, 2, "Italic"), + (3, EN, 4, "Alias Script Italic"), + (3, JA, 4, "例の書体 斜体"), + (3, EN, 6, "AliasScriptI"), + ]); + let dir = std::env::temp_dir().join(format!("vc-sysfonts-{}-full-name", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let file = dir.join("Alias.ttf"); + std::fs::write(&file, &data).unwrap(); + for loaded in [false, true] { + let db = FontDb::with_font_dirs(vec![dir.clone()]); + if loaded { + assert!(db.add_font(data.clone()) > 0); + } + for name in ["Alias Script Italic", "Alias-Script-Italic", "ALIAS SCRIPT ITALIC", "例の書体 斜体", "AliasScriptI"] { + for requested_style in ["Regular", "Bold"] { + let (face, matched) = db.resolve(name, requested_style).unwrap(); + assert_eq!((face.family.as_str(), face.style.as_str(), matched), ("Alias Script", "Italic", FontMatch::Exact)); + let wanted = WantedFonts::new(&[WantedFont { family: name.into(), style: requested_style.into(), installed: None }]); + assert_eq!(wanted.provided_by(&file), [0], "search for {name} {requested_style}"); + } + } + // Full names are face aliases; the API for actual PostScript names stays precise. + assert!(db.find_postscript("Alias Script Italic").is_none()); + assert!(db.by_postscript_name("Alias Script Italic").is_none()); + assert_eq!(db.find_postscript("AliasScriptI").unwrap().style, "Italic"); + } + let _ = std::fs::remove_dir_all(dir); +} + +/// A real family's name keeps the requested style even if another face uses it as a full name. +#[test] +fn a_full_name_does_not_override_an_installed_family() { + let db = FontDb::with_font_dirs(vec![]); + assert!( + db.add_font(named(&[(3, EN, 1, "Alias Script"), (3, EN, 2, "Italic"), (3, EN, 4, "Alias Script Italic"), (3, EN, 6, "AliasScriptI")])) > 0 + ); + assert!(db.add_font(named(&[(3, EN, 1, "Alias Script Italic"), (3, EN, 2, "Regular"), (3, EN, 6, "OtherAlias-Regular")])) > 0); + let (face, matched) = db.resolve("Alias Script Italic", "Regular").unwrap(); + assert_eq!((face.family.as_str(), face.style.as_str(), matched), ("Alias Script Italic", "Regular", FontMatch::Exact)); +} diff --git a/crates/text/src/tests_variable.rs b/crates/text/src/tests_variable.rs index 00bc8f4e..10e6eb5a 100644 --- a/crates/text/src/tests_variable.rs +++ b/crates/text/src/tests_variable.rs @@ -246,3 +246,37 @@ fn segoe_ui_variable_lists_its_instances() { assert!(w(&bold) > w(&light), "{} vs {}", w(&bold), w(&light)); } } + +/// #1258: a variable font's full name identifies the default face, not a named weight. +#[test] +fn a_variable_full_name_names_its_default_face() { + let bytes = font(); + let source = skrifa::FontRef::new(&bytes).unwrap(); + let names = name_table(&[ + (1, FAMILY), + (2, "Regular"), + (4, "Varitest Default Face"), + (6, "VaritestSans-Regular"), + (256, "Weight"), + (257, "Normal"), + (258, "SemiBold"), + (259, "Bold"), + (261, "BOLD"), + ]) + .unwrap(); + let mut builder = write_fonts::FontBuilder::new(); + builder.add_raw(write_fonts::types::Tag::new(b"name"), names); + for record in source.table_directory.table_records() { + let tag = write_fonts::types::Tag::new(&record.tag().to_be_bytes()); + if !builder.contains(tag) { + builder.add_raw(tag, source.table_data(record.tag()).unwrap().as_bytes()); + } + } + let db = FontDb::with_font_dirs(vec![]); + assert_eq!(db.add_font(builder.build()), 3); + let (face, matched) = db.resolve("Varitest Default Face", "Bold").unwrap(); + assert_eq!((face.family.as_str(), face.style.as_str(), matched), (FAMILY, "Regular", FontMatch::Exact)); + assert!(face.variations().is_empty()); + let bold = db.resolve("VaritestSans-Bold", "Regular").unwrap().0; + assert_eq!(bold.variations(), [(*b"wght", 700.0)]); +} diff --git a/docs/gaps.md b/docs/gaps.md index 72cc66ec..16655b5f 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Gaps -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (G11: no-op anchor Align keeps history and saved state (#1180 follow-up); estimates unchanged; G1: dedicated Eyedropper cursor and precise-cursor checks (#1269); estimates unchanged; G1: Eyedropper reads visible locked and outside-isolation sources (#1251); estimate unchanged; G11: Direct Selection segment Delete (#1278); estimates unchanged; G1: painted strokes regain the existing default after None/zero-width inheritance (#1253); estimate unchanged; G15: large-selection edits, cached live effects and faster Pathfinder, #1173; G4: proportional vertical metrics done, #966; G11: Shape Builder merge seams on grids of lines fixed, #893; scaled text controls corrected, #1034; group font-size preservation deferred) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (G4: full-name font matching (#1258); Windows font-service QA pending; estimate unchanged; G11: no-op anchor Align keeps history and saved state (#1180 follow-up); estimates unchanged; G1: dedicated Eyedropper cursor and precise-cursor checks (#1269); estimates unchanged; G1: Eyedropper reads visible locked and outside-isolation sources (#1251); estimate unchanged; G11: Direct Selection segment Delete (#1278); estimates unchanged; G1: painted strokes regain the existing default after None/zero-width inheritance (#1253); estimate unchanged; G15: large-selection edits, cached live effects and faster Pathfinder, #1173; G4: proportional vertical metrics done, #966; G11: Shape Builder merge seams on grids of lines fixed, #893; scaled text controls corrected, #1034; group font-size preservation deferred) · **Target:** Adobe Illustrator 2026 (30.x) Every known shortfall against Illustrator, one entry each, **ranked by how much it stops a professional from switching**. This is the work list: unless you were given a task, pick the highest-ranked gap you can make @@ -104,6 +104,7 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall ## G4 Advanced type +- **Improved (#1258):** full font names (name ID 4) resolve the face instead of falling back, and Missing Fonts searches recognize the same aliases. Synthetic regressions cover loaded/cataloged fonts, localized names, real-family precedence and variable default faces. Windows font-service activation still needs reporter QA; estimate unchanged. - **Missing:** hyphenation and justification options, Optical Margin Alignment (type core); CJK vertical composition (ruby, mojikumi sets and dialog, kinsoku settings #633, manual tate-chu-yoko), Middle Eastern features beyond bidi and digit types (Character Direction, kashidas, composers), variable font axes, spell check with an open dictionary, @@ -213,6 +214,7 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | G4: full-name font matching and Missing Fonts searches verified (#1258); Windows font-service QA pending; estimate unchanged | | 2026-10-12 | minor | G11: no-op anchor Align no longer consumes Undo, clears Redo or marks saved paths modified (#1180 follow-up); estimate unchanged | | 2026-10-12 | minor | G1: Eyedropper uses an original pipette glyph; precise setting and DPI checks verified (#1269); estimates unchanged | | 2026-10-12 | minor | G1: Eyedropper reads visible locked and outside-isolation sources (#1251); estimate unchanged | diff --git a/docs/roadmap.md b/docs/roadmap.md index be0a1c56..505fe76b 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-10-12 · **Change:** minor (M1: no-op anchor Align keeps Undo, Redo and saved state (#1180 follow-up); estimates unchanged; M14: dedicated Eyedropper cursor (#1269); estimates unchanged; M3: Eyedropper reads visible locked and outside-isolation sources (#1251); estimates unchanged; M2: Direct Selection segment picks and Delete (#1278); estimates unchanged; M3: adding paint to an unpainted zero-width stroke restores 1 pt (#1253); estimates unchanged; M12: graph designs, graph data, Graph Type and label options; M14: native macOS save Format menu, #866; M6: compound appearance and member fill-rule fixes, #1197; estimates unchanged; M9: PDF raster-effect cap warnings; G11: #893 fixed, #937 open; M7: accurate character controls after scaling, #1034; estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (M7: full-name font matching (#1258); Windows font-service QA pending; estimates unchanged; M1: no-op anchor Align keeps Undo, Redo and saved state (#1180 follow-up); estimates unchanged; M14: dedicated Eyedropper cursor (#1269); estimates unchanged; M3: Eyedropper reads visible locked and outside-isolation sources (#1251); estimates unchanged; M2: Direct Selection segment picks and Delete (#1278); estimates unchanged; M3: adding paint to an unpainted zero-width stroke restores 1 pt (#1253); estimates unchanged; M12: graph designs, graph data, Graph Type and label options; M14: native macOS save Format menu, #866; M6: compound appearance and member fill-rule fixes, #1197; estimates unchanged; M9: PDF raster-effect cap warnings; G11: #893 fixed, #937 open; M7: accurate character controls after scaling, #1034; estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) Forward-looking plan. Where VectorCraft stands is in [`ROADMAP.md`](../ROADMAP.md) and [target-app-parity.md](target-app-parity.md); the ranked work list is [gaps.md](gaps.md). Hours are Opus 5.5 @@ -57,7 +57,7 @@ Checked 2026-10-10 from the code, tests and open issues; no workflow fails, so V | M4 | Files (native, SVG, PDF, raster, Export for Screens, clipboard interop) | ✅ done (M4.14–M4.98 on 2026-10-05): Save As/Save a Copy/Revert/templates, Data Recovery and background save, SVG import and export fidelity (units, text, symbols, filters, SVGZ, Preserve Editing, SVG Options), PDF import (colours, layers, masks, editable text, security) and export (presets, marks, bleed, ICC and output intent, raster effects, subset fonts, PDF/X-1a, X-3 and X-4, PDF layers, overprint, page thumbnails, fast web view, PDF 1.3), Place and Links, Package, EPS and DXF in and out, EMF/WMF, TIFF/BMP/Targa/PSD/GIF/PNG-8 export, Print and print presets, slices, Save for Web, Asset Export, clipboard flavours, File Info; AF.1–AF.4 add Affinity import (native layers, artboards, vectors, text and images from Affinity 1–3 files; the embedded preview only as a warned fallback; AF.4 expands current `.af` fixtures and every-board round-trip validation). Left: DWG (use DXF), PSD placement as layers, overprint read back from PDF, Affinity export, polish | 3–6 (existing scope; Affinity export unestimated) | | M5 | Performance | 🟡 background render + caches + MT done; `vectorcraft-cli bench` and `vectorcraft-cli perf` (budget suite); 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); 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 (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) | 3–6 | -| M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | 🟡 Document-point character controls after object/group scaling (#1034; optional font-size preservation remains separate), Character/Paragraph Styles, per-paragraph paragraph attributes, 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, 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, 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) | 🟡 Document-point character controls after object/group scaling (#1034; optional font-size preservation remains separate), Character/Paragraph Styles, per-paragraph paragraph attributes, 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, 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 | | 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; 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; initial Revolve only; SVG Filters, remaining raster effects and 3D 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; brushes/symbols in progress | 23–37 | @@ -82,6 +82,7 @@ After placed documents (File › Place of a `.vectorcraft` file, linked), M4.14 | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | M7: full-name font matching and Missing Fonts searches (#1258); Windows font-service QA pending; estimates unchanged | | 2026-10-12 | minor | M1: repeated or rounding-only anchor Align preserves Undo, Redo and saved state (#1180 follow-up); estimate unchanged | | 2026-10-12 | minor | M14: Eyedropper cursor distinguished from its precise crosshair (#1269); estimates unchanged | | 2026-10-12 | minor | M3: Eyedropper reads visible locked and outside-isolation sources (#1251); estimates unchanged | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 6ea0ba6d..9f78d5f0 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-10-12 · **Change:** minor (Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) The authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it; [`gaps.md`](gaps.md) is the work list made from it. Deep areas have their own checklists: [UI and interaction](ui-parity.md), @@ -78,7 +78,7 @@ dominate; 3D, graphs and automation are specialist). They sum to 105. Feature de | Live vector effects | 5 | 85% | Outline Object, Effect › Rasterize, Pathfinder Hard Mix/Soft Mix/Trap, SVG Filters, the Transform effect's missing options (#885) | 6–10 | measured count 44/50, depth estimated | | Raster effects (Effect Gallery) | 4 | 48% | 29 Photoshop-style filters (Artistic, Sketch), the Effect Gallery dialog, Load Texture | 24–40 | measured count 28/57 | | 3D and Materials (and Turntable) | 4 | ~8% | initial live Revolve done (flat shading, one light, no caps, approximate visibility for intersecting surfaces); Extrude & Bevel, Inflate, Rotate, materials, Turntable views, 3D export | 50–85 | estimated (Revolve landed after the first measure, #846) | -| Type core | 9 | 80% | hyphenation and justification options, 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 | +| Type core | 9 | 80% | hyphenation and justification options, 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; full-name font matches regression-tested (#1258) | | 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 | | Symbols, blends, envelopes, Repeat, perspective | 5 | 85% | 30.7's Blend panel with ease in/out, dynamic symbols, symbol libraries, Start Global Edit, envelope Anti-Alias/Preserve Shape | 6–10 | estimated (lowered from 88%: target moved) | | Image Trace, graphs, image tools | 3 | 75% | graph Column designs, Image Trace Create Strokes depth (Create Object Mosaic is done) | 5–8 | estimated (raised from 72%) | @@ -474,10 +474,13 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Tests and robustness:** ~2,840 automated tests: model-based property tests, a junk-parameter sweep over every command, import fuzzing (SVG, PDF, libraries), golden renders, and MCP end-to-end tests over stdio. Shipped code never panics (workspace lints and a rollback safety net; see `docs/development.md`). - **Raster-effect resolution in PDF:** when the 64 million pixel cap on an effect image renders an object's raster effects below the document's raster effects resolution, PDF export warns once per object with the requested and the lowest achieved ppi (`crates/engine/src/tests_pdfraster.rs`). +- **Full font names (#1258):** OpenType name ID 4 identifies its face alongside family/legacy/PostScript names, including localized names, for loaded fonts, cataloged files and Missing Fonts searches. Synthetic name-table regressions cover full names distinct from their PostScript name, real-family precedence and variable fonts' default faces. Windows font-service activation remains unverified; scores and hours are unchanged. + ## Revision history | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Type core: full-name font matches and Missing Fonts searches verified with synthetic fonts (#1258); Windows font-service QA pending; scores and hours unchanged | | 2026-10-12 | minor | Selection: repeated or rounding-only anchor Align preserves Undo, Redo, saved state and path geometry (#1180 follow-up); engine regression tests; score and hours unchanged (estimated) | | 2026-10-12 | minor | UI chrome: original Eyedropper glyph and precise-cursor checks (#1269); score and hours unchanged (estimated) | | 2026-10-12 | minor | Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged | diff --git a/docs/type-parity.md b/docs/type-parity.md index a35dc039..394ea6f7 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-10-11 · **Change:** minor (Middle Eastern digit types: Character panel menu › Digits, `{digits}`; the font menu's script filter lists Thai fonts; Thai, Lao, Khmer and Myanmar lines break between words; the caret steps over whole clusters such as Thai consonants with their marks; Thai typed in a font without Thai falls back to a font made for Thai; earlier: Proportional Metrics in vertical type, `vpal`, #966) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Full-name font matching (#1258); Windows font-service QA pending; scores unchanged; Middle Eastern digit types: Character panel menu › Digits, `{digits}`; the font menu's script filter lists Thai fonts; Thai, Lao, Khmer and Myanmar lines break between words; the caret steps over whole clusters such as Thai consonants with their marks; Thai typed in a font without Thai falls back to a font made for Thai; earlier: Proportional Metrics in vertical type, `vpal`, #966) · **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 @@ -29,6 +29,7 @@ against Illustrator's public documentation; **estimated** unless noted. | Font menu: samples, live preview, filters, favourites; font matching by any name; Find Font | done | #339; script filter: Japanese, Latin, Thai | | Font menu: samples, live preview, filters, favourites; font matching by any name; Find Font | done | #339 | | Missing glyphs set in an installed font made for their script (Thai, Lao, Khmer, Myanmar first; then any font that has them) | done | Thai: Leelawadee UI, Tahoma, Thonburi, Sukhumvit Set, Noto Sans Thai, TLWG | +| Full font-name matching (OpenType name ID 4) | done | #1258: loaded/cataloged faces and Missing Fonts searches; localized names and variable default faces; Windows font-service QA pending | | Variable fonts | partial | named instances as styles; axis sliders missing | | Colour fonts (OpenType SVG / COLR) | partial | | | Every-line and Single-line composers | done | tuned by eye, never compared side by side | @@ -56,6 +57,7 @@ against Illustrator's public documentation; **estimated** unless noted. | Date | Change | Summary | |---|---|---| +| 2026-10-12 | minor | Full-name font matching and Missing Fonts searches (#1258): synthetic static/variable font regressions; Windows font-service QA pending; scores and hours unchanged | | 2026-10-11 | minor | Middle Eastern: digit types done (Character panel menu › Digits: Default, Arabic, Hindi, Farsi; `text.setFormat` / `text.setRangeStyle {digits}`); Character Direction, kashidas, composers and the split caret still missing; scores unchanged at this rounding | | 2026-10-11 | minor | Thai, Lao, Khmer and Myanmar words for the caret (Ctrl/Alt+arrows) and double-click; combining marks count as part of a word; score unchanged (estimated) | | 2026-10-11 | minor | Font menu: a Thai script filter beside Japanese and Latin; score unchanged (estimated) |