From b72c822f1d8e32a08b833402dd22a62a7f18fd07 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 3 Nov 2026 12:39:55 -0800 Subject: [PATCH 1/4] Add font-aware OpenType stylistic sets menu --- crates/text/src/fontdb.rs | 2 + crates/text/src/fontdb/stylistic_sets.rs | 128 +++++++++++++++++++++++ crates/text/src/lib.rs | 6 +- crates/ui-egui/src/panels/opentype.rs | 29 ++++- docs/roadmap.md | 5 +- docs/target-app-parity.md | 5 +- docs/type-parity.md | 5 +- 7 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 crates/text/src/fontdb/stylistic_sets.rs diff --git a/crates/text/src/fontdb.rs b/crates/text/src/fontdb.rs index 6eca651bb..1972577ef 100644 --- a/crates/text/src/fontdb.rs +++ b/crates/text/src/fontdb.rs @@ -26,6 +26,7 @@ mod glyph_pen; mod lookup; mod names; mod scan; +mod stylistic_sets; mod traits; mod wanted; @@ -57,6 +58,7 @@ pub(crate) use scan::file_face_names_within; #[cfg(test)] use scan::{MAX_FACES, enumerate_faces}; pub use scan::{is_font_file, is_suitcase}; +pub use stylistic_sets::StylisticSet; use traits::Alias; #[cfg(test)] use traits::{FaceKeys, face_names}; diff --git a/crates/text/src/fontdb/stylistic_sets.rs b/crates/text/src/fontdb/stylistic_sets.rs new file mode 100644 index 000000000..3bded3d03 --- /dev/null +++ b/crates/text/src/fontdb/stylistic_sets.rs @@ -0,0 +1,128 @@ +//! The font-defined OpenType stylistic sets offered by the OpenType panel. +//! + +use skrifa::raw::TableProvider; +use skrifa::raw::tables::layout::{FeatureList, FeatureParams}; + +use super::FontFace; +use super::names::name; + +/// One `ss01`–`ss20` feature in a face. `number` is the tag's number; `name` is the font's UI +/// label when it supplies one, otherwise `Set 1` through `Set 20`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StylisticSet { + pub number: u8, + pub name: String, +} + +/// The `ssNN` number in a feature tag, if it is one of the twenty registered sets. +fn number(tag: [u8; 4]) -> Option { + if &tag[..2] != b"ss" || !tag[2].is_ascii_digit() || !tag[3].is_ascii_digit() { + return None; + } + let n = usize::from(tag[2] - b'0') * 10 + usize::from(tag[3] - b'0'); + (1..=20).contains(&n).then_some(n) +} + +/// Collect registered set records from either layout table. Fonts may carry a set in GSUB, GPOS, +/// or both. A readable custom name takes precedence over the numbered fallback. +fn collect(font: &skrifa::FontRef<'_>, features: FeatureList<'_>, labels: &mut [Option; 20]) { + for record in features.feature_records().iter().take(4096) { + let Some(n) = number(record.feature_tag().to_be_bytes()) else { continue }; + let Ok(feature) = record.feature(features.offset_data()) else { continue }; + if feature.lookup_list_indices().is_empty() { + continue; + } + let custom = feature.feature_params().and_then(Result::ok).and_then(|params| match params { + FeatureParams::StylisticSet(p) => name(font, &[p.ui_name_id()]), + _ => None, + }); + let slot = &mut labels[n - 1]; + if let Some(custom) = custom { + *slot = Some(custom); + } else if slot.is_none() { + *slot = Some(format!("Set {n}")); + } + } +} + +impl FontFace { + /// Sets this face lists in GSUB or GPOS, in number order. Damaged tables and records are + /// skipped, and no more than 4096 feature records from either table are read. + pub fn stylistic_sets(&self) -> Vec { + let Some(font) = self.skrifa() else { return Vec::new() }; + let mut labels: [Option; 20] = std::array::from_fn(|_| None); + if let Ok(gsub) = font.gsub() + && let Ok(features) = gsub.feature_list() + { + collect(&font, features, &mut labels); + } + if let Ok(gpos) = font.gpos() + && let Ok(features) = gpos.feature_list() + { + collect(&font, features, &mut labels); + } + labels.into_iter().enumerate().filter_map(|(i, name)| Some(StylisticSet { number: u8::try_from(i + 1).ok()?, name: name? })).collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use skrifa::MetadataProvider; + use write_fonts::FontBuilder; + use write_fonts::tables::gsub::{Gsub, SingleSubstFormat2, SubstitutionLookup, SubstitutionLookupList}; + use write_fonts::tables::layout::{ + CoverageFormat1, Feature, FeatureList, FeatureParams as WriteParams, FeatureRecord, LangSys, Lookup, Script, ScriptList, ScriptRecord, + StylisticSetParams, + }; + use write_fonts::types::{GlyphId16, NameId, Tag}; + + #[test] + fn registered_set_numbers_only() { + assert_eq!(number(*b"ss01"), Some(1)); + assert_eq!(number(*b"ss20"), Some(20)); + assert_eq!(number(*b"ss00"), None); + assert_eq!(number(*b"ss21"), None); + assert_eq!(number(*b"salt"), None); + } + + #[test] + fn face_lists_only_its_sets_with_the_fonts_name() { + const BASE: &[u8] = include_bytes!("../../../../assets/fonts/SourceSans3-Regular.ttf"); + let base = skrifa::FontRef::new(BASE).unwrap(); + let gid = |c| GlyphId16::new(u16::try_from(base.charmap().map(c).unwrap().to_u32()).unwrap()); + let subst = SingleSubstFormat2::new(CoverageFormat1::new(vec![gid('a')]).into(), vec![gid('A')]); + let lookup = SubstitutionLookup::Single(Lookup::new(Default::default(), vec![subst.into()])); + let features = FeatureList::new(vec![ + FeatureRecord::new(Tag::new(b"ss01"), Feature::new(Some(WriteParams::StylisticSet(StylisticSetParams::new(NameId::new(256)))), vec![0])), + FeatureRecord::new(Tag::new(b"ss03"), Feature::new(None, vec![0])), + FeatureRecord::new(Tag::new(b"ss05"), Feature::new(None, vec![])), + ]); + let scripts = ScriptList::new(vec![ScriptRecord::new(Tag::new(b"DFLT"), Script::new(Some(LangSys::new(vec![0, 1, 2])), vec![]))]); + let mut builder = FontBuilder::new(); + let names = crate::test_fonts::name_table(&[ + (1, "Stylistic Test Sans"), + (2, "Regular"), + (4, "Stylistic Test Sans Regular"), + (6, "StylisticTestSans-Regular"), + (256, "Curved Capitals"), + ]) + .unwrap(); + builder.add_raw(Tag::new(b"name"), names); + builder.add_table(&Gsub::new(scripts, features, SubstitutionLookupList::new(vec![lookup]))).unwrap(); + for record in base.table_directory.table_records() { + let tag = Tag::new(&record.tag().to_be_bytes()); + if !builder.contains(tag) { + builder.add_raw(tag, base.table_data(record.tag()).unwrap().as_bytes()); + } + } + let db = super::super::FontDb::with_font_dirs(vec![]); + assert_eq!(db.add_font(builder.build()), 1); + let face = db.face("Stylistic Test Sans", "Regular").unwrap(); + assert_eq!( + face.stylistic_sets(), + [StylisticSet { number: 1, name: "Curved Capitals".into() }, StylisticSet { number: 3, name: "Set 3".into() }] + ); + } +} diff --git a/crates/text/src/lib.rs b/crates/text/src/lib.rs index a49e187ae..eb9f14bf3 100644 --- a/crates/text/src/lib.rs +++ b/crates/text/src/lib.rs @@ -35,9 +35,9 @@ pub use bitmap::GlyphImage; pub use craft_fonts::{CRAFT_FONTS, CraftFont, WEB_FONTS, WebFont}; pub use features::{EXTRA_FEATURES, LIGATURE_TRACKING_LIMITS, MAX_STYLE_FEATURES, OtFeatures, explicit_ligatures, ligatures_suppressed_by}; pub use fontdb::{ - FALLBACK_FAMILY, FontClass, FontDb, FontFace, FontMatch, FontTraits, IcfMargins, PlatformFontFiles, WantedFont, WantedFonts, app_font_dir, - is_font_file, is_suitcase, set_app_font_dir, set_platform_font_files, set_user_font_dirs, set_user_font_dirs_confined, style_weight, - system_font_dirs, user_font_dirs, + FALLBACK_FAMILY, FontClass, FontDb, FontFace, FontMatch, FontTraits, IcfMargins, PlatformFontFiles, StylisticSet, WantedFont, WantedFonts, + app_font_dir, is_font_file, is_suitcase, set_app_font_dir, set_platform_font_files, set_user_font_dirs, set_user_font_dirs_confined, + style_weight, system_font_dirs, user_font_dirs, }; pub use hyphen::{hyphenation_exceptions, set_hyphenation_exceptions}; use std::sync::Arc; diff --git a/crates/ui-egui/src/panels/opentype.rs b/crates/ui-egui/src/panels/opentype.rs index d6c62a13d..443ca87e1 100644 --- a/crates/ui-egui/src/panels/opentype.rs +++ b/crates/ui-egui/src/panels/opentype.rs @@ -4,7 +4,7 @@ use egui::Ui; use serde_json::json; -use vectorcraft_text::{EXTRA_FEATURES, OtFeatures}; +use vectorcraft_text::{EXTRA_FEATURES, FontDb, OtFeatures}; use super::character::{shared, text_editing, text_style}; use crate::VectorcraftApp; @@ -199,6 +199,21 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { change = Some(Change::Toggle(flag, !on)); } } + // The font, not the text, decides which numbered sets can be offered. Each set is an + // independent feature, so the menu permits several at once. A font's own label wins over + // the numbered fallback. See the public Stylistic Sets help page. + let sets = FontDb::global().face(&st.font_family, &st.font_style).map(|face| face.stylistic_sets()).unwrap_or_default(); + ui.add_enabled_ui(!sets.is_empty(), |ui| { + ui.menu_button(tl!("Stylistic Sets"), |ui| { + for set in &sets { + let flag = Flag::Extra(EXTRA_FEATURES[usize::from(set.number - 1)]); + let on = flag.get(&next); + if widgets::check(ui, &set.name, on, true) { + change = Some(Change::Toggle(flag, !on)); + } + } + }); + }); if cur.ligatures && vectorcraft_text::ligatures_suppressed_by(st.tracking) && !vectorcraft_text::explicit_ligatures(&st.features) { widgets::dim_label(ui, tl!("Ligatures are off while the text is tracked.")); } @@ -284,6 +299,18 @@ mod tests { } } + /// Stylistic sets are independent: selecting a second set keeps the first, and clearing one + /// keeps the other. The command reaches the selected text's stored OpenType feature tags. + #[test] + fn multiple_stylistic_sets_stay_independent() { + let mut app = selected_text(); + edit(&mut app, Change::Toggle(Flag::Extra("ss01"), true)); + edit(&mut app, Change::Toggle(Flag::Extra("ss03"), true)); + assert_eq!(text_style(&app).unwrap().0.features, ["ss01", "ss03"]); + edit(&mut app, Change::Toggle(Flag::Extra("ss01"), false)); + assert_eq!(text_style(&app).unwrap().0.features, ["ss03"]); + } + /// The Figure menu lists its five entries in order; Proportional Lining sets `pnum` and /// `lnum` and clears `tnum` and `onum`, Tabular Lining clears `pnum`, Proportional Oldstyle /// clears `lnum`, and Default Figure and Tabular Oldstyle clear `pnum` and `lnum`. diff --git a/docs/roadmap.md b/docs/roadmap.md index cd27fae7f..d4bf3859c 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-03 · **Change:** minor (new gradients follow document color mode; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-03 · **Change:** minor (OpenType panel offers font-defined Stylistic Sets; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -105,7 +105,7 @@ Checked 2026-10-10 from the code, tests and open issues; no workflow fails, so V | 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; 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) | 🟡 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, 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) | 🟡 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) | 🟡 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) | 🟡 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 | @@ -158,6 +158,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-11-03 | minor | M7: the OpenType panel offers the selected font's available Stylistic Sets with custom or numbered names; estimates unchanged. | | 2026-11-03 | minor | M1: Enclosing Lasso adds freehand whole-object selection under Object › Plug-ins; estimates unchanged. | | 2026-11-02 | minor | Font-family fields in Character, Properties and Control step selected text with the wheel (#5253), with one undo step per notch; scores and estimates unchanged. | | 2026-11-03 | minor | M7 records Find Font's shipped replacement-source chooser (#5217); estimates unchanged. | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 9367a069e..cc6012913 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-03 · **Change:** minor (Actions panel steps excluded from playback; 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-03 · **Change:** minor (OpenType Stylistic Sets menu added; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026; Units › East Asian Type acts, with Q and Ha units; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -23,6 +23,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | Date | Change | Summary | |---|---|---| +| 2026-11-03 | minor | The OpenType panel offers the selected font's stylistic sets by custom or numbered name, and allows several to be active. Scores and estimates unchanged. | | 2026-11-03 | minor | Actions panel: an expanded action's steps can be excluded from playback and Batch… by clearing their checkmarks; action and set checkmarks include or exclude all their steps, and exclusions survive restart, duplication and `.vcactions` save/load. Scores and estimates unchanged. | | 2026-11-03 | minor | Correct the Type core missing list and remove duplicate target and Type core entries: Hyphenation and Justification options are complete; the remaining Every-line Composer fidelity limits stay listed. Record the Kashidas paragraph lengths and per-character switch in the feature history (#5404). Scores and estimates unchanged. | | 2026-11-03 | minor | Correct the Type core missing list: Hyphenation and Justification options are complete; the remaining Every-line Composer fidelity limits stay listed. Record the Kashidas paragraph lengths and per-character switch in the feature history (#5404). Scores and estimates unchanged. | @@ -143,6 +144,7 @@ dominate; 3D, graphs and automation are specialist). They sum to 105. Feature de | Strokes, brushes, width profiles | 5 | 88% | Shift-double-click adjoining adjustment is implemented; brush libraries are done (four generated libraries, User Defined, Other Library…, Save Brushes); remaining: Art brush Overlap, Pattern brush auto corners, pen pressure on macOS, tilt/bearing/rotation | 8–15 | estimated; width-point creation and adjoining adjustment regression-tested; dialog layout reviewed; evidence below; finite-arithmetic follow-up regressions and full CI passed (evidence below) | | 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; 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); 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 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; 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) | | 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 | | 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 | @@ -914,6 +916,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur The Character panel's Small Caps, Superscript and Subscript buttons and the panel menu's items of the same names turn small caps and the superscript or subscript position on and off for the selected type, or for the text the Type tool has selected (`text.setFormat` or `text.setRangeStyle` `{smallCaps, position}`), in Document Setup's proportions. Turning Superscript on turns Subscript off, and turning Subscript on turns Superscript off. A button or item shows as on when the first selected character (at a caret, the character before it) has its attribute. The OpenType panel's Stylistic Alternates and Titling Alternates checkboxes turn `salt` and `titl` on and off for the selected type, and its Figure menu lists Default Figure, Tabular Lining, Proportional Lining, Proportional Oldstyle and Tabular Oldstyle; Proportional Lining sets `pnum` and `lnum` and turns `tnum` and `onum` off, Tabular Lining turns `pnum` off and keeps `lnum`, Proportional Oldstyle turns `lnum` off and keeps `pnum`, and Default Figure and Tabular Oldstyle turn `pnum` and `lnum` off. Behavior reference: [Format text with OpenType features](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/format-text-with-opentype-features.html) and [Use swashes, titling alternates, or stylistic alternates](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/use-swashes-titling-alternates-or-stylistic-alternates.html) (public documentation). Tests: `panels::opentype::tests`. The OpenType panel's Position menu lists Default Position, Superscript, Subscript, Numerator and Denominator, as [Format text with OpenType features](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/format-text-with-opentype-features.html) describes; Superscript, Subscript, Numerator and Denominator set `sups`, `subs`, `numr` and `dnom` for the selected type and turn the other three off, and Default Position turns all four off. The menu shows Mixed when the characters it acts on differ, and an entry chosen then applies to all of them. These two rules are VectorCraft's own, as is setting the feature on type whose font lacks the glyphs, which then draws unchanged. The panel's menus and checkboxes change only their own features on each selected character, so characters keep their other OpenType features (`text.setStyle` and `text.setRangeStyle` `{featureChanges}`). Tests: `panels::opentype::tests`, `features::tests::numerators_and_denominators_are_kept_and_shaped`. + The OpenType panel's Stylistic Sets menu offers `ss01`–`ss20` sets present in the selected text's font, using the font's custom labels when available and numbered labels otherwise. Sets can be combined and deselected individually. With mixed fonts, the menu currently shows the first selected style's sets and applies the feature tag to all selected text; a font without that set draws unchanged. The mixed-font rule is a VectorCraft choice pending a behavior decision. Behavior reference: [Add stylistic sets to selected text](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/add-stylistic-sets-to-selected-text.html) and the [OpenType specification](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt). `text.setStyle {stroke, strokeWidth}` sets the character stroke's color and weight of the selected type or of a range of its characters (the weight in document points, as `stroke.set` sets it), in the same undo step as the command's other changes. Tests: `tests::textedit::set_style_sets_the_character_stroke`. - **Paragraph alignment:** the shared Paragraph/Properties alignment row leaves every button unselected when the paragraphs it acts on differ. Auto resolves per paragraph; choosing an alignment uses the existing command. Headless paragraph_mixed_alignment_* regressions exercise mixed → Center → Undo, caret/uniform states, Unicode range boundaries, Auto/direction, split runs and grouped text. These regressions and full integration CI pass. Original captures confirm mixed → Center → Undo in the existing alignment-button layout. The [public Paragraph panel guide](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/paragraph-panel-overview.html) lists the seven controls, but does not specify mixed-state rendering. Other paragraph fields remain outside this change; scores and hours unchanged. For Auto Size area type in columns, the bisection for the frame's height runs on the lines of the one-column layout, and its result is checked with at most two layouts of the columns. When the text wraps around objects or does not fit the one-column layout, or when the check fails, the columns are laid out at every step of the bisection, as before. Typing at the start of a story of 50,000 characters in 2 columns took 1,670 ms a keystroke and now takes 292 ms (release build, `auto_height_in_columns_typing_timing`). diff --git a/docs/type-parity.md b/docs/type-parity.md index 5e506c236..59cafc3db 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-03 · **Change:** minor (Touch Type moves right-to-left characters along their line; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-03 · **Change:** minor (OpenType panel lists font-defined stylistic sets; 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 @@ -29,7 +29,7 @@ against Illustrator's public documentation; **estimated** unless noted. | Character and Paragraph panels, styles with overrides | done | Character-style Auto-leading resets, partial definitions, local leading overrides, override markers, Undo/Redo and native serialization have regression coverage; differing paragraph alignments leave the shared alignment buttons unselected (headless regressions, full integration CI and visual review pass); mixed-value display for other paragraph fields remains missing | | Character size and horizontal scale after object/group scaling | done | #1034: fields and setters compensate the stored affine; point size follows the transformed em-height axis, not the glyph ink bounds. Range edits, keyboard increments, leading and baseline shift use document points; rotation, reflection and shear remain editable. | | Preserve font sizes while scaling a group | missing | Separate enhancement; scaling still scales text with the other artwork. | -| OpenType panel, Glyphs panel, alternates | done | Highlight Alternate Glyphs preference (#394, off by default) paints pink behind OpenType substitutions on screen when on (required shaping such as Arabic joining forms and the lam-alef ligature, including a lam-alef older fonts file under `liga`, is not highlighted; an optional feature that changes the ligature is); stylistic sets ss01–ss20, stylistic and titling alternates, proportional and lining figures, superscript and subscript are character-style features (`text.setStyle {features}`, #1607); the OpenType panel's Position menu sets superscript, subscript, numerator or denominator glyphs (`sups`, `subs`, `numr`, `dnom`). The Glyphs panel highlights the character selected with the Type tool (a range's first character, else the one before the caret) and scrolls it into view as the selection moves (#4908; `panels::glyphs` tests); its Show menu lists Unicode ranges, and Alternates for Current Selection is not there yet | +| OpenType panel, Glyphs panel, alternates | done | Highlight Alternate Glyphs preference (#394, off by default) paints pink behind OpenType substitutions on screen when on (required shaping such as Arabic joining forms and the lam-alef ligature, including a lam-alef older fonts file under `liga`, is not highlighted; an optional feature that changes the ligature is); stylistic sets ss01–ss20, stylistic and titling alternates, proportional and lining figures, superscript and subscript are character-style features (`text.setStyle {features}`, #1607); the OpenType panel's Position menu sets superscript, subscript, numerator or denominator glyphs (`sups`, `subs`, `numr`, `dnom`). The OpenType panel's Stylistic Sets menu lists the selected font's `ss01`–`ss20` features, using a font-supplied name where available and `Set N` otherwise; several sets can be active, and selecting an active one removes it. For a mixed-font selection it currently uses the first selected style's font, a VectorCraft choice pending a behavior decision ([public help](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/add-stylistic-sets-to-selected-text.html), [OpenType feature specification](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt)). The Glyphs panel highlights the character selected with the Type tool (a range's first character, else the one before the caret) and scrolls it into view as the selection moves (#4908; `panels::glyphs` tests); its Show menu lists Unicode ranges, and Alternates for Current Selection is not there yet | | Font menu: samples, live preview, filters, favourites, recent families; font matching by any name; Find Font | done | #3515: recently applied families appear first, once each; missing families are omitted and the existing Number of Recent Fonts setting applies; #339; script filter: Japanese, Latin, Thai; the font scan on Linux also reads the folders fontconfig's configuration lists, such as TeX fonts under `/usr/share/texmf/fonts` (#1548, fixes #1529; not verified on Linux) | | 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 | @@ -77,6 +77,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha | Date | Change | Summary | |---|---|---| +| 2026-11-03 | minor | The OpenType panel lists the selected font's available Stylistic Sets with its custom names or numbered labels; sets can be combined and deselected. Scores and estimates unchanged. | | 2026-11-03 | minor | Character-menu Kashidas controls selected characters separately from the paragraph's Insert Kashidas length. Existing text allows Kashidas by default; scores and estimates unchanged. | | 2026-11-03 | minor | Type on a Path Options has a Preview checkbox: the type follows the fields while it is on, Cancel restores it, OK is one undo step (#5360, as public tutorials of the dialog show); scores and estimates unchanged. | | 2026-11-02 | minor | The wheel over Character, Properties and Control font-family fields changes selected text, one undo step per notch (#5253); scores and estimates unchanged. | From 1858da4106d27ccb010d7ae0bc6ce9610d314b97 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 3 Nov 2026 15:45:49 -0800 Subject: [PATCH 2/4] Type: use selected font version for stylistic sets --- crates/ui-egui/src/panels/opentype.rs | 2 +- docs/target-app-parity.md | 2 +- docs/type-parity.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/panels/opentype.rs b/crates/ui-egui/src/panels/opentype.rs index 443ca87e1..cdff2df1c 100644 --- a/crates/ui-egui/src/panels/opentype.rs +++ b/crates/ui-egui/src/panels/opentype.rs @@ -202,7 +202,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { // The font, not the text, decides which numbered sets can be offered. Each set is an // independent feature, so the menu permits several at once. A font's own label wins over // the numbered fallback. See the public Stylistic Sets help page. - let sets = FontDb::global().face(&st.font_family, &st.font_style).map(|face| face.stylistic_sets()).unwrap_or_default(); + let sets = FontDb::global().style_face(&st).map(|face| face.stylistic_sets()).unwrap_or_default(); ui.add_enabled_ui(!sets.is_empty(), |ui| { ui.menu_button(tl!("Stylistic Sets"), |ui| { for set in &sets { diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index cc6012913..02054ddd1 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -916,7 +916,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur The Character panel's Small Caps, Superscript and Subscript buttons and the panel menu's items of the same names turn small caps and the superscript or subscript position on and off for the selected type, or for the text the Type tool has selected (`text.setFormat` or `text.setRangeStyle` `{smallCaps, position}`), in Document Setup's proportions. Turning Superscript on turns Subscript off, and turning Subscript on turns Superscript off. A button or item shows as on when the first selected character (at a caret, the character before it) has its attribute. The OpenType panel's Stylistic Alternates and Titling Alternates checkboxes turn `salt` and `titl` on and off for the selected type, and its Figure menu lists Default Figure, Tabular Lining, Proportional Lining, Proportional Oldstyle and Tabular Oldstyle; Proportional Lining sets `pnum` and `lnum` and turns `tnum` and `onum` off, Tabular Lining turns `pnum` off and keeps `lnum`, Proportional Oldstyle turns `lnum` off and keeps `pnum`, and Default Figure and Tabular Oldstyle turn `pnum` and `lnum` off. Behavior reference: [Format text with OpenType features](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/format-text-with-opentype-features.html) and [Use swashes, titling alternates, or stylistic alternates](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/use-swashes-titling-alternates-or-stylistic-alternates.html) (public documentation). Tests: `panels::opentype::tests`. The OpenType panel's Position menu lists Default Position, Superscript, Subscript, Numerator and Denominator, as [Format text with OpenType features](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/format-text-with-opentype-features.html) describes; Superscript, Subscript, Numerator and Denominator set `sups`, `subs`, `numr` and `dnom` for the selected type and turn the other three off, and Default Position turns all four off. The menu shows Mixed when the characters it acts on differ, and an entry chosen then applies to all of them. These two rules are VectorCraft's own, as is setting the feature on type whose font lacks the glyphs, which then draws unchanged. The panel's menus and checkboxes change only their own features on each selected character, so characters keep their other OpenType features (`text.setStyle` and `text.setRangeStyle` `{featureChanges}`). Tests: `panels::opentype::tests`, `features::tests::numerators_and_denominators_are_kept_and_shaped`. - The OpenType panel's Stylistic Sets menu offers `ss01`–`ss20` sets present in the selected text's font, using the font's custom labels when available and numbered labels otherwise. Sets can be combined and deselected individually. With mixed fonts, the menu currently shows the first selected style's sets and applies the feature tag to all selected text; a font without that set draws unchanged. The mixed-font rule is a VectorCraft choice pending a behavior decision. Behavior reference: [Add stylistic sets to selected text](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/add-stylistic-sets-to-selected-text.html) and the [OpenType specification](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt). + The OpenType panel's Stylistic Sets menu offers `ss01`–`ss20` sets present in the selected text's font, using the font's custom labels when available and numbered labels otherwise. Sets can be combined and deselected individually. With mixed fonts, the menu shows the first selected character's font's sets and applies the feature tag to all selected text; a font without that set draws unchanged. The mixed-font rule is a VectorCraft design choice. Behavior reference: [Add stylistic sets to selected text](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/add-stylistic-sets-to-selected-text.html) and the [OpenType specification](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt). `text.setStyle {stroke, strokeWidth}` sets the character stroke's color and weight of the selected type or of a range of its characters (the weight in document points, as `stroke.set` sets it), in the same undo step as the command's other changes. Tests: `tests::textedit::set_style_sets_the_character_stroke`. - **Paragraph alignment:** the shared Paragraph/Properties alignment row leaves every button unselected when the paragraphs it acts on differ. Auto resolves per paragraph; choosing an alignment uses the existing command. Headless paragraph_mixed_alignment_* regressions exercise mixed → Center → Undo, caret/uniform states, Unicode range boundaries, Auto/direction, split runs and grouped text. These regressions and full integration CI pass. Original captures confirm mixed → Center → Undo in the existing alignment-button layout. The [public Paragraph panel guide](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/paragraph-panel-overview.html) lists the seven controls, but does not specify mixed-state rendering. Other paragraph fields remain outside this change; scores and hours unchanged. For Auto Size area type in columns, the bisection for the frame's height runs on the lines of the one-column layout, and its result is checked with at most two layouts of the columns. When the text wraps around objects or does not fit the one-column layout, or when the check fails, the columns are laid out at every step of the bisection, as before. Typing at the start of a story of 50,000 characters in 2 columns took 1,670 ms a keystroke and now takes 292 ms (release build, `auto_height_in_columns_typing_timing`). diff --git a/docs/type-parity.md b/docs/type-parity.md index 59cafc3db..12b6ef401 100644 --- a/docs/type-parity.md +++ b/docs/type-parity.md @@ -29,7 +29,7 @@ against Illustrator's public documentation; **estimated** unless noted. | Character and Paragraph panels, styles with overrides | done | Character-style Auto-leading resets, partial definitions, local leading overrides, override markers, Undo/Redo and native serialization have regression coverage; differing paragraph alignments leave the shared alignment buttons unselected (headless regressions, full integration CI and visual review pass); mixed-value display for other paragraph fields remains missing | | Character size and horizontal scale after object/group scaling | done | #1034: fields and setters compensate the stored affine; point size follows the transformed em-height axis, not the glyph ink bounds. Range edits, keyboard increments, leading and baseline shift use document points; rotation, reflection and shear remain editable. | | Preserve font sizes while scaling a group | missing | Separate enhancement; scaling still scales text with the other artwork. | -| OpenType panel, Glyphs panel, alternates | done | Highlight Alternate Glyphs preference (#394, off by default) paints pink behind OpenType substitutions on screen when on (required shaping such as Arabic joining forms and the lam-alef ligature, including a lam-alef older fonts file under `liga`, is not highlighted; an optional feature that changes the ligature is); stylistic sets ss01–ss20, stylistic and titling alternates, proportional and lining figures, superscript and subscript are character-style features (`text.setStyle {features}`, #1607); the OpenType panel's Position menu sets superscript, subscript, numerator or denominator glyphs (`sups`, `subs`, `numr`, `dnom`). The OpenType panel's Stylistic Sets menu lists the selected font's `ss01`–`ss20` features, using a font-supplied name where available and `Set N` otherwise; several sets can be active, and selecting an active one removes it. For a mixed-font selection it currently uses the first selected style's font, a VectorCraft choice pending a behavior decision ([public help](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/add-stylistic-sets-to-selected-text.html), [OpenType feature specification](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt)). The Glyphs panel highlights the character selected with the Type tool (a range's first character, else the one before the caret) and scrolls it into view as the selection moves (#4908; `panels::glyphs` tests); its Show menu lists Unicode ranges, and Alternates for Current Selection is not there yet | +| OpenType panel, Glyphs panel, alternates | done | Highlight Alternate Glyphs preference (#394, off by default) paints pink behind OpenType substitutions on screen when on (required shaping such as Arabic joining forms and the lam-alef ligature, including a lam-alef older fonts file under `liga`, is not highlighted; an optional feature that changes the ligature is); stylistic sets ss01–ss20, stylistic and titling alternates, proportional and lining figures, superscript and subscript are character-style features (`text.setStyle {features}`, #1607); the OpenType panel's Position menu sets superscript, subscript, numerator or denominator glyphs (`sups`, `subs`, `numr`, `dnom`). The OpenType panel's Stylistic Sets menu lists the selected font's `ss01`–`ss20` features, using a font-supplied name where available and `Set N` otherwise; several sets can be active, and selecting an active one removes it. For a mixed-font selection it uses the first selected character's font, a VectorCraft design choice ([public help](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/add-stylistic-sets-to-selected-text.html), [OpenType feature specification](https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt)). The Glyphs panel highlights the character selected with the Type tool (a range's first character, else the one before the caret) and scrolls it into view as the selection moves (#4908; `panels::glyphs` tests); its Show menu lists Unicode ranges, and Alternates for Current Selection is not there yet | | Font menu: samples, live preview, filters, favourites, recent families; font matching by any name; Find Font | done | #3515: recently applied families appear first, once each; missing families are omitted and the existing Number of Recent Fonts setting applies; #339; script filter: Japanese, Latin, Thai; the font scan on Linux also reads the folders fontconfig's configuration lists, such as TeX fonts under `/usr/share/texmf/fonts` (#1548, fixes #1529; not verified on Linux) | | 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 | From 4fa4862613e7598cf59518e4da0339f2e4850f3b Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 3 Nov 2026 21:01:29 -0800 Subject: [PATCH 3/4] Type: translate the numbered stylistic set label StylisticSet.name now holds only a name the font supplies. The OpenType panel shows a set without one as `Set {n}` through tl!, so the label is translated, and each UI catalog has a `Set {n}` row. --- crates/text/src/fontdb/stylistic_sets.rs | 20 ++++++++--------- crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/bs.tsv | 1 + crates/ui-egui/src/i18n/ca.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/da.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ka.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + crates/ui-egui/src/panels/opentype.rs | 28 ++++++++++++++++++++++-- 28 files changed, 61 insertions(+), 13 deletions(-) diff --git a/crates/text/src/fontdb/stylistic_sets.rs b/crates/text/src/fontdb/stylistic_sets.rs index 3bded3d03..8d6578227 100644 --- a/crates/text/src/fontdb/stylistic_sets.rs +++ b/crates/text/src/fontdb/stylistic_sets.rs @@ -8,11 +8,11 @@ use super::FontFace; use super::names::name; /// One `ss01`–`ss20` feature in a face. `number` is the tag's number; `name` is the font's UI -/// label when it supplies one, otherwise `Set 1` through `Set 20`. +/// label when it supplies one, and `None` otherwise. #[derive(Clone, Debug, PartialEq, Eq)] pub struct StylisticSet { pub number: u8, - pub name: String, + pub name: Option, } /// The `ssNN` number in a feature tag, if it is one of the twenty registered sets. @@ -25,8 +25,9 @@ fn number(tag: [u8; 4]) -> Option { } /// Collect registered set records from either layout table. Fonts may carry a set in GSUB, GPOS, -/// or both. A readable custom name takes precedence over the numbered fallback. -fn collect(font: &skrifa::FontRef<'_>, features: FeatureList<'_>, labels: &mut [Option; 20]) { +/// or both. `labels[n - 1]` is `Some` once set `n` is found, holding its readable custom name if +/// any record supplies one. +fn collect(font: &skrifa::FontRef<'_>, features: FeatureList<'_>, labels: &mut [Option>; 20]) { for record in features.feature_records().iter().take(4096) { let Some(n) = number(record.feature_tag().to_be_bytes()) else { continue }; let Ok(feature) = record.feature(features.offset_data()) else { continue }; @@ -38,10 +39,10 @@ fn collect(font: &skrifa::FontRef<'_>, features: FeatureList<'_>, labels: &mut [ _ => None, }); let slot = &mut labels[n - 1]; - if let Some(custom) = custom { + if custom.is_some() { *slot = Some(custom); } else if slot.is_none() { - *slot = Some(format!("Set {n}")); + *slot = Some(None); } } } @@ -51,7 +52,7 @@ impl FontFace { /// skipped, and no more than 4096 feature records from either table are read. pub fn stylistic_sets(&self) -> Vec { let Some(font) = self.skrifa() else { return Vec::new() }; - let mut labels: [Option; 20] = std::array::from_fn(|_| None); + let mut labels: [Option>; 20] = std::array::from_fn(|_| None); if let Ok(gsub) = font.gsub() && let Ok(features) = gsub.feature_list() { @@ -120,9 +121,6 @@ mod tests { let db = super::super::FontDb::with_font_dirs(vec![]); assert_eq!(db.add_font(builder.build()), 1); let face = db.face("Stylistic Test Sans", "Regular").unwrap(); - assert_eq!( - face.stylistic_sets(), - [StylisticSet { number: 1, name: "Curved Capitals".into() }, StylisticSet { number: 3, name: "Set 3".into() }] - ); + assert_eq!(face.stylistic_sets(), [StylisticSet { number: 1, name: Some("Curved Capitals".into()) }, StylisticSet { number: 3, name: None }]); } } diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index b37a00f29..a69bd4f0d 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -3238,6 +3238,7 @@ digits Malayalam Малаялам Swap start and end arrowheads Размяна на началната и крайната стрелка Swap the selected object for the file, keeping its place and transform Замяна на избрания обект с файла, като се запазват мястото и трансформациите му Stylistic Alternates Стилистични алтернативи + Set {n} Набор {n} Swash Декоративен елемент Swatch Conflict Конфликт на образци Swatch Conflicts Конфликти на образци diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 80241a31a..212e28f2b 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -1816,6 +1816,7 @@ digits Malayalam Malajalam Select text to set its OpenType features. Odaberite tekst da postavite njegove OpenType mogućnosti. Standard Ligatures Standardne ligature Stylistic Alternates Stilske alternative + Set {n} Skup {n} Swash Ukrasni potez Tabular Lining Tabelarne brojke Tabular Oldstyle Tabelarne stare brojke diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 95e790242..552b0b8ac 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -6021,6 +6021,7 @@ anchor Points: Punts: Character Color Color del caràcter OpenType Features Funcions OpenType Stylistic Sets Jocs estilístics + Set {n} Joc {n} @msg features lists at most {max} tags features admet com a màxim {max} etiquetes @msg unknown OpenType feature `{t}` funció OpenType desconeguda `{t}` @msg {label} must be a number above 0 and at most {max} {label} ha de ser un nombre superior a 0 i com a màxim {max} diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 358d56060..d96985198 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -6037,6 +6037,7 @@ anchor Points: Body: Character Color Barva znaků OpenType Features Funkce OpenType Stylistic Sets Stylistické sady + Set {n} Sada {n} @msg features lists at most {max} tags features obsahuje nejvýše {max} značek @msg unknown OpenType feature `{t}` neznámá funkce OpenType `{t}` @msg {label} must be a number above 0 and at most {max} {label} musí být číslo větší než 0 a nejvýše {max} diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 8c8b83023..b050dbb63 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1656,6 +1656,7 @@ digits Malayalam Malayalam Select text to set its OpenType features. Markér tekst for at angive dens OpenType-funktioner. Standard Ligatures Standardligaturer Stylistic Alternates Stilistiske alternativer + Set {n} Sæt {n} Swash Sving Tabular Lining Tabulære versalcifre Tabular Oldstyle Tabulære mediævalcifre diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index ffdcec8b7..a26cc0c1a 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5622,6 +5622,7 @@ anchor Points: Punkte: Character Color Zeichenfarbe OpenType Features OpenType-Funktionen Stylistic Sets Stilsätze + Set {n} Satz {n} @msg features lists at most {max} tags features enthält höchstens {max} Tags @msg unknown OpenType feature `{t}` unbekannte OpenType-Funktion `{t}` @msg {label} must be a number above 0 and at most {max} {label} muss eine Zahl über 0 und höchstens {max} sein diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 8fc68ba0f..78bc64257 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -5988,6 +5988,7 @@ anchor Points: Σημεία: Character Color Χρώμα χαρακτήρων OpenType Features Χαρακτηριστικά OpenType Stylistic Sets Στιλιστικά σύνολα + Set {n} Σύνολο {n} @msg features lists at most {max} tags Το features περιέχει έως {max} ετικέτες @msg unknown OpenType feature `{t}` άγνωστο χαρακτηριστικό OpenType `{t}` @msg {label} must be a number above 0 and at most {max} Το {label} πρέπει να είναι αριθμός μεγαλύτερος από 0 και έως {max} diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index ff63ecb21..4c49048ae 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -5979,6 +5979,7 @@ anchor Points: Puntos: Character Color Color de los caracteres OpenType Features Funciones OpenType Stylistic Sets Conjuntos estilísticos + Set {n} Conjunto {n} @msg features lists at most {max} tags features admite como máximo {max} etiquetas @msg unknown OpenType feature `{t}` función OpenType desconocida `{t}` @msg {label} must be a number above 0 and at most {max} {label} debe ser un número mayor que 0 y como máximo {max} diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 76aa0990b..363c388db 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -6067,6 +6067,7 @@ anchor Points: Pisteet: Character Color Merkin väri OpenType Features OpenType-ominaisuudet Stylistic Sets Tyylisarjat + Set {n} Sarja {n} @msg features lists at most {max} tags features sisältää enintään {max} tunnistetta @msg unknown OpenType feature `{t}` tuntematon OpenType-ominaisuus `{t}` @msg {label} must be a number above 0 and at most {max} {label} on oltava luku, joka on yli 0 ja enintään {max} diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 22365ad64..2fc442dab 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -6032,6 +6032,7 @@ anchor Points: Points : Character Color Couleur des caractères OpenType Features Fonctions OpenType Stylistic Sets Jeux stylistiques + Set {n} Jeu {n} @msg features lists at most {max} tags features contient au plus {max} balises @msg unknown OpenType feature `{t}` fonction OpenType inconnue `{t}` @msg {label} must be a number above 0 and at most {max} {label} doit être un nombre supérieur à 0 et au plus égal à {max} diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 9523bca33..f5be6e413 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -6021,6 +6021,7 @@ anchor Points: Pontok: Character Color Karakterszín OpenType Features OpenType-funkciók Stylistic Sets Stíluskészletek + Set {n} {n}. készlet @msg features lists at most {max} tags a features legfeljebb {max} címkét tartalmazhat @msg unknown OpenType feature `{t}` ismeretlen OpenType-funkció: `{t}` @msg {label} must be a number above 0 and at most {max} {label} 0-nál nagyobb és legfeljebb {max} értékű szám kell legyen diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 15969f1da..0c4be65a6 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1656,6 +1656,7 @@ digits Malayalam Malayalam Select text to set its OpenType features. Pilih teks untuk mengatur fitur OpenType-nya. Standard Ligatures Ligatur standar Stylistic Alternates Alternatif gaya + Set {n} Set {n} Swash Swash Tabular Lining Lining tabular Tabular Oldstyle Oldstyle tabular diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 93ac2c95d..12da08947 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5656,6 +5656,7 @@ anchor Points: Punti: Character Color Colore carattere OpenType Features Funzioni OpenType Stylistic Sets Set stilistici + Set {n} Set {n} @msg features lists at most {max} tags features contiene al massimo {max} tag @msg unknown OpenType feature `{t}` funzione OpenType sconosciuta `{t}` @msg {label} must be a number above 0 and at most {max} {label} deve essere un numero maggiore di 0 e al massimo {max} diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 4fa88c824..f68f17fc5 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5721,6 +5721,7 @@ anchor Points: ポイント: Character Color 文字カラー OpenType Features OpenType 機能 Stylistic Sets スタイルセット + Set {n} セット {n} @msg features lists at most {max} tags features に指定できるタグは {max} 個までです @msg unknown OpenType feature `{t}` 不明な OpenType 機能 `{t}` @msg {label} must be a number above 0 and at most {max} {label} には 0 より大きく {max} 以下の数値を指定してください diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index d3556f443..f329a90f8 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -6033,6 +6033,7 @@ anchor Points: წერტილები: Character Color სიმბოლოს ფერი OpenType Features OpenType შესაძლებლობები Stylistic Sets სტილისტური ნაკრებები + Set {n} ნაკრები {n} @msg features lists at most {max} tags features შეიძლება შეიცავდეს არაუმეტეს {max} ტეგს @msg unknown OpenType feature `{t}` უცნობი OpenType შესაძლებლობა `{t}` @msg {label} must be a number above 0 and at most {max} {label} უნდა იყოს 0-ზე მეტი და არაუმეტეს {max}-ისა რიცხვი diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index b6e935e2e..abdcb7cf6 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5617,6 +5617,7 @@ anchor Points: 포인트: Character Color 문자 색상 OpenType Features OpenType 기능 Stylistic Sets 스타일 세트 + Set {n} 세트 {n} @msg features lists at most {max} tags features에는 태그를 최대 {max}개까지 지정할 수 있습니다 @msg unknown OpenType feature `{t}` 알 수 없는 OpenType 기능 `{t}` @msg {label} must be a number above 0 and at most {max} {label}은(는) 0 초과 {max} 이하의 숫자여야 합니다 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 2d2dcccf1..fb2ebeb99 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1670,6 +1670,7 @@ digits Malayalam Malayalam Select text to set its OpenType features. Selecteer tekst om de OpenType-functies in te stellen. Standard Ligatures Standaardligaturen Stylistic Alternates Stilistische alternatieven + Set {n} Set {n} Swash Swash Tabular Lining Tabulaire lining Tabular Oldstyle Tabulaire oldstyle diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index d30c71807..f329a539a 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -5891,6 +5891,7 @@ anchor Points: Punkty: Character Color Kolor znaków OpenType Features Funkcje OpenType Stylistic Sets Zestawy stylistyczne + Set {n} Zestaw {n} @msg features lists at most {max} tags features może zawierać najwyżej {max} znaczników @msg unknown OpenType feature `{t}` nieznana funkcja OpenType `{t}` @msg {label} must be a number above 0 and at most {max} {label} musi być liczbą większą od 0 i nie większą niż {max} diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 9bde60f44..726c16d44 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5953,6 +5953,7 @@ anchor Points: Pontos: Character Color Cor do Caractere OpenType Features Recursos OpenType Stylistic Sets Conjuntos Estilísticos + Set {n} Conjunto {n} @msg features lists at most {max} tags features aceita no máximo {max} marcas @msg unknown OpenType feature `{t}` recurso OpenType desconhecido `{t}` @msg {label} must be a number above 0 and at most {max} {label} deve ser um número maior que 0 e no máximo {max} diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 0e55857a6..ba7a94689 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -6014,6 +6014,7 @@ anchor Points: Точки: Character Color Цвет символов OpenType Features Функции OpenType Stylistic Sets Стилистические наборы + Set {n} Набор {n} @msg features lists at most {max} tags features содержит не более {max} тегов @msg unknown OpenType feature `{t}` неизвестная функция OpenType `{t}` @msg {label} must be a number above 0 and at most {max} {label} должно быть числом больше 0 и не более {max} diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 49c81d06b..81942ced5 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -6089,6 +6089,7 @@ shape Open Öppen Character Color Teckenfärg OpenType Features OpenType-funktioner Stylistic Sets Stilistiska uppsättningar + Set {n} Uppsättning {n} @msg features lists at most {max} tags features får innehålla högst {max} taggar @msg unknown OpenType feature `{t}` okänd OpenType-funktion `{t}` @msg {label} must be a number above 0 and at most {max} {label} måste vara ett tal över 0 och högst {max} diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 3dfae8af3..af1a87f89 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -5994,6 +5994,7 @@ anchor Points: Noktalar: Character Color Karakter Rengi OpenType Features OpenType Özellikleri Stylistic Sets Stilistik Setler + Set {n} Set {n} @msg features lists at most {max} tags features en fazla {max} etiket içerebilir @msg unknown OpenType feature `{t}` bilinmeyen OpenType özelliği `{t}` @msg {label} must be a number above 0 and at most {max} {label} 0'ın üzerinde ve en fazla {max} olan bir sayı olmalıdır diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 5d37cd09c..07e5d8db9 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5636,6 +5636,7 @@ anchor Points: Точки: Character Color Колір символів OpenType Features Функції OpenType Stylistic Sets Стилістичні набори + Set {n} Набір {n} @msg features lists at most {max} tags features містить не більше {max} тегів @msg unknown OpenType feature `{t}` невідома функція OpenType `{t}` @msg {label} must be a number above 0 and at most {max} {label} має бути числом більше 0 і не більше {max} diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 12fe93592..e0844fb25 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -5637,6 +5637,7 @@ anchor Points: Điểm: Character Color Màu ký tự OpenType Features Tính năng OpenType Stylistic Sets Bộ phong cách + Set {n} Bộ {n} @msg features lists at most {max} tags features chứa tối đa {max} thẻ @msg unknown OpenType feature `{t}` tính năng OpenType không xác định `{t}` @msg {label} must be a number above 0 and at most {max} {label} phải là một số lớn hơn 0 và tối đa {max} diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index d9e5f84b4..1ea470392 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -5878,6 +5878,7 @@ anchor Points: 点: Character Color 字符颜色 OpenType Features OpenType 特性 Stylistic Sets 风格集 + Set {n} 风格集 {n} @msg features lists at most {max} tags features 最多包含 {max} 个标签 @msg unknown OpenType feature `{t}` 未知的 OpenType 特性 `{t}` @msg {label} must be a number above 0 and at most {max} {label} 必须是大于 0 且不超过 {max} 的数字 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index b387b0a81..8d7d0ff58 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5674,6 +5674,7 @@ anchor Points: 點: Character Color 字元顏色 OpenType Features OpenType 功能 Stylistic Sets 風格集 + Set {n} 風格集 {n} @msg features lists at most {max} tags features 最多包含 {max} 個標籤 @msg unknown OpenType feature `{t}` 未知的 OpenType 功能 `{t}` @msg {label} must be a number above 0 and at most {max} {label} 必須是大於 0 且不超過 {max} 的數字 diff --git a/crates/ui-egui/src/panels/opentype.rs b/crates/ui-egui/src/panels/opentype.rs index cdff2df1c..ca5a7dfb0 100644 --- a/crates/ui-egui/src/panels/opentype.rs +++ b/crates/ui-egui/src/panels/opentype.rs @@ -4,7 +4,7 @@ use egui::Ui; use serde_json::json; -use vectorcraft_text::{EXTRA_FEATURES, FontDb, OtFeatures}; +use vectorcraft_text::{EXTRA_FEATURES, FontDb, OtFeatures, StylisticSet}; use super::character::{shared, text_editing, text_style}; use crate::VectorcraftApp; @@ -208,7 +208,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { for set in &sets { let flag = Flag::Extra(EXTRA_FEATURES[usize::from(set.number - 1)]); let on = flag.get(&next); - if widgets::check(ui, &set.name, on, true) { + if widgets::check(ui, &set_label(set), on, true) { change = Some(Change::Toggle(flag, !on)); } } @@ -222,6 +222,15 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { } } +/// A Stylistic Sets menu entry: the font's own name for the set, or `Set 1` through `Set 20` in +/// the UI language. +fn set_label(set: &StylisticSet) -> String { + match &set.name { + Some(name) => name.clone(), + None => crate::i18n::fmt(tl!("Set {n}"), &[("n", &set.number.to_string())]), + } +} + /// The panel's checkbox rows, in the order they are drawn. fn rows() -> [Toggle; 9] { [ @@ -311,6 +320,21 @@ mod tests { assert_eq!(text_style(&app).unwrap().0.features, ["ss03"]); } + /// A set the font names keeps the font's name; a set without one gets the numbered label in + /// the UI language. + #[test] + fn stylistic_set_labels_follow_the_ui_language() { + let named = StylisticSet { number: 1, name: Some("Curved Capitals".into()) }; + let numbered = StylisticSet { number: 3, name: None }; + assert_eq!(set_label(&numbered), "Set 3"); + let Some(de) = crate::i18n::Lang::from_code("de") else { panic!("no German catalog") }; + crate::i18n::set_current(de); + let (named_de, numbered_de) = (set_label(&named), set_label(&numbered)); + crate::i18n::set_current(crate::i18n::Lang::EN); + assert_eq!(named_de, "Curved Capitals"); + assert_eq!(numbered_de, "Satz 3"); + } + /// The Figure menu lists its five entries in order; Proportional Lining sets `pnum` and /// `lnum` and clears `tnum` and `onum`, Tabular Lining clears `pnum`, Proportional Oldstyle /// clears `lnum`, and Default Figure and Tabular Oldstyle clear `pnum` and `lnum`. From 3bd1514acb7c30f70ffac216b3b9233d0d3787cf Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Tue, 3 Nov 2026 22:41:36 -0800 Subject: [PATCH 4/4] Type: offer stylistic sets only for an installed font, by their own names A font that is not installed offered the sets of the face drawn in its place; the menu now offers none. Set names the font supplies are shown as they are, without UI translation. --- crates/ui-egui/src/panels/opentype.rs | 32 +++++++++++++++++++++++---- docs/target-app-parity.md | 5 ++--- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/crates/ui-egui/src/panels/opentype.rs b/crates/ui-egui/src/panels/opentype.rs index ca5a7dfb0..91b804365 100644 --- a/crates/ui-egui/src/panels/opentype.rs +++ b/crates/ui-egui/src/panels/opentype.rs @@ -202,13 +202,13 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { // The font, not the text, decides which numbered sets can be offered. Each set is an // independent feature, so the menu permits several at once. A font's own label wins over // the numbered fallback. See the public Stylistic Sets help page. - let sets = FontDb::global().style_face(&st).map(|face| face.stylistic_sets()).unwrap_or_default(); + let sets = offered_sets(&st); ui.add_enabled_ui(!sets.is_empty(), |ui| { ui.menu_button(tl!("Stylistic Sets"), |ui| { for set in &sets { let flag = Flag::Extra(EXTRA_FEATURES[usize::from(set.number - 1)]); let on = flag.get(&next); - if widgets::check(ui, &set_label(set), on, true) { + if widgets::check_name(ui, &set_label(set), on) { change = Some(Change::Toggle(flag, !on)); } } @@ -222,8 +222,19 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { } } -/// A Stylistic Sets menu entry: the font's own name for the set, or `Set 1` through `Set 20` in -/// the UI language. +/// The stylistic sets of the font `st` names. A font that is not available offers none, rather than +/// the sets of the face drawn in its place. +fn offered_sets(st: &vectorcraft_doc::CharStyle) -> Vec { + let db = FontDb::global(); + let (family, _) = db.canonical(&st.font_family, &st.font_style); + if !db.has_family(&family) { + return Vec::new(); + } + db.style_face(st).map(|face| face.stylistic_sets()).unwrap_or_default() +} + +/// A Stylistic Sets menu entry: the font's own name for the set, shown as the font gives it, or +/// `Set 1` through `Set 20` in the UI language. fn set_label(set: &StylisticSet) -> String { match &set.name { Some(name) => name.clone(), @@ -335,6 +346,19 @@ mod tests { assert_eq!(numbered_de, "Satz 3"); } + /// A font that is not available offers no sets, even though the face drawn in its place has + /// some. + #[test] + fn a_missing_font_offers_no_stylistic_sets() { + let fallback = vectorcraft_doc::CharStyle { font_family: vectorcraft_text::FALLBACK_FAMILY.into(), ..Default::default() }; + // Builds without the bundled fonts have no fallback face to draw in a missing font's place. + if FontDb::global().has_family(vectorcraft_text::FALLBACK_FAMILY) { + assert!(!offered_sets(&fallback).is_empty(), "the fallback face lists its sets"); + } + let missing = vectorcraft_doc::CharStyle { font_family: "No Such Family Zq".into(), ..fallback }; + assert!(offered_sets(&missing).is_empty()); + } + /// The Figure menu lists its five entries in order; Proportional Lining sets `pnum` and /// `lnum` and clears `tnum` and `onum`, Tabular Lining clears `pnum`, Proportional Oldstyle /// clears `lnum`, and Default Figure and Tabular Oldstyle clear `pnum` and `lnum`. diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 02054ddd1..a792ebe27 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-03 · **Change:** minor (OpenType Stylistic Sets menu added; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026; Units › East Asian Type acts, with Q and Ha units; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-03 · **Change:** minor (OpenType Stylistic Sets menu added; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -144,8 +144,7 @@ dominate; 3D, graphs and automation are specialist). They sum to 105. Feature de | Strokes, brushes, width profiles | 5 | 88% | Shift-double-click adjoining adjustment is implemented; brush libraries are done (four generated libraries, User Defined, Other Library…, Save Brushes); remaining: Art brush Overlap, Pattern brush auto corners, pen pressure on macOS, tilt/bearing/rotation | 8–15 | estimated; width-point creation and adjoining adjustment regression-tested; dialog layout reviewed; evidence below; finite-arithmetic follow-up regressions and full CI passed (evidence below) | | 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; 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); 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 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; 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) | +| 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; 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 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 | | 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 |