diff --git a/crates/ui-egui/src/panels/character.rs b/crates/ui-egui/src/panels/character.rs index 2ad678bf62..d1712c42d7 100644 --- a/crates/ui-egui/src/panels/character.rs +++ b/crates/ui-egui/src/panels/character.rs @@ -940,18 +940,89 @@ const FONT_HEIGHT_REFERENCES: [(vectorcraft_doc::FontHeightReference, &str); 4] (vectorcraft_doc::FontHeightReference::IcfBox, "icfBox"), ]; +/// The size of the icon before each name in the Set Font Height Reference dropdown. +const FONT_HEIGHT_ICON: f32 = 16.0; + /// The Set Font Height Reference dropdown, `width` wide: the height of the font that the Font Size -/// measures, blank where the characters the panel acts on differ. +/// measures, blank where the characters the panel acts on differ. Each reference shows an icon of +/// the height it measures before its name ([`paint_font_height_icon`], #6304). fn font_height_reference(app: &mut VectorcraftApp, ui: &mut Ui, width: f32) { // The labels of [`FONT_HEIGHT_REFERENCES`], in its order. let labels = [tl!("Em Box"), tl!("Cap Height"), tl!("x-Height"), tl!("ICF Box")]; let current = shared(app, |s| s.font_height_reference); - let shown = FONT_HEIGHT_REFERENCES.iter().zip(labels).find(|(r, _)| Some(r.0) == current).map_or("", |(_, l)| l); - if let Some((_, key)) = widgets::dropdown_names(ui, "ch-font-height", shown, &labels, width).and_then(|i| FONT_HEIGHT_REFERENCES.get(i)) { + let shown = FONT_HEIGHT_REFERENCES.iter().position(|(r, _)| Some(*r) == current); + let mark = Tokens::get(ui.ctx()).accent; + let icon = |ui: &Ui, rect: egui::Rect, i: usize, ink: egui::Color32| { + if let Some((r, _)) = FONT_HEIGHT_REFERENCES.get(i) { + paint_font_height_icon(ui.painter(), rect, *r, ink, mark); + } + }; + let picked = widgets::dropdown_icon_names(ui, "ch-font-height", shown, &labels, width, FONT_HEIGHT_ICON, icon); + if let Some((_, key)) = picked.and_then(|i| FONT_HEIGHT_REFERENCES.get(i)) { format(app, json!({"fontHeightReference": key})); } } +/// Paints the icon of font height reference `r` into the square `rect` (#6304): a letter drawn in +/// `ink` on a baseline, and in `mark` the height the reference measures. Em Box frames the whole +/// square around an "A"; Cap Height puts a bar from the baseline to the top of a capital "A" +/// beside it; x-Height a bar from the baseline to the top of a lowercase "x"; ICF Box frames a +/// square inset in the faint em square around an ideograph-like cross, as the ideographic +/// character face sits inside the em box. The drawings are VectorCraft's own. +fn paint_font_height_icon( + painter: &egui::Painter, + rect: egui::Rect, + r: vectorcraft_doc::FontHeightReference, + ink: egui::Color32, + mark: egui::Color32, +) { + use vectorcraft_doc::FontHeightReference as R; + // A point at (x, y) in the unit square mapped onto `rect`. + let p = |x: f32, y: f32| rect.lerp_inside(vec2(x, y)); + let glyph = egui::Stroke::new(1.3, ink); + let measure = egui::Stroke::new(1.2, mark); + let line = |a: (f32, f32), b: (f32, f32), stroke: egui::Stroke| painter.line_segment([p(a.0, a.1), p(b.0, b.1)], stroke); + let frame = |min: f32, max: f32, stroke: egui::Stroke| { + painter.rect_stroke(egui::Rect::from_min_max(p(min, min), p(max, max)), 0.0, stroke, egui::StrokeKind::Middle); + }; + // A capital A from `left` to `right`, its apex at `top` and its feet on `base`. + let capital = |left: f32, right: f32, top: f32, base: f32| { + let mid = (left + right) / 2.0; + painter.add(egui::Shape::line(vec![p(left, base), p(mid, top), p(right, base)], glyph)); + let bar = top + (base - top) * 0.62; + let inset = (right - left) * 0.5 * 0.62; + line((mid - inset, bar), (mid + inset, bar), glyph); + }; + // A vertical measure from `top` to `base` at `x`, with ticks at its ends. + let height_bar = |x: f32, top: f32, base: f32| { + line((x, top), (x, base), measure); + line((x - 0.1, top), (x + 0.1, top), measure); + line((x - 0.1, base), (x + 0.1, base), measure); + }; + let (base, cap_top, x_top) = (0.86, 0.16, 0.46); + match r { + R::EmBox => { + frame(0.06, 0.94, measure); + capital(0.26, 0.74, 0.26, 0.76); + } + R::CapHeight => { + capital(0.06, 0.6, cap_top, base); + height_bar(0.82, cap_top, base); + } + R::XHeight => { + line((0.1, x_top), (0.54, base), glyph); + line((0.54, x_top), (0.1, base), glyph); + height_bar(0.82, x_top, base); + } + R::IcfBox => { + frame(0.06, 0.94, egui::Stroke::new(1.0, ink.gamma_multiply(0.4))); + frame(0.2, 0.8, measure); + line((0.33, 0.5), (0.67, 0.5), glyph); + line((0.5, 0.31), (0.5, 0.69), glyph); + } + } +} + /// The font family menu and the font style dropdown of style `s` (Character and Properties panels, /// Control bar), with their `ids` and `widths`. The mouse wheel over the closed family field steps /// the text through the families. diff --git a/crates/ui-egui/src/panels/character/tests.rs b/crates/ui-egui/src/panels/character/tests.rs index a90286e2fb..4e403f0903 100644 --- a/crates/ui-egui/src/panels/character/tests.rs +++ b/crates/ui-egui/src/panels/character/tests.rs @@ -822,6 +822,70 @@ fn show_font_height_options_shows_the_font_height_reference() { assert!(item(&texts(&ctx, &mut app, show), "Cap Height").is_none()); } +/// The Set Font Height Reference dropdown shows an icon before each reference's name, in the +/// closed field and in the open list, and each reference's icon is its own (#6304). +#[test] +fn font_height_references_show_their_icons() { + use super::super::tests_appearance::click; + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 300, "height": 200})).unwrap(); + let id = app.run("text.create", json!({"x": 20, "y": 50, "text": "Hx", "size": 12})).unwrap()["id"].clone(); + app.run("select.set", json!({"ids": [id]})).unwrap(); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + set_pstate(&ctx, FONT_HEIGHT_OPTIONS, true); + // Every text drawn with its rect, and the rects of every other shape drawn. + let frame = |ctx: &egui::Context, app: &mut VectorcraftApp| { + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(app, ui)); + out.textures_delta.clear(); + fn collect(s: &egui::Shape, texts: &mut Vec<(String, egui::Rect)>, marks: &mut Vec) { + match s { + egui::Shape::Text(t) => texts.push((t.galley.text().to_string(), t.visual_bounding_rect())), + egui::Shape::Vec(v) => v.iter().for_each(|s| collect(s, texts, marks)), + egui::Shape::Noop => {} + s => marks.push(s.visual_bounding_rect()), + } + } + let (mut texts, mut marks) = (vec![], vec![]); + for c in &out.shapes { + collect(&c.shape, &mut texts, &mut marks); + } + (texts, marks) + }; + // The shapes in the icon's place before the text `rect`, relative to that place and rounded. + let icon = |marks: &[egui::Rect], rect: egui::Rect| { + let place = egui::Rect::from_min_max( + egui::pos2(rect.min.x - FONT_HEIGHT_ICON - 8.0, rect.center().y - 10.0), + egui::pos2(rect.min.x, rect.center().y + 10.0), + ); + let mut found: Vec<[i32; 4]> = marks + .iter() + .filter(|m| m.is_finite() && place.contains_rect(**m)) + .map(|m| [m.min.x - place.min.x, m.min.y - place.min.y, m.max.x - place.min.x, m.max.y - place.min.y].map(|v| v.round() as i32)) + .collect(); + found.sort_unstable(); + found + }; + frame(&ctx, &mut app); + let (texts, marks) = frame(&ctx, &mut app); + let field = texts.iter().find(|(t, _)| t == "Em Box").map(|(_, r)| *r).expect("the closed field shows Em Box"); + assert!(icon(&marks, field).len() >= 2, "no icon before Em Box in the closed field: {:?}", icon(&marks, field)); + click(&ctx, &mut app, field.center(), show); + frame(&ctx, &mut app); + let (texts, marks) = frame(&ctx, &mut app); + let mut icons = vec![]; + for name in ["Em Box", "Cap Height", "x-Height", "ICF Box"] { + // The list's row is the last text of that name drawn (the closed field is drawn first). + let row = texts.iter().rev().find(|(t, _)| t == name).map(|(_, r)| *r).unwrap_or_else(|| panic!("the open list shows {name}: {texts:?}")); + let found = icon(&marks, row); + assert!(found.len() >= 2, "no icon before {name} in the open list: {found:?}"); + icons.push(found); + } + for (i, a) in icons.iter().enumerate() { + assert!(icons.iter().skip(i + 1).all(|b| a != b), "two references share an icon: {icons:?}"); + } +} + /// Tate-chu-yoko is in the panel menu with the East Asian options, and choosing it marks the /// selected characters. #[test] diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 826e7e02f6..52849bc2bc 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -47,8 +47,8 @@ pub use drag::{ use dropdowns::combo_button_id; pub(crate) use dropdowns::{combo, combo_menu, combo_menu_field, combo_named, field_focused_in, popover_is_open}; pub use dropdowns::{ - dropdown, dropdown_named, dropdown_names, dropdown_names_named, dropdown_with, dropdown_with_named, list_scroll_style, menu_room, menu_scroll, - panel_scroll, popover, set_menu_floor, strip_scroll, + dropdown, dropdown_icon_names, dropdown_named, dropdown_names, dropdown_names_named, dropdown_with, dropdown_with_named, list_scroll_style, + menu_room, menu_scroll, panel_scroll, popover, set_menu_floor, strip_scroll, }; #[cfg(test)] pub(crate) use dropdowns::{menu_floor, menu_room_either}; diff --git a/crates/ui-egui/src/widgets/dropdowns.rs b/crates/ui-egui/src/widgets/dropdowns.rs index 44903d6613..c8fba54aed 100644 --- a/crates/ui-egui/src/widgets/dropdowns.rs +++ b/crates/ui-egui/src/widgets/dropdowns.rs @@ -1,3 +1,5 @@ +use egui::IntoAtoms as _; + use super::drag::PanelDrag; use super::labels::{PreviousLabel, label_field, name_field, take_previous_label}; use super::text::take_armed_focus; @@ -33,6 +35,37 @@ pub fn dropdown_names_named(ui: &mut Ui, id: impl std::hash::Hash, name: Option< }) } +/// [`dropdown_names`] whose options each show an icon before their name, in the open list and in +/// the closed field, so that a name that says little on its own is shown with a picture of what it +/// means (#6304). `icon(ui, rect, index, ink)` paints option `index`'s icon into `rect`, an +/// `icon_size` square, with `ink`, the colour the row's name is drawn in. `current` is the option +/// shown; `None` shows a blank field (characters that differ). +pub fn dropdown_icon_names( + ui: &mut Ui, + id: impl std::hash::Hash, + current: Option, + options: &[&str], + width: f32, + icon_size: f32, + icon: impl Fn(&Ui, Rect, usize, egui::Color32), +) -> Option { + let icon = &icon; + let atom = move |i: usize| egui::Atom::paint(Vec2::splat(icon_size), move |ui, a| icon(ui, a.rect, i, a.fallback_text_color)); + let shown = match current.and_then(|i| options.get(i).map(|name| (i, *name))) { + Some((i, name)) => (atom(i), egui::RichText::new(name).size(12.0)).into_atoms(), + None => egui::RichText::new("").size(12.0).into_atoms(), + }; + combo_atoms_named(ui, id, None, shown, width, |ui| { + let mut chosen = None; + for (i, o) in options.iter().enumerate() { + if ui.add(egui::Button::selectable(current == Some(i), (atom(i), *o))).clicked() { + chosen = Some(i); + } + } + chosen + }) +} + /// [`dropdown`] whose options can be disabled (`enabled(index)`: greyed and not choosable). pub fn dropdown_with( ui: &mut Ui, @@ -88,6 +121,18 @@ pub(crate) fn combo_named( current: &str, width: f32, list: impl FnOnce(&mut Ui) -> Option, +) -> Option { + combo_atoms_named(ui, id, name, egui::RichText::new(current).size(12.0), width, list) +} + +/// [`combo_named`] whose closed field shows the atoms `current` (an icon and a name, say). +fn combo_atoms_named<'a, R>( + ui: &mut Ui, + id: impl std::hash::Hash, + name: Option<&str>, + current: impl egui::IntoAtoms<'a>, + width: f32, + list: impl FnOnce(&mut Ui) -> Option, ) -> Option { let t = Tokens::get(ui.ctx()); let label = take_previous_label(ui); @@ -99,7 +144,7 @@ pub(crate) fn combo_named( let button_id = combo_button_id(ui, &id); let enter_for_combo = ui.memory(|m| m.has_focus(button_id)) || craft_ui::ComboBox::is_open(ui.ctx(), button_id); let response = craft_ui::ComboBox::from_id_salt(HashOnly(&id)) - .selected_text(egui::RichText::new(current).size(12.0)) + .selected_text(current) .width(width - 4.0) .height(ui.spacing().combo_height) .show_ui(ui, list); diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 086d4cfc0d..7904896324 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-13 · **Change:** minor (PDF import: glyphs in a span with an empty ActualText come in as outlines, #6971; scores and estimates unchanged; previously: library panels' lists fill a taller panel, #2992; scores and estimates unchanged; previously: Swatches list fills a taller panel, #1638; scores and estimates unchanged; previously minor: Object › Rasterize with the Grayscale or Bitmap colour model makes a grayscale image, #6969; scores and estimates unchanged; previously minor: `.ai` and EPS import: type in an envelope with Distort Appearance is also tried mapped from the box of what it paints, miter tips and moved strokes included, after its glyphs grown by half its strokes' weight (#6824; #6617: Swatches panel menu: Show Recent Colors; scores unchanged; previously minor: icon-strip flyouts also resize from the top edge and the canvas-side top corner, keeping the moved top in workspaces (storytold/craft-libs#1094); scores and estimates unchanged; previously: #6865: explicitly requested oversized editing artboard; macOS: the canvas cursor stays after the pointer moves, #6362; scores and estimates unchanged; previously minor: Type tool: kerning at a caret at the start of a line moves the character after it, #5111; scores and estimates unchanged; previously minor: Rulers: the zero point dragged out of the rulers' corner snaps as a drawn point does; previously minor: Live Paint Bucket fills faces crossed in one drag (#6373); previously: #6404: transform-aware Eyedropper character copying; scores and estimates unchanged; previously: Envelopes: with Distort Appearance, type's strokes paint along the bent glyph outlines at their own weight, as users' `.ai` files draw them, which changes how VectorCraft's own envelopes of stroked type draw (#6644; scores and estimates unchanged); previously minor: G5: Energy Beams brush library; bundled content ~44% → ~46%; previously minor: G5: Skill Icons symbol library; bundled content ~42% → ~44%; previously minor: `.ai` and EPS import: an envelope saved in Edit Contents whose mesh lies away from its drawn art by a move opens live on that art (#6570; scores and estimates unchanged); previously minor: ruler double-click adds a guide at the pointer, #5239; G5: Status Effects graphic style library; bundled content ~40% → ~42%; MCP symbol libraries list UI Frames and Map Markers; previously minor: G5: Inventory Icons built-in symbol library; bundled content ~38% → ~40%; previously minor: `.ai` and EPS import: stroked type in an envelope maps from its glyphs grown by half its stroke (#3179; scores and estimates unchanged); previously minor: `.ai` and EPS import: warps of type open as mesh envelopes on their saved grid where VectorCraft's warp doesn't draw as their drawn art (#3179; scores and estimates unchanged); G5: Game Trails brush library; bundled content ~36% → ~38%; previously minor: Edit › Edit Custom Dictionary…: its dialog adds, deletes and changes the custom dictionary's words, #4936; previously minor: `.ai` and EPS import: envelopes of type open live where they draw as their drawn art and the layers with them draw as the page (#3179; scores and estimates unchanged); G5: Game HUD graphic style library; bundled content ~34% → ~36%; previously minor: Line Arrow arrowhead (#5421; scores and estimates unchanged); G5: Map Markers built-in symbol library; bundled content ~32% → ~34%; previously minor: right-click on type: Spelling › Auto Spell Check, with its check mark, #4936; previously minor: right-click on a word Auto Spell Check underlined: its suggestions, Ignore All and Add to Dictionary, #4936; previously minor: Canvas: a 3D effect changed on type or a group redraws where its solid was; Fit to Artwork Bounds and per-artboard SVG export build each solid once for all artboards; previously minor: Effect Gallery: a thumbnail for each filter and a preview area inside the dialog, on an original drawing; Raster effects 52% → 55%, hours 1–2 → none; headline numbers unchanged; Auto Spell Check: `edit.autoSpellCheck` underlines the unknown words, #4936; previously minor: PDF editable import preserves authored ActualText once, including empty/nested spans and authored ordering (#6165); cross-line/style layout remains approximate; scores and estimates unchanged; PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; G5: UI Frames built-in symbol library with default 9-slice; bundled content ~32%; previously minor: G5: dynamic symbol part on-canvas opacity/blend/gradient/pattern; strokes remaining no longer lists Overlap/auto corners; symbols ~88%; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; previously minor: harakat paint fallback clarified; previously minor: current Guides feature-area rows consolidated using existing ruler evidence; scores and estimates unchanged; previously minor: Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; previously minor: G5 docs: Art brush Overlap and Pattern brush auto corners removed from strokes remaining; symbols remaining lists dynamic on-canvas beyond solid and guides outside the art; previously minor: custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: EPS import: embedded fonts (composite, Type 42, CID-keyed, Type 1, CFF FontSet) draw their glyphs; spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; `.ai`/EPS type keeps its tab stops, and right-aligned lines with tabs lay out as the file's page, #6147; `.ai`/EPS type from the text document lays its lines out closer to the file's page, within limits, #6137; `.ai` and EPS import: older Art brush objects read; numeric fields step on wheel hover without taking keyboard focus, and a panel wheel turn cannot seed a canvas zoom glide, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Tools stays beside the canvas with left-side panels; new gradients follow document color mode; Selection QoL: marquee modes and visible feedback, #5313; SVG CSS classes survive export and repeated import in all four styling modes (#2480); current-main integration retains event attributes and scripts; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026; Units › East Asian Type acts, with Q and Ha units; scores unchanged) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-13 · **Change:** minor (Character panel: the Set Font Height Reference dropdown shows an icon before each reference, #6304; scores and estimates unchanged; previously: PDF import: glyphs in a span with an empty ActualText come in as outlines, #6971; scores and estimates unchanged; previously: library panels' lists fill a taller panel, #2992; scores and estimates unchanged; previously: Swatches list fills a taller panel, #1638; scores and estimates unchanged; previously minor: Object › Rasterize with the Grayscale or Bitmap colour model makes a grayscale image, #6969; scores and estimates unchanged; previously minor: `.ai` and EPS import: type in an envelope with Distort Appearance is also tried mapped from the box of what it paints, miter tips and moved strokes included, after its glyphs grown by half its strokes' weight (#6824; #6617: Swatches panel menu: Show Recent Colors; scores unchanged; previously minor: icon-strip flyouts also resize from the top edge and the canvas-side top corner, keeping the moved top in workspaces (storytold/craft-libs#1094); scores and estimates unchanged; previously: #6865: explicitly requested oversized editing artboard; macOS: the canvas cursor stays after the pointer moves, #6362; scores and estimates unchanged; previously minor: Type tool: kerning at a caret at the start of a line moves the character after it, #5111; scores and estimates unchanged; previously minor: Rulers: the zero point dragged out of the rulers' corner snaps as a drawn point does; previously minor: Live Paint Bucket fills faces crossed in one drag (#6373); previously: #6404: transform-aware Eyedropper character copying; scores and estimates unchanged; previously: Envelopes: with Distort Appearance, type's strokes paint along the bent glyph outlines at their own weight, as users' `.ai` files draw them, which changes how VectorCraft's own envelopes of stroked type draw (#6644; scores and estimates unchanged); previously minor: G5: Energy Beams brush library; bundled content ~44% → ~46%; previously minor: G5: Skill Icons symbol library; bundled content ~42% → ~44%; previously minor: `.ai` and EPS import: an envelope saved in Edit Contents whose mesh lies away from its drawn art by a move opens live on that art (#6570; scores and estimates unchanged); previously minor: ruler double-click adds a guide at the pointer, #5239; G5: Status Effects graphic style library; bundled content ~40% → ~42%; MCP symbol libraries list UI Frames and Map Markers; previously minor: G5: Inventory Icons built-in symbol library; bundled content ~38% → ~40%; previously minor: `.ai` and EPS import: stroked type in an envelope maps from its glyphs grown by half its stroke (#3179; scores and estimates unchanged); previously minor: `.ai` and EPS import: warps of type open as mesh envelopes on their saved grid where VectorCraft's warp doesn't draw as their drawn art (#3179; scores and estimates unchanged); G5: Game Trails brush library; bundled content ~36% → ~38%; previously minor: Edit › Edit Custom Dictionary…: its dialog adds, deletes and changes the custom dictionary's words, #4936; previously minor: `.ai` and EPS import: envelopes of type open live where they draw as their drawn art and the layers with them draw as the page (#3179; scores and estimates unchanged); G5: Game HUD graphic style library; bundled content ~34% → ~36%; previously minor: Line Arrow arrowhead (#5421; scores and estimates unchanged); G5: Map Markers built-in symbol library; bundled content ~32% → ~34%; previously minor: right-click on type: Spelling › Auto Spell Check, with its check mark, #4936; previously minor: right-click on a word Auto Spell Check underlined: its suggestions, Ignore All and Add to Dictionary, #4936; previously minor: Canvas: a 3D effect changed on type or a group redraws where its solid was; Fit to Artwork Bounds and per-artboard SVG export build each solid once for all artboards; previously minor: Effect Gallery: a thumbnail for each filter and a preview area inside the dialog, on an original drawing; Raster effects 52% → 55%, hours 1–2 → none; headline numbers unchanged; Auto Spell Check: `edit.autoSpellCheck` underlines the unknown words, #4936; previously minor: PDF editable import preserves authored ActualText once, including empty/nested spans and authored ordering (#6165); cross-line/style layout remains approximate; scores and estimates unchanged; PDF marked-text export (#6309): compatible glyphs stay on one layout line with per-glyph offsets; scores and estimates unchanged; G5: UI Frames built-in symbol library with default 9-slice; bundled content ~32%; previously minor: G5: dynamic symbol part on-canvas opacity/blend/gradient/pattern; strokes remaining no longer lists Overlap/auto corners; symbols ~88%; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; previously minor: harakat paint fallback clarified; previously minor: current Guides feature-area rows consolidated using existing ruler evidence; scores and estimates unchanged; previously minor: Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; previously minor: G5 docs: Art brush Overlap and Pattern brush auto corners removed from strokes remaining; symbols remaining lists dynamic on-canvas beyond solid and guides outside the art; previously minor: custom dictionary and Ignore All: `text.spelling.addWord`, `removeWord`, `changeWord`, `dictionary` and `ignoreAll`, #4936; previously minor: EPS import: embedded fonts (composite, Type 42, CID-keyed, Type 1, CFF FontSet) draw their glyphs; spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; `.ai`/EPS type keeps its tab stops, and right-aligned lines with tabs lay out as the file's page, #6147; `.ai`/EPS type from the text document lays its lines out closer to the file's page, within limits, #6137; `.ai` and EPS import: older Art brush objects read; numeric fields step on wheel hover without taking keyboard focus, and a panel wheel turn cannot seed a canvas zoom glide, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Tools stays beside the canvas with left-side panels; new gradients follow document color mode; Selection QoL: marquee modes and visible feedback, #5313; SVG CSS classes survive export and repeated import in all four styling modes (#2480); current-main integration retains event attributes and scripts; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026; Units › East Asian Type acts, with Q and Ha units; scores unchanged) > > Oversized artboard import update (2026-11-12): Explicit editing-data import retains a single artboard beyond a capped 14,400 pt PDF page (#6865). > @@ -1224,7 +1224,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Find Font: type in opacity masks and pattern swatches:** Type › Find Font…'s Document list (`text.fonts`), Find (`select.font`), Change and Change All (`text.replaceFont`), Replace Missing Fonts (`addon.cleanup.replaceFonts`), the Missing Fonts dialog (`text.missingFonts`), Document Info's Fonts and Font Details for the whole document (`document.info`), File › Package and the export warning about fonts that aren't available include the type in opacity masks (on objects and layers, disabled masks and masks inside mask, symbol or pattern art included) and in the art of pattern swatches, also a swatch that nothing paints with. A pattern's type is listed and changed once, however many objects the pattern paints. Find selects the objects whose mask holds the font, the text whose inline graphics draw a symbol whose art uses it, and the objects painted with a pattern whose art uses it by a visible fill or stroke above 0% opacity (VectorCraft's own rule; a layer's mask and a swatch that nothing paints with select nothing); Change and Change All leave the type in the mask of a hidden or locked object in its font and count it in `skipped`, and Change does the same with the type of a pattern that only hidden or locked selected objects paint with. The help page (https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/find-and-replace-fonts.html) says the Document list holds all the fonts the document uses. Tests: `crates/engine/src/cmd/fonts.rs`, `crates/engine/src/cmd/addons/cleanup/text/tests.rs`, `crates/engine/src/cmd/docinfo.rs`. - **Mouse wheel over the font family field (#5253):** In the Character and Properties panels and the Control bar, turning the mouse wheel over the closed font family field applies the next family to the selected type (the previous one when turned up), keeping the style when the family has it, else its closest. Each notch is one undo step; leaving the field or Escape keeps the family, and Undo takes it back. The steps use the font menu's order at the first turn and keep it until the pointer leaves the field or the text selection changes, so recent-font updates do not reshuffle later turns. That order follows the font filters every font menu shares (one chosen in the Glyphs panel's menu or a dialog's applies too) but ignores its search text, puts recent families first, and stops at the first and last family. A turn with a modifier key is left to the panel and the canvas. Behavior reference: the [Illustrator CS6 reference](https://help.adobe.com/archive/en/illustrator/cs6/illustrator_reference.pdf#page=10) describes editing hovered dropdown controls with the mouse wheel; [Find and apply fonts](https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/find-and-apply-fonts.html) names the family field's places. Applying it to the font field, the order, the ends and the undo steps are VectorCraft's own rules. Tests: `crates/ui-egui/src/tests/font_menu.rs`, `crates/ui-egui/src/font_menu/tests.rs`, `crates/ui-egui/tests/font_wheel.rs`. - **Type:** Character size, leading, baseline shift and horizontal-scale controls account for object/group transforms (#1034): exact document-point sizes, mixed-value display and keyboard increments, range edits, resettable horizontal stretch, undo/redo and native save/reopen (`tests_typescale`, Character panel tests). Point size is the transformed em-height axis, independent of glyph ink bounds; affine rotation, reflection and shear are retained. Group scaling still scales text; preserving font sizes is a separate enhancement. Behaviour reference: [Transform text](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/transform-text.html) (public documentation). Text Wrap, Type on a Path effects (Rainbow/Skew/3D Ribbon/Stair Step/Gravity) and Options (Flip, Align to Path, Spacing; OK keeps the type where it is), type on a path's start, centre and end brackets (Selection and Direct Selection drag them to set where the type runs, slide it along its path and, across the path, flip it: #429), Character and Paragraph Styles (override-preserving redefine), paragraph attributes per paragraph (alignment, indents, spacing, hyphenation, hanging punctuation, tab stops, paragraph style, direction, mojikumi and leading model; the Paragraph, Tabs and Paragraph Styles panels act on the paragraphs the Type tool's selection or caret touches, Return continues the paragraph's style, threads and SVG keep them), Area Type Options (rows/columns/inset/first baseline, Text Flow by rows or by columns (#4978), vertical Align Top/Center/Bottom/Justify per row/column, reflowing non-rectangular frames and text wrap), threaded text across any closed shapes, Fit Headline, the Every-line Composer for justified and ragged text (paragraph-wide breaks for an even rag, Single-line per object from the Paragraph panel menu or `text.setFormat {composer}`). Area type resizes like Illustrator's type areas: a bounding-box handle drag (Selection tool) or Area Type Options Width/Height size the frame and the text reflows at its size (threads too); Direct Selection drags a frame corner or edge (`text.reshapeArea`), and an anchor of the path of type on a path outside perspective, the type reflowing along it (`text.reshapePath`, #3980). Point type still scales. Auto Size: the frame height follows the text after every edit, in the same undo step (rectangular frames of horizontal type in one row; columns get the least height that holds them; a height set by hand or threading turns it off), and the "Auto Size New Area Type" preference gives it to new area type. Shrink Text to Fit (beyond Illustrator): overflowing area type scales its sizes, leading and baseline shifts down by the largest factor (in 0.1 pt steps of the first run, down to a minimum percentage) that fits, at layout time so every export sees it. `text.areaOptions` and `document.inspect` report overflow and the shrink factor. The Selection tool shows the type widget beside selected type's bounding box (hollow on point type, filled on area type); double-clicking it converts between them (Type › Convert To Area Type / Convert To Point Type), keeping the text and its styles in place. Variable fonts list every named instance as a style of their family (Figtree Light to Black and their italics, Bahnschrift, Segoe UI Variable), and the instance chosen is shaped, drawn, outlined and measured at its axis settings; PDF and SVG embed it as that instance. The Character panel's Variable Font sliders set each axis (see Fonts). Beyond Illustrator: inline graphics (`text.insertInline`, Type ▸ Insert Inline Symbol) set a document symbol in a run of type as one character that flows, wraps and justifies like a glyph (InDesign-style inline objects, e.g. mana symbols in card rules text): sized to a multiple of the font size, centred on the cap height, opening up tall lines, following Redefine Symbol; a character of its run's size for Character Alignment and Top-to-Top leading, placed by the bidi reordering in right-to-left text (a neutral U+FFFC); drawn on the canvas, kept as live text plus a `` in SVG and as vector art in PDF; Backspace removes it whole. Hebrew and Arabic are laid out right to left with the Unicode bidirectional algorithm (embedded Latin and numbers keep their direction, lines put in visual order after wrapping) in point, area and path type, with caret, selection and arrow keys in visual order; each paragraph's direction comes from its first strong character unless Paragraph Direction sets it (Paragraph panel with Show Indic Options, `text.setFormat {direction}`), and new type aligns to the start of its direction. SVG export outlines right-to-left text. Arabic typefaces: builds made with a craft-fonts checkout that holds its Arabic families (Noto Sans Arabic, the Arabic fallback, plus Cairo, Tajawal, Almarai, IBM Plex Sans Arabic, Noto Kufi Arabic, Amiri, Alexandria, Fustat, Reem Kufi, Aref Ruqaa and Gulzar once craft-fonts adds them, storytold/craft-fonts#11; releases get them when their craft-fonts pin moves) list them in the font menus on desktop; the web build fetches them from beside the wasm (Noto Sans Arabic before start, so Arabic never shows as boxes, the rest in the background). Font Size and Leading are combo boxes in the Character and Properties panels (Font Size in the Control bar too, beside the Character link, font family and style): type, step with ↑/↓ or the spinner, scrub the label, or pick a preset (6–72 pt, shown in the type unit; Leading also Auto), each pick one undo step; Kerning and Tracking list -100…200; a size, leading, kerning or tracking the selected text doesn't share shows blank (#458). Type › Bold and Italic (beyond Illustrator, as page layout apps have them; Cmd+Shift+B / Cmd+Shift+I while the Type tool edits text) switch the selected type to its family's own Bold or Italic face and back, keeping its width (a Narrow face stays Narrow); a family without that face says so (#724). The commands that make or restyle type (and the MCP `add_text` tool) say in their result when area type does not fit its frame, and `add_text` takes Auto Size and Shrink Text to Fit (#1500, fixes #1471). Cmd+Shift+C (Align center) works on Windows and Linux too (#1940). A double-click on area type's bottom middle handle turns on Auto Size (#1940). Print and Print Preview warn about the area type and type on a path that prints without the text that doesn't fit, and give their ids. A tab stop in the Tabs panel drags from where the press landed, a stop just added included (#1487, fixes #1458). Area type in a non-rectangular shape moves a word too wide for the top lines down to the first line that holds it instead of breaking it (#1617, fixes #1615). Stylistic sets (ss01–ss20), stylistic and titling alternates, proportional and lining figures, and superscript and subscript glyphs can be turned on as OpenType features of a character style (`text.setStyle {features}`), and the OpenType panel keeps them (#1611, fixes #1607). Escape while typing ends the edit, keeps the type selected and switches back to the tool that was active before type editing began (kept while clicks move editing to other text), or to the Selection tool when there was none (#1802, #1686, #1656). Select › Same matches type on its characters' fill and stroke (#1746, #1737). `text.create`, `text.createInPath` and the MCP `add_text` tool take `justify`, so point type can be centred on or end at its point (#1752, #1748). - The Character panel menu's Show Font Height Options shows a Set Font Height Reference dropdown under Font Size (`text.setFormat` or `text.setRangeStyle` `{fontHeightReference}`). With Em Box, the default, the font size is the size of the em square; with Cap Height, x-Height or ICF Box, the characters are set at the em size at which the font's OS/2 cap height, its x height or the height of its ideographic character face equals the font size. With a font that has no such value, or one outside 0.1 to 2 em, the size measures the em box. SVG, PDF and legacy `.ai` export and the CSS Properties rules write the em size the characters are drawn at, and DXF export writes 0.7 times that em size as each TEXT entity's height (group 40, the height of its capitals). Behavior reference: [Vary font height](https://web.archive.org/web/20251118174722/https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/vary-font-height.html). + The Character panel menu's Show Font Height Options shows a Set Font Height Reference dropdown under Font Size (`text.setFormat` or `text.setRangeStyle` `{fontHeightReference}`). Each reference shows an icon of the height it measures before its name, in the open list and in the closed field (#6304): a frame around a letter for Em Box, a bar from the baseline to the top of a capital for Cap Height and of a lowercase x for x-Height, and a frame inset in the em square for ICF Box. With Em Box, the default, the font size is the size of the em square; with Cap Height, x-Height or ICF Box, the characters are set at the em size at which the font's OS/2 cap height, its x height or the height of its ideographic character face equals the font size. With a font that has no such value, or one outside 0.1 to 2 em, the size measures the em box. SVG, PDF and legacy `.ai` export and the CSS Properties rules write the em size the characters are drawn at, and DXF export writes 0.7 times that em size as each TEXT entity's height (group 40, the height of its capitals). Behavior reference: [Vary font height](https://web.archive.org/web/20251118174722/https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/vary-font-height.html). The Every-line Composer composes a paragraph whose characters differ in line metrics (type size, font, leading or vertical scale, or an inline graphic taller than the text) in rectangular area type, rows and columns included. In area type of another shape, or with text wrap, such a paragraph is broken with the Single-line Composer. 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 Character panel's Anti-aliasing Method dropdown, under Character Rotation, sets how the edges of the selected type objects are drawn as pixels (`text.setFormat {antiAlias}`), for the whole object, with each frame of a thread keeping its own method and graph labels keeping it when the graph is drawn again, on the canvas, in raster exports and when the type is rasterized. VectorCraft defines the methods: Sharp, the default, draws edges as before; None paints a pixel only where the type covers at least half of it; Crisp shows a pixel covered by a share c at `2c - 0.5`, and Strong shows it at `2c`, each kept between 0 and 1. SVG, PDF and EPS export are unchanged. Scores and hours unchanged. @@ -2168,6 +2168,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s | Date | Change | Summary | |---|---|---| +| 2026-11-13 | minor | Character panel: the Set Font Height Reference dropdown shows an icon of the height each reference measures before its name, in the open list and in the closed field (#6304). Scores and estimates unchanged. | | 2026-11-12 | minor | Object › Rasterize with the Grayscale colour model makes a grayscale image (one gray channel, with alpha over a transparent background), so the Properties panel says Grayscale and a fill color colorizes it; Bitmap makes a 1-bit image (a TIFF), or a black-and-white gray image with alpha over a transparent background, since 1-bit gray with alpha is no format readers take. Both were RGB images of gray pixels (#6969). Scores and estimates unchanged. | | 2026-11-12 | minor | `.ai` and EPS import: type in an envelope with Distort Appearance is also tried mapped from the box of what it paints (its strokes' miter tips, and strokes a Transform effect moves, included), after its glyphs grown by half its strokes' weight; `.ai` import evidence updated (#6824); scores and estimates unchanged. | | 2026-11-12 | minor | Explicit editing-data import keeps a single artboard beyond a capped 14,400 pt PDF page (#6865), with a warning; automatic mode stays unchanged. Scores and estimates unchanged. |