From ad75ea5d0688d7b1d501a76aeada3b3c5dc6332b Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 24 Oct 2026 17:35:33 -0700 Subject: [PATCH] =?UTF-8?q?Type:=20Highlight=20Substituted=20Fonts=20moves?= =?UTF-8?q?=20to=20Preferences=20=E2=80=BA=20Type=20and=20applies=20to=20e?= =?UTF-8?q?very=20document?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Highlight Substituted Fonts is now a preference in Preferences › Type, next to Highlight Alternate Glyphs, on by default and saved with the other preferences (`prefs.set highlightSubstitutedFonts`). The canvas reads it through `RenderOptions::highlight_substituted_fonts` for every open document; a document's own setup no longer decides it. File › Document Setup no longer shows the checkbox and keeps Highlight Substituted Glyphs. A file saved with Document Setup's Highlight Substituted Fonts off now shows the pink highlight when it opens, while the preference is on. The file still opens with the stored value, and saving it again keeps that value. `document.setup` no longer reports `highlightSubstitutedFonts`. It ignores the key and adds a `warnings` entry that names `prefs.set` to its reply, so an older reply passed back still applies its other keys. The raster cache of type under a raster effect now also redraws when Highlight Alternate Glyphs changes. --- crates/doc/src/setup.rs | 3 +- crates/engine/src/cmd/docsetup.rs | 28 ++++++++++--- crates/engine/src/cmd/prefscmds.rs | 1 + crates/engine/src/lib.rs | 4 ++ crates/engine/src/tests_docsetup.rs | 42 ++++++++++++++++++++ crates/engine/src/tests_prefs.rs | 19 +++++++++ crates/render/src/fx.rs | 16 +++++--- crates/render/src/lib.rs | 18 ++++++--- crates/render/src/tests_alternate.rs | 31 ++++++++++++++- crates/render/src/tests_memory.rs | 23 +++++++++-- crates/ui-egui/src/canvas.rs | 34 ++++++++++++++++ crates/ui-egui/src/dialogs/document_setup.rs | 24 +++++++++-- crates/ui-egui/src/lib.rs | 2 + docs/control-protocol.md | 2 +- docs/mcp.md | 14 +++++-- docs/target-app-parity.md | 7 ++-- 16 files changed, 233 insertions(+), 35 deletions(-) diff --git a/crates/doc/src/setup.rs b/crates/doc/src/setup.rs index d3860fc58..b5a1ffe32 100644 --- a/crates/doc/src/setup.rs +++ b/crates/doc/src/setup.rs @@ -212,7 +212,8 @@ pub struct DocSetup { pub bleed: [f64; 4], /// Show Images in Outline Mode. pub outline_images: bool, - /// Highlight text whose font is missing (drawn in a substitute font). + /// The Highlight Substituted Fonts value older builds kept in Document Setup. Files keep it as + /// they were saved; the screen follows Preferences › Type › Highlight Substituted Fonts. pub highlight_substituted_fonts: bool, /// Highlight glyphs the font lacks (drawn from a fallback font). pub highlight_substituted_glyphs: bool, diff --git a/crates/engine/src/cmd/docsetup.rs b/crates/engine/src/cmd/docsetup.rs index 4446bc0ad..0dc6fbad3 100644 --- a/crates/engine/src/cmd/docsetup.rs +++ b/crates/engine/src/cmd/docsetup.rs @@ -17,7 +17,7 @@ pub fn specs() -> Vec { "Document Setup", [], None, - "{units?: \"Points\"|\"Inches\"|\"Millimeters\"|…, bleed?: pt (all sides)|[top, bottom, left, right]|{top?, bottom?, left?, right?} (0–72 pt), outlineImages?: bool (show images in Outline mode), highlightSubstitutedFonts?: bool, highlightSubstitutedGlyphs?: bool, gridSize?: \"small\"|\"medium\"|\"large\", gridColors?: \"Light\"|\"Medium\"|\"Dark\"|\"Red\"|\"Orange\"|\"Green\"|\"Blue\"|\"Purple\"|[colour, colour] (transparency grid; the first is also the paper colour), simulatePaper?: bool, flattenerPreset?: \"High Resolution\"|\"Medium Resolution\"|\"Low Resolution\"|a saved preset (flattener.presets.list), discardWhiteOverprint?: bool, language?: \"English: USA\"|\"German\"|\"French\"|… (also picks its quotes), quotes?: {double?: \"“”\", single?: \"‘’\"} (beside a language: only if they differ from the current ones), typographersQuotes?: bool (typed straight quotes become the document's quotes), superscript?: {size?: %, position?: %}, subscript?: {size?: %, position?: %}, smallCapsSize?: %, exportText?: \"editable\"|\"appearance\" (SVG text as text or as outlines), backgroundContents?: \"transparent\"|\"white\" (white: raster exports are white behind the art), …the keys of its own reply (it can be passed back; the values it only reports are ignored)} change the document setup in one undo step; no params → the current setup", + "{units?: \"Points\"|\"Inches\"|\"Millimeters\"|…, bleed?: pt (all sides)|[top, bottom, left, right]|{top?, bottom?, left?, right?} (0–72 pt), outlineImages?: bool (show images in Outline mode), highlightSubstitutedGlyphs?: bool (Highlight Substituted Fonts is a preference: prefs.set highlightSubstitutedFonts; a highlightSubstitutedFonts key is ignored and the reply carries a warning),gridSize?: \"small\"|\"medium\"|\"large\", gridColors?: \"Light\"|\"Medium\"|\"Dark\"|\"Red\"|\"Orange\"|\"Green\"|\"Blue\"|\"Purple\"|[colour, colour] (transparency grid; the first is also the paper colour), simulatePaper?: bool, flattenerPreset?: \"High Resolution\"|\"Medium Resolution\"|\"Low Resolution\"|a saved preset (flattener.presets.list), discardWhiteOverprint?: bool, language?: \"English: USA\"|\"German\"|\"French\"|… (also picks its quotes), quotes?: {double?: \"“”\", single?: \"‘’\"} (beside a language: only if they differ from the current ones), typographersQuotes?: bool (typed straight quotes become the document's quotes), superscript?: {size?: %, position?: %}, subscript?: {size?: %, position?: %}, smallCapsSize?: %, exportText?: \"editable\"|\"appearance\" (SVG text as text or as outlines), backgroundContents?: \"transparent\"|\"white\" (white: raster exports are white behind the art), …the keys of its own reply (it can be passed back; the values it only reports are ignored)} change the document setup in one undo step; no params → the current setup", has_doc, setup )] @@ -31,7 +31,6 @@ pub(crate) fn setup_json(d: &Document) -> Value { "units": d.units.label(), "bleed": s.bleed, "outlineImages": s.outline_images, - "highlightSubstitutedFonts": s.highlight_substituted_fonts, "highlightSubstitutedGlyphs": s.highlight_substituted_glyphs, "gridSize": s.grid_size.id(), "gridColors": s.grid_colors.map(|c| c.to_hex()), @@ -72,9 +71,19 @@ fn setup(s: &mut Session, p: &Value) -> Result { } apply(&mut new, &mut units, k, v, &s.prefs.flattener_presets)?; } + // An older build's reply passed back still applies; the moved key only gets a warning. + let reply = |d: &Document| { + let mut r = setup_json(d); + if o.contains_key("highlightSubstitutedFonts") + && let Some(m) = r.as_object_mut() + { + m.insert("warnings".into(), json!([MOVED_FONTS_HIGHLIGHT])); + } + r + }; let st = s.doc()?; if new == st.doc.setup && units == st.doc.units { - return Ok(setup_json(&st.doc)); + return Ok(reply(&st.doc)); } s.edit("Document Setup", |d, _| { let scripts_changed = @@ -86,9 +95,13 @@ fn setup(s: &mut Session, p: &Value) -> Result { } Ok(()) })?; - Ok(setup_json(&s.doc()?.doc)) + Ok(reply(&s.doc()?.doc)) } +/// The warning for `highlightSubstitutedFonts`, which `document.setup` ignores. +const MOVED_FONTS_HIGHLIGHT: &str = + "highlightSubstitutedFonts is ignored: it is a preference (Preferences › Type), set with prefs.set {key: \"highlightSubstitutedFonts\", value}"; + fn flag(k: &str, v: &Value) -> Result { v.as_bool().ok_or_else(|| bad(C, format!("{k} must be true or false"))) } @@ -102,7 +115,10 @@ fn apply(s: &mut DocSetup, units: &mut vectorcraft_doc::Unit, k: &str, v: &Value "units" => *units = v.as_str().and_then(vectorcraft_doc::Unit::named).ok_or_else(|| bad(C, "unknown units"))?, "bleed" => s.bleed = bleed_param(v, s.bleed, C)?, "outlineImages" => s.outline_images = flag(k, v)?, - "highlightSubstitutedFonts" => s.highlight_substituted_fonts = flag(k, v)?, + // Preferences › Type holds it for every document: ignored, with a warning in the reply. + "highlightSubstitutedFonts" => { + flag(k, v)?; + } "highlightSubstitutedGlyphs" => s.highlight_substituted_glyphs = flag(k, v)?, "gridSize" => s.grid_size = v.as_str().and_then(GridSize::parse).ok_or_else(|| bad(C, "gridSize must be small, medium or large"))?, "gridColors" => s.grid_colors = grid_colors(v)?, @@ -125,7 +141,7 @@ fn apply(s: &mut DocSetup, units: &mut vectorcraft_doc::Unit, k: &str, v: &Value "exportText" => s.export_text = v.as_str().and_then(ExportText::parse).ok_or_else(|| bad(C, "exportText must be editable or appearance"))?, "backgroundContents" => s.background = background_param(v, C)?, // Read-only values `document.setup` reports. - "gridColorsName" => {} + "gridColorsName" | "warnings" => {} _ => return Err(bad(C, format!("unknown setting `{k}`"))), } Ok(()) diff --git a/crates/engine/src/cmd/prefscmds.rs b/crates/engine/src/cmd/prefscmds.rs index 574b2d5f7..b3ab20ddb 100644 --- a/crates/engine/src/cmd/prefscmds.rs +++ b/crates/engine/src/cmd/prefscmds.rs @@ -193,6 +193,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[ p!("recentFontsCount", "Type", "Options", "Number of Recent Fonts", int(1, 15)), p!("missingGlyphProtection", "Type", "Options", "Enable Missing Glyph Protection", bool), p!("highlightAlternateGlyphs", "Type", "Options", "Highlight Alternate Glyphs", bool), + p!("highlightSubstitutedFonts", "Type", "Options", "Highlight Substituted Fonts", bool), p!("placeholderText", "Type", "Options", "Fill New Type Objects With Placeholder Text", bool), p!("fontsFolder", "Type", "Options", "Additional Fonts Folder", text), // Units diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 8321041ff..bf1d3d1c9 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -986,6 +986,9 @@ pub struct Prefs { pub recent_fonts_count: u32, pub missing_glyph_protection: bool, pub highlight_alternate_glyphs: bool, + /// Type › Highlight Substituted Fonts: on screen, text drawn in a substitute for a missing + /// font is highlighted, in every document (on by default). + pub highlight_substituted_fonts: bool, pub placeholder_text: bool, // Units pub units_general: String, @@ -1243,6 +1246,7 @@ impl Default for Prefs { recent_fonts_count: 10, missing_glyph_protection: true, highlight_alternate_glyphs: false, + highlight_substituted_fonts: true, placeholder_text: true, units_general: s("points"), units_stroke: s("points"), diff --git a/crates/engine/src/tests_docsetup.rs b/crates/engine/src/tests_docsetup.rs index 4ec581e8d..c4a4fa3e3 100644 --- a/crates/engine/src/tests_docsetup.rs +++ b/crates/engine/src/tests_docsetup.rs @@ -288,6 +288,48 @@ fn setup_survives_save_and_load() { assert_eq!(back.setup.grid_colors_name(), "Custom"); } +/// Highlight Substituted Fonts is a preference (Preferences › Type): `document.setup` does not +/// report it, and ignores it with a warning that names `prefs.set`, so an older build's reply +/// passed back still applies its other keys. The document's stored value and the preference stay. +#[test] +fn highlight_substituted_fonts_is_ignored_with_a_pointer_to_the_preference() { + let mut s = session(); + let q = s.execute("document.setup", &json!({})).unwrap(); + assert!(q.get("highlightSubstitutedFonts").is_none() && q.get("warnings").is_none(), "{q}"); + let r = s.execute("document.setup", &json!({"highlightSubstitutedFonts": false, "bleed": 9})).unwrap(); + let w = r["warnings"][0].as_str().unwrap_or_default(); + assert!(w.contains("prefs.set") && w.contains("highlightSubstitutedFonts"), "{r}"); + assert_eq!(setup(&s).bleed, [9.0; 4]); + assert!(setup(&s).highlight_substituted_fonts, "the stored value is not changed"); + assert!(s.prefs.highlight_substituted_fonts, "nor is the preference"); + assert_eq!(s.doc().unwrap().history.undo.len(), 1); + // The reply, warning included, can be passed back; alone, the key changes nothing. + let mut plain = r.clone(); + plain.as_object_mut().unwrap().remove("warnings"); + assert_eq!(s.execute("document.setup", &r).unwrap(), plain); + assert_eq!(s.execute("document.setup", &json!({"highlightSubstitutedFonts": true})).unwrap()["warnings"], r["warnings"]); + assert_eq!(s.doc().unwrap().history.undo.len(), 1); + let e = s.execute("document.setup", &json!({"highlightSubstitutedFonts": 1})).unwrap_err().to_string(); + assert!(e.contains("true or false"), "{e}"); +} + +/// A file saved with Document Setup's old Highlight Substituted Fonts value opens, keeps that +/// value when saved again, and leaves the preference as it was. +#[test] +fn a_file_with_the_old_highlight_substituted_fonts_value_opens_and_keeps_it() { + let mut s = session(); + let mut d = (*s.doc().unwrap().doc).clone(); + d.setup.highlight_substituted_fonts = false; + let data = vectorcraft_format::base64_encode(&vectorcraft_format::save_file(&d)); + s.execute("document.open", &json!({"name": "old.vectorcraft", "dataBase64": data})).unwrap(); + assert!(!setup(&s).highlight_substituted_fonts); + assert!(s.prefs.highlight_substituted_fonts, "the preference decides the screen"); + let again = vectorcraft_format::load(&vectorcraft_format::save_file(&s.doc().unwrap().doc)).unwrap(); + assert!(!again.setup.highlight_substituted_fonts); + let q = s.execute("document.setup", &json!({})).unwrap(); + assert_eq!(s.execute("document.setup", &q).unwrap(), q); +} + /// A file whose bleed, superscript, subscript or small caps size lies outside the range /// `document.setup` takes opens with it at the nearer end of that range, and the setup it reports /// can be fed back. diff --git a/crates/engine/src/tests_prefs.rs b/crates/engine/src/tests_prefs.rs index 86fd7161c..1605c0775 100644 --- a/crates/engine/src/tests_prefs.rs +++ b/crates/engine/src/tests_prefs.rs @@ -1454,6 +1454,25 @@ fn highlight_alternate_glyphs_preference_is_off_by_default() { assert!(!s.prefs.highlight_alternate_glyphs); } +/// Preferences › Type › Highlight Substituted Fonts: on by default, listed in the Type category, +/// and saved with the other preferences. +/// +#[test] +fn highlight_substituted_fonts_preference_is_on_by_default_and_saved() { + let mut s = Session::new(); + assert!(s.prefs.highlight_substituted_fonts, "on by default"); + let spec = crate::cmd::prefscmds::PREF_SPECS.iter().find(|p| p.key == "highlightSubstitutedFonts").expect("listed"); + assert_eq!((spec.category, spec.label), ("Type", "Highlight Substituted Fonts")); + assert_eq!(s.execute("prefs.get", &json!({"key": "highlightSubstitutedFonts"})).unwrap(), json!(true)); + set_pref(&mut s, "highlightSubstitutedFonts", json!(false)); + assert!(!s.prefs.highlight_substituted_fonts); + let saved: Prefs = serde_json::from_value(serde_json::to_value(&s.prefs).unwrap()).unwrap(); + assert!(!saved.highlight_substituted_fonts); + // Preferences saved before the key existed read it as on. + let old: Prefs = serde_json::from_value(json!({"highlightAlternateGlyphs": true})).unwrap(); + assert!(old.highlight_substituted_fonts); +} + /// Preferences › Performance › Real-time Drawing and Editing (#394): off, the document isn't /// previewed while dragging but the pending shape still shows as a canvas overlay until commit. #[test] diff --git a/crates/render/src/fx.rs b/crates/render/src/fx.rs index 5df4cf046..166accd4a 100644 --- a/crates/render/src/fx.rs +++ b/crates/render/src/fx.rs @@ -35,8 +35,8 @@ //! //! A cached raster is made again when something its content reads from outside the object //! changes ([`Inputs`]): the pattern definitions, symbols, images and brushes it uses, the -//! document's raster resolution, color mode and substitution highlights, the knockout its object -//! inherits, the color settings and fonts, the plug-ins its plug-in effects run, and the options +//! document's raster resolution and color mode, the substitution highlights, the knockout its +//! object inherits, the color settings and fonts, the plug-ins its plug-in effects run, and the options //! and layer settings it is drawn with. //! //! The same applies to one fill or stroke's own raster effects (around that item alone) and to @@ -941,8 +941,8 @@ pub(crate) struct Reads { images: bool, /// Brushed strokes: the brush library. brushes: bool, - /// Type: the fonts, whether overprinting shows ([`crate::proof::overprints`]) and Document - /// Setup's substitution highlights. + /// Type: the fonts, whether overprinting shows ([`crate::proof::overprints`]) and the + /// substitution highlights. text: bool, /// Plug-in effects on its objects: the plug-ins installed. Those in the symbol and pattern art /// it shows are found with the document's inputs ([`DocInputs`]). @@ -2136,8 +2136,12 @@ impl Renderer { self.brushes.library_key(f.doc, self.stamp).hash(&mut h); } if reads.text { - let setup = &f.doc.setup; - let highlights = f.opts.highlight_substitutions.then_some((setup.highlight_substituted_fonts, setup.highlight_substituted_glyphs)); + let o = f.opts; + let highlights = o.highlight_substitutions.then_some(( + o.highlight_substituted_fonts, + f.doc.setup.highlight_substituted_glyphs, + o.highlight_alternate_glyphs, + )); (vectorcraft_text::FontDb::global().generation(), crate::proof::overprints(f.opts), highlights).hash(&mut h); } if reads.plugins || (doc.plugins && (reads.symbols || reads.patterns)) { diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index a9e63db4e..49e6e23ff 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -145,8 +145,12 @@ pub struct RenderOptions { /// View Opacity Mask (Alt-click the mask thumbnail): instead of the artwork, show this object's /// opacity mask alone as its coverage in greyscale (white = opaque, black = transparent). pub mask_view: Option, - /// Screen view: highlight substituted fonts and glyphs as Document Setup asks. + /// Screen view: highlight substituted fonts and glyphs, as Preferences › Type and Document + /// Setup ask. pub highlight_substitutions: bool, + /// Screen view: Preferences › Type › Highlight Substituted Fonts. Pink behind text drawn in a + /// substitute for a missing font, in every document (on by default). + pub highlight_substituted_fonts: bool, /// Screen view: Preferences › Type › Highlight Alternate Glyphs. Pink behind glyphs OpenType /// features substituted (ligatures, contextual forms), not Document Setup's missing-glyph /// highlights. @@ -266,6 +270,7 @@ impl Default for RenderOptions { guide: ([0x4a, 0xd8, 0xff], false), mask_view: None, highlight_substitutions: false, + highlight_substituted_fonts: true, highlight_alternate_glyphs: false, anti_alias: AntiAlias::Art, progressive_placed: false, @@ -1976,10 +1981,9 @@ impl Renderer { self.draw_text_items(ctx, f, n, below, all, tb); } if f.opts.highlight_substitutions { - let setup = &f.doc.setup; for (on, path) in [ - (setup.highlight_substituted_fonts, Some(&g.substituted_fonts)), - (setup.highlight_substituted_glyphs, Some(&g.substituted_glyphs)), + (f.opts.highlight_substituted_fonts, Some(&g.substituted_fonts)), + (f.doc.setup.highlight_substituted_glyphs, Some(&g.substituted_glyphs)), // Measured only when shown: it lays the text out again. (f.opts.highlight_alternate_glyphs, f.opts.highlight_alternate_glyphs.then(|| g.alternate_glyphs(t))), ] { @@ -2535,7 +2539,8 @@ struct TextGeom { spans: Vec<(usize, usize, usize)>, bounds: Rect, /// The boxes of characters whose font is missing (drawn in a substitute), and of glyphs the - /// font lacks (drawn from a fallback font): Document Setup's substitution highlights. + /// font lacks (drawn from a fallback font): Highlight Substituted Fonts and Highlight + /// Substituted Glyphs. substituted_fonts: BezPath, substituted_glyphs: BezPath, /// Glyphs OpenType may have substituted (a ligature, or not the face's cmap glyph for their @@ -2733,7 +2738,8 @@ fn text_geom_snapped(t: &TextObject, snap: Option) -> TextGeom { } } -/// Document Setup's and Preferences › Highlight Alternate Glyphs pink (screen only). +/// The substitution highlights' pink: Preferences › Highlight Substituted Fonts and Highlight +/// Alternate Glyphs, and Document Setup's Highlight Substituted Glyphs (screen only). const SUBSTITUTED: peniko::Color = peniko::Color::from_rgba8(255, 120, 190, 110); /// The pixel size of `region` rendered at `scale` pixels per point (at least 1×1). pub fn region_pixels(region: Rect, scale: f64) -> (u32, u32) { diff --git a/crates/render/src/tests_alternate.rs b/crates/render/src/tests_alternate.rs index 2438be8be..4459f37db 100644 --- a/crates/render/src/tests_alternate.rs +++ b/crates/render/src/tests_alternate.rs @@ -1,8 +1,37 @@ //! Preferences › Type › Highlight Alternate Glyphs (#394): OpenType substitutions get a pink -//! cell behind them on screen when the preference is on. +//! cell behind them on screen when the preference is on. Preferences › Type › Highlight +//! Substituted Fonts does the same for text drawn in a substitute for a missing font. use super::*; +/// Highlight Substituted Fonts follows the preference passed in [`RenderOptions`] for every +/// document, whatever a document's own setup stored: on, type in a missing font has pink behind +/// it; off, it does not. +#[test] +fn highlight_substituted_fonts_follows_the_preference_in_every_document() { + use vectorcraft_doc::text::{CharStyle, TextObject}; + use vectorcraft_doc::{Node, NodeKind}; + use vectorcraft_geom::Point; + + let on = RenderOptions { background: Some([255, 255, 255, 255]), highlight_substitutions: true, ..Default::default() }; + assert!(on.highlight_substituted_fonts, "on by default"); + let off = RenderOptions { highlight_substituted_fonts: false, ..on.clone() }; + let pinkish = |px: &[u8]| px.chunks(4).filter(|p| p[0] > 200 && p[1] < 180 && p[2] > 150 && p[2] < 230).count(); + // A document saved with the old Document Setup value on, and one with it off. + for stored in [true, false] { + let mut d = Document::new(200.0, 100.0); + d.setup.highlight_substituted_fonts = stored; + let style = CharStyle { size: 30.0, font_family: "No Such Family".into(), ..Default::default() }; + let n = Node::new(d.alloc_id(), NodeKind::Text(Box::new(TextObject::point(Point::new(10.0, 60.0), "Hamburg", style)))); + let l = d.layers[0].id; + d.insert(Some(l), 0, n).unwrap(); + let with = Renderer::new().render(&d, 200, 100, Affine::IDENTITY, &on); + let without = Renderer::new().render(&d, 200, 100, Affine::IDENTITY, &off); + assert!(pinkish(&with.pixels) > 50, "stored {stored}: the preference on highlights the text"); + assert_eq!(pinkish(&without.pixels), 0, "stored {stored}: the preference off leaves no highlight"); + } +} + #[test] fn is_opentype_alternate_marks_ligatures_and_forms() { assert!(is_opentype_alternate(100, 100, 2), "ligature cluster"); diff --git a/crates/render/src/tests_memory.rs b/crates/render/src/tests_memory.rs index fdc1447cf..9b0304e3c 100644 --- a/crates/render/src/tests_memory.rs +++ b/crates/render/src/tests_memory.rs @@ -1794,8 +1794,8 @@ fn missing_font_type(d: &mut Document, fx: vectorcraft_doc::Effect) -> Node { /// Raster effects are made again when a document or layer setting they read changes: the raster /// resolution under De-Interlace, Page Knockout Group around a blurred group of translucent -/// objects, Document Setup's highlight of substituted fonts under a blur, and the layer's Dim -/// Images under an image's blur. +/// objects, the Highlight Substituted Fonts and Highlight Alternate Glyphs preferences under a +/// blur, and the layer's Dim Images under an image's blur. #[test] fn rasters_draw_document_and_layer_setting_edits() { let _fonts = fonts_steady(); @@ -1823,14 +1823,29 @@ fn rasters_draw_document_and_layer_setting_edits() { edit_draws_as_new(with_art(d, vec![group]), |d| d.page_knockout = true); // Blurred type in a missing family, with the substitution highlights on screen. let mut d = Document::new(200.0, 100.0); - d.setup.highlight_substituted_fonts = false; let blurred = missing_font_type(&mut d, effect("blur.gaussian", serde_json::json!({"radius": 1.0}))); let mut d = with_art(d, vec![blurred]); let highlights = RenderOptions { background: Some([255, 255, 255, 255]), highlight_substitutions: true, ..Default::default() }; + let unhighlighted = RenderOptions { highlight_substituted_fonts: false, ..highlights.clone() }; // The first lookup of the missing family catalogs the installed fonts, which changes the font // generation: a frame drawn first does that. Renderer::new().render(&d, 200, 100, Affine::IDENTITY, &highlights); - draws_as_new(&mut d, &highlights, &highlights, |d| d.setup.highlight_substituted_fonts = true); + // Preferences › Type › Highlight Substituted Fonts turned on between two frames. + draws_as_new(&mut d, &unhighlighted, &highlights, |_| {}); + // Blurred type with a ligature, with Highlight Alternate Glyphs turned on between two frames. + let mut d = Document::new(200.0, 100.0); + let mut ligature = missing_font_type(&mut d, effect("blur.gaussian", serde_json::json!({"radius": 1.0}))); + if let NodeKind::Text(t) = &mut ligature.kind { + **t = vectorcraft_doc::text::TextObject::point( + vectorcraft_geom::Point::new(10.0, 60.0), + "fi", + vectorcraft_doc::text::CharStyle { size: 48.0, font_family: "Source Serif 4".into(), ..Default::default() }, + ); + } + let mut d = with_art(d, vec![ligature]); + let alternates = RenderOptions { highlight_alternate_glyphs: true, ..highlights.clone() }; + Renderer::new().render(&d, 200, 100, Affine::IDENTITY, &highlights); + draws_as_new(&mut d, &highlights, &alternates, |_| {}); // The layer of a blurred image dims its images to 50%. edit_draws_as_new(blurred_image_doc("dimmed", 106), |d| { if let NodeKind::Layer { dim_images, .. } = &mut Arc::make_mut(&mut d.layers[0]).kind { diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 1b6f2846d..99f5d96f1 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -421,6 +421,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { rot: v.rotation, anti_alias: app.session.prefs.anti_aliased_artwork, highlight_alternate_glyphs: app.session.prefs.highlight_alternate_glyphs, + highlight_substituted_fonts: app.session.prefs.highlight_substituted_fonts, placed: vectorcraft_render::placed_document::generation(), pixel, smooth_images, @@ -494,6 +495,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { highlight_substitutions: true, // Type › Highlight Alternate Glyphs (#394): pink behind OpenType substitutions. highlight_alternate_glyphs: app.session.prefs.highlight_alternate_glyphs, + // Type › Highlight Substituted Fonts: pink behind text in a substitute font. + highlight_substituted_fonts: app.session.prefs.highlight_substituted_fonts, // General › Anti-aliased Artwork: off, edges are hard on screen (raster effects and // pattern tiles stay smooth), as in Illustrator. anti_alias: if app.session.prefs.anti_aliased_artwork { vectorcraft_render::AntiAlias::Art } else { vectorcraft_render::AntiAlias::None }, @@ -5614,6 +5617,37 @@ mod tests { assert!(partial(&alphas(&mut app).expect("re-rendered")) > 50, "smooth again"); } + /// Type › Highlight Substituted Fonts: on (the default), type in a missing font has pink + /// behind it on the canvas; off, it does not, and turning it either way re-renders. + #[test] + fn highlight_substituted_fonts_preference_reaches_the_canvas() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + app.canvas.worker_started = true; // render on this thread + app.session.execute("text.create", &json!({"x": 20, "y": 120, "text": "Hamburg", "size": 60, "font": "Missing Family"})).unwrap(); + let ctx = egui::Context::default(); + // How many pink pixels the art's raster uploaded by one frame has, None when it was not + // re-rendered. + let pink = |app: &mut VectorcraftApp| -> Option { + let (_, mut delta) = frame_output(app, &ctx); + let art = app.canvas.texture.as_ref().unwrap().id(); + let n = delta.set.iter().find(|e| e.0.eq(&art)).and_then(|e| e.1.last()).map(|d| { + let egui::ImageData::Color(img) = &d.image; + img.pixels.iter().filter(|c| c.a() != 0 && c.r() > c.g().saturating_add(30) && c.b() > c.g()).count() + }); + delta.clear(); + n + }; + let on = pink(&mut app).expect("the first frame renders the art"); + assert!(on > 100, "on by default: the text has pink behind it ({on} pixels)"); + assert!(app.canvas.shown.as_ref().is_some_and(|(_, o)| o.highlight_substituted_fonts)); + app.session.execute("prefs.set", &json!({"key": "highlightSubstitutedFonts", "value": false})).unwrap(); + assert_eq!(pink(&mut app), Some(0), "the preference off re-renders without the highlight"); + assert!(app.canvas.key.as_ref().is_some_and(|k| !k.highlight_substituted_fonts)); + app.session.execute("prefs.set", &json!({"key": "highlightSubstitutedFonts", "value": true})).unwrap(); + assert_eq!(pink(&mut app), Some(on), "on again, as before"); + } + /// A drag of an object with raster effects (#1257): the drag's frames render while /// interacting and draw the object's drop shadow and filtered content from the rasters of the /// frame before; the frame after the drag renders without `interacting`, on the render worker, diff --git a/crates/ui-egui/src/dialogs/document_setup.rs b/crates/ui-egui/src/dialogs/document_setup.rs index 070d93594..666d25258 100644 --- a/crates/ui-egui/src/dialogs/document_setup.rs +++ b/crates/ui-egui/src/dialogs/document_setup.rs @@ -1,5 +1,5 @@ -//! File → Document Setup: General (units, bleed, Edit Artboards, outline images, substitution -//! highlights), Transparency (grid size and colours, simulated paper, flattener preset, white +//! File → Document Setup: General (units, bleed, Edit Artboards, outline images, the substituted +//! glyphs highlight), Transparency (grid size and colours, simulated paper, flattener preset, white //! overprint) and Type (language, quotes, superscript/subscript/small caps, text export) tabs. //! //! Fields: what `document.setup` reports (and takes back), plus `tab` and `bleedLinked`. OK runs @@ -98,7 +98,6 @@ fn general(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool }); ui.add_space(12.0); check(ui, d, "outlineImages", tl!("Show Images in Outline Mode")); - check(ui, d, "highlightSubstitutedFonts", tl!("Highlight Substituted Fonts")); check(ui, d, "highlightSubstitutedGlyphs", tl!("Highlight Substituted Glyphs")); close } @@ -325,4 +324,23 @@ mod tests { assert!(crate::dialogs::confirm(&mut app).is_err()); assert!(app.ui.dialog.is_some()); } + + /// Highlight Substituted Fonts is in Preferences › Type, so the General tab shows only the + /// substituted glyphs highlight, and OK still applies. + #[test] + fn general_tab_has_no_substituted_fonts_checkbox() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 200})).unwrap(); + app.run("file.documentSetup", json!({})).unwrap(); + assert!(app.ui.dialog.as_ref().unwrap().fields.get("highlightSubstitutedFonts").is_none()); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + crate::theme::apply(&ctx, Default::default()); + dialog_frame(&mut app, &ctx, vec![]); + let texts: Vec = dialog_frame(&mut app, &ctx, vec![]).into_iter().map(|(t, _)| t).collect(); + assert!(texts.iter().any(|t| t == "Highlight Substituted Glyphs"), "{texts:?}"); + assert!(!texts.iter().any(|t| t == "Highlight Substituted Fonts"), "{texts:?}"); + crate::dialogs::confirm(&mut app).unwrap(); + assert!(app.ui.dialog.is_none()); + } } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 93d1366a6..dd9d987ba 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -432,6 +432,8 @@ pub struct CacheKey { pub anti_alias: bool, /// Type › Highlight Alternate Glyphs (#394). pub highlight_alternate_glyphs: bool, + /// Type › Highlight Substituted Fonts. + pub highlight_substituted_fonts: bool, /// [`vectorcraft_render::placed_document::generation`]: placed documents' bitmaps made since. pub placed: u64, /// View › Pixel Preview: the document pixels rendered (x0, y0, x1, y1), one per point, shown diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 5cc400298..3e7649ac2 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -466,7 +466,7 @@ string is points). The `preferences` dialog's `unitsGeneral` starts as the activ Document Setup: File → Document Setup… (`file.documentSetup`, Cmd+Alt+P) opens the `documentSetup` dialog. Its fields are what `document.setup` reports (`units`, `bleed` [top, bottom, left, right] in pt, `outlineImages`, -`highlightSubstitutedFonts`, `highlightSubstitutedGlyphs`, `gridSize`, `gridColors`, `simulatePaper`, `flattenerPreset`, +`highlightSubstitutedGlyphs`, `gridSize`, `gridColors`, `simulatePaper`, `flattenerPreset`, `discardWhiteOverprint`, `language`, `quotes`, `typographersQuotes`, `superscript`, `subscript`, `smallCapsSize`, `exportText`, `backgroundContents`) plus `tab` (`General`, `Transparency` or `Type`) and `bleedLinked`. `ui.dialog.confirm` runs `document.setup` with them as one undo step; a bad value answers with an error and the dialog diff --git a/docs/mcp.md b/docs/mcp.md index 8fe109fce..67afd06ea 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2784,8 +2784,9 @@ off, an empty window, and the Home button or `app.home` still shows the screen), default: on, a click away from a panel popped out of the icon column puts it away), `displayPrintSize` (off by default: View › Actual Size / `view.actualSize` is one document point per screen point; on, one document inch fills 96 screen points, an inch at the system's reference density whatever the display scaling). `highlightAlternateGlyphs` (off by default: on, the canvas paints a pink cell behind glyphs OpenType features -substituted — ligatures, contextual forms — on screen; Document Setup's Highlight Substituted Fonts / Glyphs stay -separate). `blackOnScreen` +substituted — ligatures, contextual forms — on screen; Document Setup's Highlight Substituted Glyphs stays +separate). `highlightSubstitutedFonts` (on by default: the canvas paints pink behind text drawn in a substitute for a +missing font, in every document). `blackOnScreen` (Appearance of Black › On Screen, `accurate` by default) sets how the canvas shows pure CMYK black, black ink alone at 100%: `accurate` shows it through the CMYK profile, a dark grey, and `rich` shows it as RGB black. Rich blacks, black tints and images show the same either way, and exports are unchanged. `blackOutput` (Appearance of Black › @@ -2901,8 +2902,13 @@ Pen's second anchor at (150, 431), in line with the square's centre. matched without regard to case: a word with no hyphens is never hyphenated (`typography`), and a word with hyphens breaks only where they are (`hap-pen`). `prefs.set` pushes the list into the hyphenator and reflows open documents. - `highlightAlternateGlyphs` (off by default): Preferences › Type › Highlight Alternate Glyphs. On, the canvas paints - pink behind OpenType substitutions (ligatures, contextual forms). Document Setup's Highlight Substituted Fonts / - Glyphs stay separate. Agents set it with `prefs.set`; the desktop canvas reads it on the next paint. + pink behind OpenType substitutions (ligatures, contextual forms). Document Setup's Highlight Substituted Glyphs + stays separate. Agents set it with `prefs.set`; the desktop canvas reads it on the next paint. +- `highlightSubstitutedFonts` (on by default): Preferences › Type › Highlight Substituted Fonts. On, the canvas paints + pink behind text drawn in a substitute for a missing font, in every open document. Off, no document shows that + highlight. `document.setup` ignores this key and adds a `warnings` entry that names `prefs.set` to its reply. A file + saved with the key in its Document Setup still opens and keeps the value it was saved with, and the preference + decides whether that file shows the highlight. ```json {"name":"run_command","arguments":{"command":"prefs.set","params":{"values":{"typeSizeIncrement":4,"trackingIncrement":50}}}} diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index c183a6d81..5c8002081 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -576,10 +576,11 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Native macOS save sheets offer a localised Format menu (#866), built from the request's existing format filters. It selects the suggested filename's format and updates the suffix when changed, including legacy `.drawcraft` names. `apps/vectorcraft/src/mac_save_format.rs` has filename regressions; an actual native SVG save, selection of PDF and cancellation without changing the saved document were verified. - Save / Don't Save / Cancel before closing or quitting with unsaved documents (tabs, Close, Close All, Quit, window close). - Four brightness themes, persistent preferences. - - Preferences that act (#394, in part): Show Tool Tips, Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; Alt/Option-wheel and a trackpad pinch zoom about the pointer either way), Use Precise Cursors; Selection & Anchor Display › Tolerance (objects, anchors and handles), Object Selection by Path Only, Command Click to Select Objects Behind (cycling down the stack), Zoom to Selection, anchor Size, Highlight anchors on mouse over; Type › Number of Recent Fonts (up to 15); Guides & Grid › guide and grid Color and Style (lines or dots) and Grids In Back. Then Snap to Point (its distance, for moves, drawn points and reference points), Move Locked and Hidden Artwork with Artboard, Enable Rubber Band for the Pen and Curvature tools, Handles style, Show handles when multiple anchors are selected, Hide Corner Widget for angles greater than; General › Transform Pattern Tiles (the transforms' and dialogs' Transform Patterns, Align and Distribute, art moved or duplicated with an artboard, and objects moved whole with direct-selected anchors) and Select Same Tint %; Type › Size/Leading, Tracking and Baseline Shift increments (`type.step`, Cmd+Shift+. and Cmd+Shift+, for the size, the Type tool's Alt+arrows), Enable Missing Glyph Protection and Fill New Type Objects With Placeholder Text. Then Smart Guides › Color, Alignment Guides, Anchor/Path Labels, Measurement Labels, Transform Tools, Object Highlighting (with Smart Guides on) and Snapping Tolerance (4 px by default, for every smart guide snap), then Construction Guides and their Angles (#506: a drawn point near a line at one of those angles through the point it leaves from lands on it). Then Type › Type Object Selection by Path Only (type is picked on the baseline of any of its lines, area type's frame or the path of type on a path, no longer anywhere in its bounds; the Type tools and the Eyedropper still take a click among the characters). Then General › Anti-aliased Artwork (off, the canvas draws the art with hard edges). Then Guides & Grid › Show Pixel Grid (Above 600% Zoom), a line at every document pixel over the art in Pixel Preview, turning with a rotated view as the document grid now does too. Then General › Show The Home Screen When No Documents Are Open (off, closing the last document leaves an empty window; the Home button still opens the screen). Then Units › Numbers Without Units Are Points (on by default: a number typed with no unit into a field in picas is read in points, a typed unit still wins; dimmed unless a unit is Picas). Then User Interface › Auto-Collapse Iconic Panels (off by default: on, a click away from a panel popped out of the icon column puts it away). Then Performance › Animated Zoom (on, with GPU Performance, which it needs and which dims it when off: the Zoom tool dragged sideways zooms about the press, right in and left out, and held still zooms on, Alt out; off, a drag zooms to the area dragged across, now drawn as a marquee), User Interface › Large Tabs (taller document tabs) and Scale Cursor Proportional to UI (off, the tool cursors keep their size whatever the UI Scaling), and File Handling › Display Bitmaps as Anti-aliased Images in Pixel Preview (off by default: Pixel Preview draws images with each pixel taking its nearest image pixel). Type › Auto Size New Area Type works too (new area type gets Auto Size). They reach agents too (`prefs.set`, then `pointer_gesture` or `press_key`; `ui.wheel` on the control channel). Then Smart Guides › Spacing Guides (moves space the selection evenly with its row or column, see Drawing aids). Then Hyphenation › Exceptions (comma-separated words that never break, or break only at their hyphens; `prefs.set` and restore push them into the hyphenator, #394). Then Type › Show Font Names in English (off, font menus label a family by its native-language name when the font has one; documents still use the English family, #394). Then General › Display Print Size at 100% Zoom (off by default: View › Actual Size is one document point per screen point; on, one document inch fills 96 screen points, an inch at the reference density display scaling is set against; `view.actualSize` / `prefs.set`, #394). Then Guides & Grid › Gridline every and Subdivisions (changing either sets the grid of every open document; new and opened documents take that grid too, except a native file and the native document an SVG, PDF, `.ai` or EPS file carries, which open with the grid they were saved with; `file.new`, `document.open`, `file.newFromTemplate`, `file.revert`, #394). Then Appearance of Black › On Screen (Display All Blacks Accurately by default: pure CMYK black shows through the CMYK profile, a dark grey; Display All Blacks as Rich Black shows it on the canvas as RGB black, also in CMYK documents drawn by ink planes, while rich blacks, black tints and images show as before; `RenderOptions::rich_black`, #394). Then Appearance of Black › Printing / Exporting (Output All Blacks Accurately by default; Output All Blacks as Rich Black draws pure CMYK black as RGB black in RGB raster exports and Print as Bitmap; CMYK ink exports and the document stay unchanged; `prefs.blackOutput` / `richBlack`, #394). Then Units › Identify Objects By (Object Name by default: Layers, Variables and Layer Options show and edit free names; XML ID shows and edits AI-style XML ids, `_` for a space and `_xHH_` escapes, and `layer.setProps` / rename refuse invalid ids; the document still stores the object name, #394). Then Type › Highlight Alternate Glyphs (off by default: on, pink behind OpenType substitutions on screen — ligatures, contextual forms; Document Setup's Highlight Substituted Fonts / Glyphs stay separate; `prefs.set`, #394). About 14 stored options still do nothing yet (the rest of #394: East Asian Type units, Open Documents As Tabs, Real-time Drawing and Editing, GPU Performance beyond Animated Zoom, hyphenation language, scratch disks, the legacy clipboard, Touch Workspace). - - Preferences that act (#394, in part): Show Tool Tips, Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; Alt/Option-wheel and a trackpad pinch zoom about the pointer either way), Use Precise Cursors; Selection & Anchor Display › Tolerance (objects, anchors and handles), Object Selection by Path Only, Command Click to Select Objects Behind (cycling down the stack), Zoom to Selection, anchor Size, Highlight anchors on mouse over; Type › Number of Recent Fonts (up to 15); Guides & Grid › guide and grid Color and Style (lines or dots) and Grids In Back. Then Snap to Point (its distance, for moves, drawn points and reference points), Move Locked and Hidden Artwork with Artboard, Enable Rubber Band for the Pen and Curvature tools, Handles style, Show handles when multiple anchors are selected, Hide Corner Widget for angles greater than; General › Transform Pattern Tiles (the transforms' and dialogs' Transform Patterns, Align and Distribute, art moved or duplicated with an artboard, and objects moved whole with direct-selected anchors) and Select Same Tint %; Type › Size/Leading, Tracking and Baseline Shift increments (`type.step`, Cmd+Shift+. and Cmd+Shift+, for the size, the Type tool's Alt+arrows), Enable Missing Glyph Protection and Fill New Type Objects With Placeholder Text. Then Smart Guides › Color, Alignment Guides, Anchor/Path Labels, Measurement Labels, Transform Tools, Object Highlighting (with Smart Guides on) and Snapping Tolerance (4 px by default, for every smart guide snap), then Construction Guides and their Angles (#506: a drawn point near a line at one of those angles through the point it leaves from lands on it). Then Type › Type Object Selection by Path Only (type is picked on the baseline of any of its lines, area type's frame or the path of type on a path, no longer anywhere in its bounds; the Type tools and the Eyedropper still take a click among the characters). Then General › Anti-aliased Artwork (off, the canvas draws the art with hard edges). Then Guides & Grid › Show Pixel Grid (Above 600% Zoom), a line at every document pixel over the art in Pixel Preview, turning with a rotated view as the document grid now does too. Then General › Show The Home Screen When No Documents Are Open (off, closing the last document leaves an empty window; the Home button still opens the screen). Then Units › Numbers Without Units Are Points (on by default: a number typed with no unit into a field in picas is read in points, a typed unit still wins; dimmed unless a unit is Picas). Then User Interface › Auto-Collapse Iconic Panels (off by default: on, a click away from a panel popped out of the icon column puts it away). Then Performance › Animated Zoom (on, with GPU Performance, which it needs and which dims it when off: the Zoom tool dragged sideways zooms about the press, right in and left out, and held still zooms on, Alt out; off, a drag zooms to the area dragged across, now drawn as a marquee), User Interface › Large Tabs (taller document tabs) and Scale Cursor Proportional to UI (off, the tool cursors keep their size whatever the UI Scaling), and File Handling › Display Bitmaps as Anti-aliased Images in Pixel Preview (off by default: Pixel Preview draws images with each pixel taking its nearest image pixel). Type › Auto Size New Area Type works too (new area type gets Auto Size). They reach agents too (`prefs.set`, then `pointer_gesture` or `press_key`; `ui.wheel` on the control channel). Then Smart Guides › Spacing Guides (moves space the selection evenly with its row or column, see Drawing aids). Then Hyphenation › Exceptions (comma-separated words that never break, or break only at their hyphens; `prefs.set` and restore push them into the hyphenator, #394). Then Type › Show Font Names in English (off, font menus label a family by its native-language name when the font has one; documents still use the English family, #394). Then General › Display Print Size at 100% Zoom (off by default: View › Actual Size is one document point per screen point; on, one document inch fills 96 screen points, an inch at the reference density display scaling is set against; `view.actualSize` / `prefs.set`, #394). Then Guides & Grid › Gridline every and Subdivisions (changing either sets the grid of every open document; new and opened documents take that grid too, except a native file and the native document an SVG, PDF, `.ai` or EPS file carries, which open with the grid they were saved with; `file.new`, `document.open`, `file.newFromTemplate`, `file.revert`, #394). Then Appearance of Black › On Screen (Display All Blacks Accurately by default: pure CMYK black shows through the CMYK profile, a dark grey; Display All Blacks as Rich Black shows it on the canvas as RGB black, also in CMYK documents drawn by ink planes, while rich blacks, black tints and images show as before; `RenderOptions::rich_black`, #394). Then Appearance of Black › Printing / Exporting (Output All Blacks Accurately by default; Output All Blacks as Rich Black draws pure CMYK black as RGB black in RGB raster exports and Print as Bitmap; CMYK ink exports and the document stay unchanged; `prefs.blackOutput` / `richBlack`, #394). Then Units › Identify Objects By (Object Name by default: Layers, Variables and Layer Options show and edit free names; XML ID shows and edits AI-style XML ids, `_` for a space and `_xHH_` escapes, and `layer.setProps` / rename refuse invalid ids; the document still stores the object name, #394). Then Type › Highlight Alternate Glyphs (on by default: pink behind OpenType substitutions on screen — ligatures, contextual forms; Document Setup's Highlight Substituted Fonts / Glyphs stay separate; `prefs.set`, #394). About 14 stored options still do nothing yet (the rest of #394: East Asian Type units, Open Documents As Tabs, Real-time Drawing and Editing, GPU Performance beyond Animated Zoom, hyphenation language, scratch disks, the legacy clipboard, Touch Workspace). + - Preferences that act (#394, in part): Show Tool Tips, Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; Alt/Option-wheel and a trackpad pinch zoom about the pointer either way), Use Precise Cursors; Selection & Anchor Display › Tolerance (objects, anchors and handles), Object Selection by Path Only, Command Click to Select Objects Behind (cycling down the stack), Zoom to Selection, anchor Size, Highlight anchors on mouse over; Type › Number of Recent Fonts (up to 15); Guides & Grid › guide and grid Color and Style (lines or dots) and Grids In Back. Then Snap to Point (its distance, for moves, drawn points and reference points), Move Locked and Hidden Artwork with Artboard, Enable Rubber Band for the Pen and Curvature tools, Handles style, Show handles when multiple anchors are selected, Hide Corner Widget for angles greater than; General › Transform Pattern Tiles (the transforms' and dialogs' Transform Patterns, Align and Distribute, art moved or duplicated with an artboard, and objects moved whole with direct-selected anchors) and Select Same Tint %; Type › Size/Leading, Tracking and Baseline Shift increments (`type.step`, Cmd+Shift+. and Cmd+Shift+, for the size, the Type tool's Alt+arrows), Enable Missing Glyph Protection and Fill New Type Objects With Placeholder Text. Then Smart Guides › Color, Alignment Guides, Anchor/Path Labels, Measurement Labels, Transform Tools, Object Highlighting (with Smart Guides on) and Snapping Tolerance (4 px by default, for every smart guide snap), then Construction Guides and their Angles (#506: a drawn point near a line at one of those angles through the point it leaves from lands on it). Then Type › Type Object Selection by Path Only (type is picked on the baseline of any of its lines, area type's frame or the path of type on a path, no longer anywhere in its bounds; the Type tools and the Eyedropper still take a click among the characters). Then General › Anti-aliased Artwork (off, the canvas draws the art with hard edges). Then Guides & Grid › Show Pixel Grid (Above 600% Zoom), a line at every document pixel over the art in Pixel Preview, turning with a rotated view as the document grid now does too. Then General › Show The Home Screen When No Documents Are Open (off, closing the last document leaves an empty window; the Home button still opens the screen). Then Units › Numbers Without Units Are Points (on by default: a number typed with no unit into a field in picas is read in points, a typed unit still wins; dimmed unless a unit is Picas). Then User Interface › Auto-Collapse Iconic Panels (off by default: on, a click away from a panel popped out of the icon column puts it away). Then Performance › Animated Zoom (on, with GPU Performance, which it needs and which dims it when off: the Zoom tool dragged sideways zooms about the press, right in and left out, and held still zooms on, Alt out; off, a drag zooms to the area dragged across, now drawn as a marquee), User Interface › Large Tabs (taller document tabs) and Scale Cursor Proportional to UI (off, the tool cursors keep their size whatever the UI Scaling), and File Handling › Display Bitmaps as Anti-aliased Images in Pixel Preview (off by default: Pixel Preview draws images with each pixel taking its nearest image pixel). Type › Auto Size New Area Type works too (new area type gets Auto Size). They reach agents too (`prefs.set`, then `pointer_gesture` or `press_key`; `ui.wheel` on the control channel). Then Smart Guides › Spacing Guides (moves space the selection evenly with its row or column, see Drawing aids). Then Hyphenation › Exceptions (comma-separated words that never break, or break only at their hyphens; `prefs.set` and restore push them into the hyphenator, #394). Then Type › Show Font Names in English (off, font menus label a family by its native-language name when the font has one; documents still use the English family, #394). Then General › Display Print Size at 100% Zoom (off by default: View › Actual Size is one document point per screen point; on, one document inch fills 96 screen points, an inch at the reference density display scaling is set against; `view.actualSize` / `prefs.set`, #394). Then Guides & Grid › Gridline every and Subdivisions (changing either sets the grid of every open document; new and opened documents take that grid too, except a native file and the native document an SVG, PDF, `.ai` or EPS file carries, which open with the grid they were saved with; `file.new`, `document.open`, `file.newFromTemplate`, `file.revert`, #394). Then Appearance of Black › On Screen (Display All Blacks Accurately by default: pure CMYK black shows through the CMYK profile, a dark grey; Display All Blacks as Rich Black shows it on the canvas as RGB black, also in CMYK documents drawn by ink planes, while rich blacks, black tints and images show as before; `RenderOptions::rich_black`, #394). Then Appearance of Black › Printing / Exporting (Output All Blacks Accurately by default; Output All Blacks as Rich Black draws pure CMYK black as RGB black in RGB raster exports and Print as Bitmap; CMYK ink exports and the document stay unchanged; `prefs.blackOutput` / `richBlack`, #394). Then Units › Identify Objects By (Object Name by default: Layers, Variables and Layer Options show and edit free names; XML ID shows and edits AI-style XML ids, `_` for a space and `_xHH_` escapes, and `layer.setProps` / rename refuse invalid ids; the document still stores the object name, #394). Then Type › Highlight Alternate Glyphs (off by default: on, pink behind OpenType substitutions on screen — ligatures, contextual forms; Document Setup's Highlight Substituted Glyphs stays separate; `prefs.set`, #394). About 14 stored options still do nothing yet (the rest of #394: East Asian Type units, Open Documents As Tabs, Real-time Drawing and Editing, GPU Performance beyond Animated Zoom, hyphenation language, scratch disks, the legacy clipboard, Touch Workspace). + - Preferences that act (#394, in part): Show Tool Tips, Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; Alt/Option-wheel and a trackpad pinch zoom about the pointer either way), Use Precise Cursors; Selection & Anchor Display › Tolerance (objects, anchors and handles), Object Selection by Path Only, Command Click to Select Objects Behind (cycling down the stack), Zoom to Selection, anchor Size, Highlight anchors on mouse over; Type › Number of Recent Fonts (up to 15); Guides & Grid › guide and grid Color and Style (lines or dots) and Grids In Back. Then Snap to Point (its distance, for moves, drawn points and reference points), Move Locked and Hidden Artwork with Artboard, Enable Rubber Band for the Pen and Curvature tools, Handles style, Show handles when multiple anchors are selected, Hide Corner Widget for angles greater than; General › Transform Pattern Tiles (the transforms' and dialogs' Transform Patterns, Align and Distribute, art moved or duplicated with an artboard, and objects moved whole with direct-selected anchors) and Select Same Tint %; Type › Size/Leading, Tracking and Baseline Shift increments (`type.step`, Cmd+Shift+. and Cmd+Shift+, for the size, the Type tool's Alt+arrows), Enable Missing Glyph Protection and Fill New Type Objects With Placeholder Text. Then Smart Guides › Color, Alignment Guides, Anchor/Path Labels, Measurement Labels, Transform Tools, Object Highlighting (with Smart Guides on) and Snapping Tolerance (4 px by default, for every smart guide snap), then Construction Guides and their Angles (#506: a drawn point near a line at one of those angles through the point it leaves from lands on it). Then Type › Type Object Selection by Path Only (type is picked on the baseline of any of its lines, area type's frame or the path of type on a path, no longer anywhere in its bounds; the Type tools and the Eyedropper still take a click among the characters). Then General › Anti-aliased Artwork (off, the canvas draws the art with hard edges). Then Guides & Grid › Show Pixel Grid (Above 600% Zoom), a line at every document pixel over the art in Pixel Preview, turning with a rotated view as the document grid now does too. Then General › Show The Home Screen When No Documents Are Open (off, closing the last document leaves an empty window; the Home button still opens the screen). Then Units › Numbers Without Units Are Points (on by default: a number typed with no unit into a field in picas is read in points, a typed unit still wins; dimmed unless a unit is Picas). Then User Interface › Auto-Collapse Iconic Panels (off by default: on, a click away from a panel popped out of the icon column puts it away). Then Performance › Animated Zoom (on, with GPU Performance, which it needs and which dims it when off: the Zoom tool dragged sideways zooms about the press, right in and left out, and held still zooms on, Alt out; off, a drag zooms to the area dragged across, now drawn as a marquee), User Interface › Large Tabs (taller document tabs) and Scale Cursor Proportional to UI (off, the tool cursors keep their size whatever the UI Scaling), and File Handling › Display Bitmaps as Anti-aliased Images in Pixel Preview (off by default: Pixel Preview draws images with each pixel taking its nearest image pixel). Type › Auto Size New Area Type works too (new area type gets Auto Size). They reach agents too (`prefs.set`, then `pointer_gesture` or `press_key`; `ui.wheel` on the control channel). Then Smart Guides › Spacing Guides (moves space the selection evenly with its row or column, see Drawing aids). Then Hyphenation › Exceptions (comma-separated words that never break, or break only at their hyphens; `prefs.set` and restore push them into the hyphenator, #394). Then Type › Show Font Names in English (off, font menus label a family by its native-language name when the font has one; documents still use the English family, #394). Then General › Display Print Size at 100% Zoom (off by default: View › Actual Size is one document point per screen point; on, one document inch fills 96 screen points, an inch at the reference density display scaling is set against; `view.actualSize` / `prefs.set`, #394). Then Guides & Grid › Gridline every and Subdivisions (changing either sets the grid of every open document; new and opened documents take that grid too, except a native file and the native document an SVG, PDF, `.ai` or EPS file carries, which open with the grid they were saved with; `file.new`, `document.open`, `file.newFromTemplate`, `file.revert`, #394). Then Appearance of Black › On Screen (Display All Blacks Accurately by default: pure CMYK black shows through the CMYK profile, a dark grey; Display All Blacks as Rich Black shows it on the canvas as RGB black, also in CMYK documents drawn by ink planes, while rich blacks, black tints and images show as before; `RenderOptions::rich_black`, #394). Then Appearance of Black › Printing / Exporting (Output All Blacks Accurately by default; Output All Blacks as Rich Black draws pure CMYK black as RGB black in RGB raster exports and Print as Bitmap; CMYK ink exports and the document stay unchanged; `prefs.blackOutput` / `richBlack`, #394). Then Units › Identify Objects By (Object Name by default: Layers, Variables and Layer Options show and edit free names; XML ID shows and edits AI-style XML ids, `_` for a space and `_xHH_` escapes, and `layer.setProps` / rename refuse invalid ids; the document still stores the object name, #394). Then Type › Highlight Alternate Glyphs (on by default: pink behind OpenType substitutions on screen — ligatures, contextual forms; Document Setup's Highlight Substituted Glyphs stays separate; `prefs.set`, #394). About 14 stored options still do nothing yet (the rest of #394: East Asian Type units, Open Documents As Tabs, Real-time Drawing and Editing, GPU Performance beyond Animated Zoom, hyphenation language, scratch disks, the legacy clipboard, Touch Workspace). - The desktop app keeps the Color panel's Show Options or Hide Options between sessions, and `ui.colorPanel` sets its mode and options as its panel menu does. - Preferences that act (#394, in part): Show Tool Tips, Double Click To Isolate, Zoom with Mouse Wheel (the wheel zooms about the pointer, Shift-wheel scrolls up and down, Cmd/Ctrl-wheel sideways; Alt/Option-wheel and a trackpad pinch zoom about the pointer either way), Use Precise Cursors; Selection & Anchor Display › Tolerance (objects, anchors and handles), Object Selection by Path Only, Command Click to Select Objects Behind (cycling down the stack), Zoom to Selection, anchor Size, Highlight anchors on mouse over; Type › Number of Recent Fonts (up to 15); Guides & Grid › guide and grid Color and Style (lines or dots) and Grids In Back. Then Snap to Point (its distance, for moves, drawn points and reference points), Move Locked and Hidden Artwork with Artboard, Enable Rubber Band for the Pen and Curvature tools, Handles style, Show handles when multiple anchors are selected, Hide Corner Widget for angles greater than; General › Transform Pattern Tiles (the transforms' and dialogs' Transform Patterns, Align and Distribute, art moved or duplicated with an artboard, and objects moved whole with direct-selected anchors) and Select Same Tint %; Type › Size/Leading, Tracking and Baseline Shift increments (`type.step`, Cmd+Shift+. and Cmd+Shift+, for the size, the Type tool's Alt+arrows), Enable Missing Glyph Protection and Fill New Type Objects With Placeholder Text. Then Smart Guides › Color, Alignment Guides, Anchor/Path Labels, Measurement Labels, Transform Tools, Object Highlighting (with Smart Guides on) and Snapping Tolerance (4 px by default, for every smart guide snap), then Construction Guides and their Angles (#506: a drawn point near a line at one of those angles through the point it leaves from lands on it). Then Type › Type Object Selection by Path Only (type is picked on the baseline of any of its lines, area type's frame or the path of type on a path, no longer anywhere in its bounds; the Type tools and the Eyedropper still take a click among the characters). Then General › Anti-aliased Artwork (off, the canvas draws the art with hard edges). Then Guides & Grid › Show Pixel Grid (Above 600% Zoom), a line at every document pixel over the art in Pixel Preview, turning with a rotated view as the document grid now does too. Then General › Show The Home Screen When No Documents Are Open (off, closing the last document leaves an empty window; the Home button still opens the screen). Then Units › Numbers Without Units Are Points (on by default: a number typed with no unit into a field in picas is read in points, a typed unit still wins; dimmed unless a unit is Picas). Then User Interface › Auto-Collapse Iconic Panels (off by default: on, a click away from a panel popped out of the icon column puts it away). Then Performance › Animated Zoom (on, with GPU Performance, which it needs and which dims it when off: the Zoom tool dragged sideways zooms about the press, right in and left out, and held still zooms on, Alt out; off, a drag zooms to the area dragged across, now drawn as a marquee), User Interface › Large Tabs (taller document tabs) and Scale Cursor Proportional to UI (off, the tool cursors keep their size whatever the UI Scaling), and File Handling › Display Bitmaps as Anti-aliased Images in Pixel Preview (off by default: Pixel Preview draws images with each pixel taking its nearest image pixel). Type › Auto Size New Area Type works too (new area type gets Auto Size). They reach agents too (`prefs.set`, then `pointer_gesture` or `press_key`; `ui.wheel` on the control channel). Then Smart Guides › Spacing Guides (moves space the selection evenly with its row or column, see Drawing aids). Then Hyphenation › Exceptions (comma-separated words that never break, or break only at their hyphens; `prefs.set` and restore push them into the hyphenator, #394). Then Type › Show Font Names in English (off, font menus label a family by its native-language name when the font has one; documents still use the English family, #394). Then General › Display Print Size at 100% Zoom (off by default: View › Actual Size is one document point per screen point; on, one document inch fills 96 screen points, an inch at the reference density display scaling is set against; `view.actualSize` / `prefs.set`, #394). Then Guides & Grid › Gridline every and Subdivisions (changing either sets the grid of every open document; new and opened documents take that grid too, except a native file and the native document an SVG, PDF, `.ai` or EPS file carries, which open with the grid they were saved with; `file.new`, `document.open`, `file.newFromTemplate`, `file.revert`, #394). Then Appearance of Black › On Screen (Display All Blacks Accurately by default: pure CMYK black shows through the CMYK profile, a dark grey; Display All Blacks as Rich Black shows it on the canvas as RGB black, also in CMYK documents drawn by ink planes, while rich blacks, black tints and images show as before; `RenderOptions::rich_black`, #394). Then Appearance of Black › Printing / Exporting (Output All Blacks Accurately by default; Output All Blacks as Rich Black draws pure CMYK black as RGB black in RGB raster exports and Print as Bitmap; CMYK ink exports and the document stay unchanged; `prefs.blackOutput` / `richBlack`, #394). Then Units › Identify Objects By (Object Name by default: Layers, Variables and Layer Options show and edit free names; XML ID shows and edits AI-style XML ids, `_` for a space and `_xHH_` escapes, and `layer.setProps` / rename refuse invalid ids; the document still stores the object name, #394). About 15 stored options still do nothing yet (the rest of #394: Highlight Alternate Glyphs, East Asian Type units, Open Documents As Tabs, Real-time Drawing and Editing, GPU Performance beyond Animated Zoom, hyphenation language, scratch disks, the legacy clipboard, Touch Workspace). + - Preferences › Type › Highlight Substituted Fonts (on by default) paints pink behind text drawn in a substitute for a missing font in every document, and turning it off removes that highlight everywhere (`prefs.set highlightSubstitutedFonts`); Document Setup keeps Highlight Substituted Glyphs ([help](https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/edit-text-without-replacing-missing-fonts.html)). - Dialogs move by dragging their heading (the band down from the top edge, as a title bar), stay on screen and keep where they were moved for the session; the form dialogs' choices are dropdowns (Offset Path's Joins, Average's and Shear's and Reflect's axis), and Offset Path lists Offset, Joins, Miter Limit and previews (#423). - Dialogs open with their first field focused and its value selected: type a value and press Enter (Move, Rotate, Scale…). A dialog opened from a menu takes the keyboard focus every time it opens, not only the first (#1367), and one whose first field starts greyed out (Blend Options on Smooth Color, Layers Panel Options) gives the focus to that field once it is enabled (#1395). Number fields in panels, the Control bar and dialogs (preferences too) step with Up/Down, or with the mouse wheel over the focused field (#485: a notch a step; the panel under it then stays put, and an unfocused field leaves the wheel to scrolling) (Shift: ten, Ctrl/Cmd: a tenth of their unit), applied at once. - A plain step of a number field (Up/Down or a wheel notch) goes to the next whole unit in its direction and a Ctrl/Cmd step goes to the next tenth, so 5.1 mm steps up to 6 mm and down to 5 mm (#2881). A Shift step adds or takes ten and keeps the fraction. @@ -1030,7 +1031,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Glass's Load Texture:** Effect › Distort › Glass… has a Load Texture… button that reads a PNG, JPEG, GIF, WebP, TIFF, BMP, Targa, PSD or PSB file and adds Loaded Texture to the Texture menu (`textureImage`: `{path}` or `{name?, dataBase64}`, stored in the effect and saved with the document as a grayscale PNG in base64). The texture lies one image pixel per point at Scaling 100%, centered on the object and repeated; each pixel's luma is the surface's height, from 0 for black to 1 pt for white, and a transparent pixel counts as middle gray. Scaling and Invert act on it as on the built-in textures. A Glass saved without `textureImage` draws and saves as before. -- **Raster effects after document and view changes:** a cached drop shadow, outer glow or filtered content is made again when something its content reads from outside the object changes: a pattern's art or tiling, a symbol shown inside another symbol, a brush definition, an image's pixels, the raster resolution (De-Interlace), Page Knockout Group, Document Setup's substitution highlights, a layer's Dim Images, the color settings, the fonts, Pixel Preview, Anti-aliased Artwork, Overprint Preview and isolation mode. A thumbnail under Color Halftone or Mezzotint is made again when the document's color mode changes. Raster effects (blur, shadow, glow, grain) drawn after a pattern, radial or freeform gradient fill export at their place and size (#1541, fixes #1536). +- **Raster effects after document and view changes:** a cached drop shadow, outer glow or filtered content is made again when something its content reads from outside the object changes: a pattern's art or tiling, a symbol shown inside another symbol, a brush definition, an image's pixels, the raster resolution (De-Interlace), Page Knockout Group, the substitution highlights (Highlight Substituted Fonts and Highlight Alternate Glyphs in Preferences › Type, Highlight Substituted Glyphs in Document Setup), a layer's Dim Images, the color settings, the fonts, Pixel Preview, Anti-aliased Artwork, Overprint Preview and isolation mode. A thumbnail under Color Halftone or Mezzotint is made again when the document's color mode changes. Raster effects (blur, shadow, glow, grain) drawn after a pattern, radial or freeform gradient fill export at their place and size (#1541, fixes #1536). - **Drop Shadow's Darkness:** Effect › Stylize › Drop Shadow… has Color and Darkness as radio buttons, each with its field, and the Appearance panel's inline editor has the two radio buttons in its Shade row (`shade`: `color` or `darkness`; `darkness`: 0 to 100%, 100 by default). Color is the default, and a shadow saved without these keys draws as before. With Darkness the shadow is the object's own colors with black added: in a CMYK document their cyan, magenta and yellow stay and their black K becomes K + darkness × (1 − K), and in an RGB document each channel is multiplied by 1 − darkness. Darkness 0% gives the object's color; 100% gives black in an RGB document and keeps the colors under 100% black in a CMYK one. SVG export writes the object blurred, moved and darkened with `feComponentTransfer` (screen colors, in a CMYK document too), and SVG import reads it back; CSS export leaves the shadow out and marks the object unsupported. When a `.ai`, EPS or PDF opens from its editing data, a Drop Shadow with Darkness in an object's art style is read as one with Color is, with the shadow's Darkness percentage (100% when the style gives none). Behavior reference: Illustrator CC Help (2014, [PDF](https://help.adobe.com/archive/en/illustrator/cc/2014/illustrator_reference.pdf)), page 428 (PDF page 434).