From 41b8114865478204eed43611f2590c28e3313592 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 20:45:54 -0800 Subject: [PATCH] Font menu: Show samples on white draws each sample in dark type on a white area MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A new Type preference, Show samples on white (`fontSamplesOnWhite`, off by default), draws each family's sample in every font menu, and in Type → Font in the in-window menu bar, in the Light brightness's text color on a white area, in every interface brightness. A row's white area is drawn while its sample is still rendering, and leaves 2 pt above and below it where a highlighted row's color shows. The family names and the rest of the menu keep the interface's colors. Preferences dims the checkbox while Enable in-menu font previews is off. Docs: docs/mcp.md (prefs), docs/target-app-parity.md evidence line. --- crates/engine/src/cmd/prefscmds.rs | 1 + crates/engine/src/prefs.rs | 4 + crates/ui-egui/src/font_menu.rs | 80 ++++++++++++++--- crates/ui-egui/src/font_menu/tests.rs | 47 ++++++++++ crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/bs.tsv | 1 + crates/ui-egui/src/i18n/ca.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/da.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hi.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ka.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + crates/ui-egui/src/menus/bar.rs | 2 +- crates/ui-egui/src/prefs_dialog.rs | 5 ++ crates/ui-egui/src/prefs_dialog/tests.rs | 38 ++++++++ crates/ui-egui/src/tests/font_menu.rs | 107 +++++++++++++++++++++-- docs/mcp.md | 7 ++ docs/target-app-parity.md | 1 + 37 files changed, 300 insertions(+), 19 deletions(-) diff --git a/crates/engine/src/cmd/prefscmds.rs b/crates/engine/src/cmd/prefscmds.rs index c605cd39f3..a73485f4fa 100644 --- a/crates/engine/src/cmd/prefscmds.rs +++ b/crates/engine/src/cmd/prefscmds.rs @@ -212,6 +212,7 @@ pub const PREF_SPECS: &[PrefSpec] = &[ p!("fontNamesInEnglish", "Type", "Options", "Show Font Names in English", bool), p!("autoSizeAreaType", "Type", "Options", "Auto Size New Area Type", bool), p!("fontPreview", "Type", "Options", "Enable in-menu font previews", bool), + p!("fontSamplesOnWhite", "Type", "Options", "Show samples on white", bool), p!("fontPreviewSize", "Type", "Options", "Font Preview Size", choice(&[("small", "Small"), ("medium", "Medium"), ("large", "Large")])), p!("recentFontsCount", "Type", "Options", "Number of Recent Fonts", int(1, 15)), p!("missingGlyphProtection", "Type", "Options", "Enable Missing Glyph Protection", bool), diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index 52c4b7a4f2..05ef5e84f5 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -104,6 +104,9 @@ pub struct Prefs { pub font_names_in_english: bool, pub auto_size_area_type: bool, pub font_preview: bool, + /// Type › Show samples on white: the font menus draw each family's sample in dark type on a + /// white area in every brightness (off by default). + pub font_samples_on_white: bool, pub font_preview_size: String, pub recent_fonts_count: u32, pub missing_glyph_protection: bool, @@ -427,6 +430,7 @@ impl Default for Prefs { font_names_in_english: true, auto_size_area_type: false, font_preview: true, + font_samples_on_white: false, font_preview_size: s("medium"), recent_fonts_count: 10, missing_glyph_protection: true, diff --git a/crates/ui-egui/src/font_menu.rs b/crates/ui-egui/src/font_menu.rs index 486cfd682f..9a0ca9c1d5 100644 --- a/crates/ui-egui/src/font_menu.rs +++ b/crates/ui-egui/src/font_menu.rs @@ -90,6 +90,9 @@ impl WheelOwner { pub struct MenuLook<'a> { /// Show each family's sample (Preferences › Type › Enable in-menu font previews). pub samples: bool, + /// Preferences › Type › Show samples on white: each sample is drawn in dark type on a white + /// area, whatever the interface's brightness (off by default). + pub on_white: bool, /// Preferences › Type › Show Font Names in English (on by default). pub english_names: bool, /// Row height in points (Font Preview Size). @@ -106,9 +109,9 @@ pub struct MenuLook<'a> { } impl Default for MenuLook<'static> { - /// Samples on, English names, medium rows, no favourites, no wheel. + /// Samples on in the theme's colors, English names, medium rows, no favorites, no wheel. fn default() -> Self { - Self { samples: true, english_names: true, row: ROW, favorites: &[], recent: &[], wheel: false, wheel_owner: None } + Self { samples: true, on_white: false, english_names: true, row: ROW, favorites: &[], recent: &[], wheel: false, wheel_owner: None } } } @@ -121,6 +124,7 @@ impl<'a> MenuLook<'a> { }; Self { samples: app.session.prefs.font_preview, + on_white: app.session.prefs.font_samples_on_white, english_names: app.session.prefs.font_names_in_english, row, favorites: &app.ui.favorite_fonts, @@ -1235,6 +1239,7 @@ fn rows(ui: &mut Ui, range: std::ops::Range, h: Listed<'_>, sample: Optio let current = current.as_str(); let row_h = look.row; let t = Tokens::get(ui.ctx()); + let ink = sample_ink(look.on_white, t.text); let db = vectorcraft_text::FontDb::global(); let favorite = |f: &str| look.favorites.iter().any(|x| x.eq_ignore_ascii_case(f)); let widest = name_width(ui, look.english_names, &rows); @@ -1315,9 +1320,12 @@ fn rows(ui: &mut Ui, range: std::ops::Range, h: Listed<'_>, sample: Optio ui.painter_at(name_rect).text(name_rect.left_center(), egui::Align2::LEFT_CENTER, name, egui::FontId::proportional(NAME_SIZE), t.text); // The sample takes the row's whole height. let sample_rect = egui::Rect::from_min_max(rect.min + vec2(name_w, 0.0), rect.max - vec2(STAR + 2.0, 0.0)); - if !look.samples { - } else if let Some(tex) = sample_texture(ui.ctx(), family, row.style(), sample, sample_rect.height()) { - paint_sample(ui, sample_rect, &tex, t.text); + if look.samples { + // The paper is drawn while the sample is still rendering, and for a family whose sample fails. + let area = sample_area(ui, sample_rect, ink, PAPER_INSET); + if let Some(tex) = sample_texture(ui.ctx(), family, row.style(), sample, area.height()) { + paint_sample(ui, area, &tex, ink); + } } if resp.clicked() && !on_triangle && !on_star { chosen = row.pick(); @@ -1366,19 +1374,67 @@ pub(crate) const MENU_SAMPLE_SIZE: egui::Vec2 = vec2(90.0, 16.0); /// A generic sample of `family` drawn in `slot` (the room a Type → Font item of the in-window /// menu bar keeps for it; the native macOS menu shows names only). -pub(crate) fn menu_item_sample(ui: &Ui, slot: egui::Rect, family: &str) { - if let Some(tex) = sample_texture(ui.ctx(), family, None, None, slot.height()) { - paint_sample(ui, slot, &tex, Tokens::get(ui.ctx()).text_dim); +/// With `on_white` (Show samples on white), the sample is drawn in dark type on a white slot. +pub(crate) fn menu_item_sample(ui: &Ui, slot: egui::Rect, family: &str, on_white: bool) { + let ink = sample_ink(on_white, Tokens::get(ui.ctx()).text_dim); + let area = sample_area(ui, slot, ink, 0.0); + if let Some(tex) = sample_texture(ui.ctx(), family, None, None, area.height()) { + paint_sample(ui, area, &tex, ink); } } -/// Draw sample `tex` (tinted `tint`) at the left of `area`, centred vertically, cut at its right. -fn paint_sample(ui: &Ui, area: egui::Rect, tex: &egui::TextureHandle, tint: Color32) { +/// What a sample is drawn on with Show samples on white. +pub(crate) const SAMPLE_PAPER: Color32 = Color32::WHITE; + +/// How a sample is drawn: in `theme` (an interface text color) on the menu's own background, or, +/// with Show samples on white, in the Light brightness's text color on [`SAMPLE_PAPER`]. +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct SampleInk { + pub ink: Color32, + pub paper: Option, +} + +pub(crate) fn sample_ink(on_white: bool, theme: Color32) -> SampleInk { + if on_white { + SampleInk { ink: Tokens::for_brightness(crate::theme::Brightness::Light).text, paper: Some(SAMPLE_PAPER) } + } else { + SampleInk { ink: theme, paper: None } + } +} + +/// Space between the left edge of a sample's paper and its type. +const PAPER_PAD: f32 = 3.0; + +/// Space above and below a menu row's paper, where the row's highlight color shows. +pub(crate) const PAPER_INSET: f32 = 2.0; + +/// The part of `slot` a sample is drawn in: `slot` itself, or, when `ink` has paper, the paper +/// painted in `slot` with `inset` points left above and below it. +fn sample_area(ui: &Ui, slot: egui::Rect, ink: SampleInk, inset: f32) -> egui::Rect { + let Some(paper) = ink.paper else { return slot }; + let area = slot.shrink2(vec2(0.0, inset)); + if area.is_positive() { + ui.painter_at(slot).rect_filled(area, 2.0, paper); + } + area +} + +/// Draw sample `tex` at the left of `area`, centered vertically, cut at its right; on paper, +/// [`PAPER_PAD`] in from its left edge. +fn paint_sample(ui: &Ui, area: egui::Rect, tex: &egui::TextureHandle, ink: SampleInk) { + let painter = ui.painter_at(area); + let mut start = area.left_center(); + if ink.paper.is_some() { + start.x += PAPER_PAD; + } let size = tex.size_vec2() / ui.ctx().pixels_per_point(); - let at = egui::Rect::from_min_size(area.left_center() - vec2(0.0, size.y * 0.5), size); + let at = egui::Rect::from_min_size(start - vec2(0.0, size.y * 0.5), size); let shown = at.intersect(area); + if !shown.is_positive() { + return; + } let uv = egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2((shown.width() / size.x).clamp(0.0, 1.0), 1.0)); - ui.painter_at(area).image(tex.id(), shown, uv, tint); + painter.image(tex.id(), shown, uv, ink.ink); } type Key = (String, String, String, u32); diff --git a/crates/ui-egui/src/font_menu/tests.rs b/crates/ui-egui/src/font_menu/tests.rs index 69063e8e87..14b203b044 100644 --- a/crates/ui-egui/src/font_menu/tests.rs +++ b/crates/ui-egui/src/font_menu/tests.rs @@ -882,3 +882,50 @@ fn the_variable_filter_lists_only_variable_families() { assert!(lasting.iter().all(|f| has(&again, f)), "{again:?}"); assert!(has(&again, TWO_AXIS_FAMILY) && has(&again, "Source Sans 3") && has(&again, "JetBrains Mono"), "{again:?}"); } + +/// A Type › Font menu item's sample follows Show samples on white: dark type on a white slot, +/// or the interface's dimmed text color with nothing behind it. The slot is white while the +/// sample is still rendering too. +#[test] +fn a_menu_items_sample_follows_show_samples_on_white() { + use crate::theme::{Brightness, Tokens}; + let dark = Tokens::for_brightness(Brightness::Light).text; + for b in Brightness::ALL { + let t = Tokens::for_brightness(b); + for on_white in [false, true] { + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + ctx.data_mut(|d| d.insert_temp(egui::Id::NULL, t)); + let slot = egui::Rect::from_min_size(egui::pos2(40.0, 20.0), super::MENU_SAMPLE_SIZE); + let frame = || { + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| super::menu_item_sample(ui, slot, "Inter", on_white)); + out.textures_delta.clear(); + out.shapes + }; + frame(); + super::wait_for_samples(&ctx); + let shapes = frame(); + let tints: Vec<_> = shapes + .iter() + .filter_map(|s| match &s.shape { + egui::Shape::Mesh(m) if m.texture_id != egui::TextureId::default() => m.vertices.first().map(|v| v.color), + _ => None, + }) + .collect(); + let papers = + shapes.iter().filter(|s| matches!(&s.shape, egui::Shape::Rect(r) if r.fill == super::SAMPLE_PAPER && r.rect == slot)).count(); + assert_eq!(tints, [if on_white { dark } else { t.text_dim }], "{}, on white {on_white}", b.id()); + assert_eq!(papers, usize::from(on_white), "{}, on white {on_white}", b.id()); + } + } + // While a family's sample is still rendering, its slot is already white. + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let slot = egui::Rect::from_min_size(egui::pos2(40.0, 20.0), super::MENU_SAMPLE_SIZE); + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| super::menu_item_sample(ui, slot, "JetBrains Mono", true)); + out.textures_delta.clear(); + let shapes = out.shapes; + super::wait_for_samples(&ctx); + assert!(!shapes.iter().any(|s| matches!(&s.shape, egui::Shape::Mesh(m) if m.texture_id != egui::TextureId::default())), "no sample"); + assert!(shapes.iter().any(|s| matches!(&s.shape, egui::Shape::Rect(r) if r.fill == super::SAMPLE_PAPER && r.rect == slot)), "the slot is white"); +} diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index ca25bdf078..8e8c62b77a 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1009,6 +1009,7 @@ font-technology Variable Променливи Enable Contextual Task Bar Разрешаване на контекстуалната лента със задачи Enable copying of text, images and other content Разрешаване на копирането на текст, изображения и друго съдържание Enable in-menu font previews Разрешаване на прегледи на шрифтове в менюто + Show samples on white Показване на мострите на бял фон Enable Missing Glyph Protection Разрешаване на защита срещу липсващи глифи Enable Opacity Mask Разрешаване на маска за непрозрачност Enable plaintext metadata Разрешаване на метаданни в обикновен текст diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 69fcb871b9..0be79627bc 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -3727,6 +3727,7 @@ theme Appearance Izgled Enable Background Export Omogući izvoz u pozadini Enable Background Save Omogući čuvanje u pozadini Enable in-menu font previews Omogući preglede fontova u meniju + Show samples on white Prikaži uzorke na bijeloj podlozi Enable Missing Glyph Protection Omogući zaštitu od nedostajućih glifova Enable Rubber Band for Omogući gumenu traku za Enable Touch Gestures Omogući pokrete dodirom diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 33df72c253..215e44805f 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -1472,6 +1472,7 @@ antiAlias Sharp Nítid Enable copying of text, images and other content Activa la còpia de text, imatges i altre contingut Enable Guides for 9-Slice Scaling Activa les guies per a l’escalat de 9 talls Enable in-menu font previews Activa les previsualitzacions de tipus de lletra als menús + Show samples on white Mostra les mostres sobre fons blanc Enable Missing Glyph Protection Activa la protecció de glifs absents Enable Opacity Mask Activa la màscara d’opacitat Enable plaintext metadata Activa les metadades en text pla diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index ff48f1ecfc..afe8b502cd 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -3781,6 +3781,7 @@ theme Appearance Motiv Enable Background Export Povolit export na pozadí Enable Background Save Povolit ukládání na pozadí Enable in-menu font previews Povolit náhledy písem v nabídce + Show samples on white Zobrazit ukázky na bílém pozadí Enable Missing Glyph Protection Povolit ochranu chybějících glyfů Enable Rubber Band for Povolit gumovou čáru pro Enable Touch Gestures Povolit dotyková gesta diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 38300ca372..d4fe181dbd 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -3514,6 +3514,7 @@ theme Appearance Udseende Enable Background Export Aktivér eksport i baggrunden Enable Background Save Aktivér lagring i baggrunden Enable in-menu font previews Aktivér skrifttypeforhåndsvisning i menuer + Show samples on white Vis eksempler på hvidt Enable Missing Glyph Protection Aktivér beskyttelse mod manglende glyffer Enable Rubber Band for Aktivér gummibånd for Enable Touch Gestures Aktivér berøringsbevægelser diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 1d3b12f63a..c9a1632227 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1011,6 +1011,7 @@ font-technology Variable Variabel Enable Contextual Task Bar Kontextbezogene Taskleiste aktivieren Enable copying of text, images and other content Kopieren von Text, Bildern und anderen Inhalten zulassen Enable in-menu font previews Schriftvorschau im Menü aktivieren + Show samples on white Muster auf Weiß anzeigen Enable Missing Glyph Protection Schutz vor fehlenden Glyphen aktivieren Enable Opacity Mask Deckkraftmaske aktivieren Enable plaintext metadata Klartext-Metadaten zulassen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 8467b8e831..4972480a40 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -979,6 +979,7 @@ font-technology Variable Μεταβλητές Enable Contextual Task Bar Ενεργοποίηση γραμμής εργασιών βάσει περιβάλλοντος Enable copying of text, images and other content Να επιτρέπεται η αντιγραφή κειμένου, εικόνων και άλλου περιεχομένου Enable in-menu font previews Ενεργοποίηση προεπισκόπησης γραμματοσειρών στα μενού + Show samples on white Εμφάνιση δειγμάτων σε λευκό Enable Missing Glyph Protection Ενεργοποίηση προστασίας από ελλείποντες γλύφους Enable Opacity Mask Ενεργοποίηση μάσκας αδιαφάνειας Enable plaintext metadata Ενεργοποίηση μεταδεδομένων απλού κειμένου diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 194ba2c602..a68ff8baa4 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1032,6 +1032,7 @@ font-technology Variable Variables Enable Contextual Task Bar Activar barra de tareas contextual Enable copying of text, images and other content Permitir copiar texto, imágenes y otro contenido Enable in-menu font previews Activar vista previa de fuentes en el menú + Show samples on white Mostrar muestras sobre blanco Enable Missing Glyph Protection Activar protección de glifos ausentes Enable Opacity Mask Activar máscara de opacidad Enable plaintext metadata Activar metadatos en texto sin formato diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index e36c5e891e..46cf7149a6 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -3719,6 +3719,7 @@ theme Appearance Ulkoasu Enable Background Export Ota taustavienti käyttöön Enable Background Save Ota taustatallennus käyttöön Enable in-menu font previews Ota fonttien esikatselu valikoissa käyttöön + Show samples on white Näytä mallit valkoisella pohjalla Enable Missing Glyph Protection Ota puuttuvien merkkien suojaus käyttöön Enable Rubber Band for Ota kumilankavalinta käyttöön kohteelle Enable Touch Gestures Ota kosketuseleet käyttöön diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 067845f493..1eda56a389 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1027,6 +1027,7 @@ font-technology Variable Variables Enable Contextual Task Bar Activer la barre des tâches contextuelle Enable copying of text, images and other content Autoriser la copie de texte, d’images et d’autres contenus Enable in-menu font previews Activer l’aperçu des polices dans les menus + Show samples on white Afficher les échantillons sur fond blanc Enable Missing Glyph Protection Activer la protection des glyphes manquants Enable Opacity Mask Activer le masque d’opacité Enable plaintext metadata Activer les métadonnées en texte brut diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 165e04ae7d..f2830959d2 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -1623,6 +1623,7 @@ antiAlias Sharp शार्प Enable copying of text, images and other content टेक्स्ट, इमेज और अन्य सामग्री की प्रतिलिपि बनाने में सक्षम करें Enable Guides for 9-Slice Scaling 9 स्लाइस स्केलिंग के लिए गाइड सक्षम करें Enable in-menu font previews इन-मेनू फ़ॉन्ट प्रीव्यू सक्षम करें + Show samples on white नमूने सफ़ेद पृष्ठभूमि पर दिखाएँ Enable Missing Glyph Protection मिसिंग ग्लाइफ संरक्षण सक्षम करें Enable Opacity Mask ओपेसिटी मास्क सक्षम करें Enable plaintext metadata सादाटेक्स्ट मेटाडाटा सक्षम करें diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 2ccf9d57bc..09b856f347 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1308,6 +1308,7 @@ font-technology Variable Változó Enable Contextual Task Bar Helyzetérzékeny műveletsáv bekapcsolása Enable copying of text, images and other content Szöveg, képek és más tartalom másolásának engedélyezése Enable in-menu font previews Betűtípus-előnézet a menüben + Show samples on white Minták fehér alapon Enable Missing Glyph Protection Hiányzó glifák elleni védelem Enable Opacity Mask Átlátszósági maszk bekapcsolása Enable plaintext metadata Titkosítatlan metaadatok engedélyezése diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 16303f38f5..edf5bb4ae5 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -3514,6 +3514,7 @@ theme Appearance Tampilan Enable Background Export Aktifkan ekspor di latar belakang Enable Background Save Aktifkan penyimpanan di latar belakang Enable in-menu font previews Aktifkan pratinjau font dalam menu + Show samples on white Tampilkan sampel di atas putih Enable Missing Glyph Protection Aktifkan perlindungan glyph yang hilang Enable Rubber Band for Aktifkan rubber band untuk Enable Touch Gestures Aktifkan gestur sentuh diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index be83d78b43..b4c799d741 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1041,6 +1041,7 @@ font-technology Variable Variabili Enable Contextual Task Bar Attiva barra attività contestuale Enable copying of text, images and other content Consenti la copia di testo, immagini e altri contenuti Enable in-menu font previews Attiva anteprima dei font nei menu + Show samples on white Mostra campioni su sfondo bianco Enable Missing Glyph Protection Attiva protezione glifi mancanti Enable Opacity Mask Attiva maschera di opacità Enable plaintext metadata Attiva metadati in testo semplice diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index b27c6d2698..4cd21698bf 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -1638,6 +1638,7 @@ antiAlias Sharp シャープ Enable Touch Workspace タッチワークスペースを有効にする Enable copying of text, images and other content テキスト、イメージ、その他のコンテンツのコピーを許可 Enable in-menu font previews メニュー内でフォントプレビューを有効にする + Show samples on white サンプルを白地に表示 Enable plaintext metadata プレーンテキストのメタデータを有効にする Enable text access for screen readers スクリーンリーダー用にテキストアクセスを有効にする Encoding エンコーディング diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 4ba40baab0..f6db9db914 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -3358,6 +3358,7 @@ theme Appearance იერსახე Enable Background Export ფონური ექსპორტის ჩართვა Enable Background Save ფონური შენახვის ჩართვა Enable in-menu font previews შრიფტების წინასწარი ნახვა მენიუში + Show samples on white ნიმუშების ჩვენება თეთრ ფონზე Enable Missing Glyph Protection გამოტოვებული გლიფებისგან დაცვის ჩართვა Enable Rubber Band for რეზინის ზოლის ჩართვა: Enable Touch Gestures შეხების ჟესტების ჩართვა diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index c46ce6b4fa..de81afb41e 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -1628,6 +1628,7 @@ antiAlias Sharp 날카롭게 Enable Touch Workspace 터치 작업 영역 사용 Enable copying of text, images and other content 텍스트, 이미지 및 기타 콘텐츠 복사 허용 Enable in-menu font previews 메뉴에서 글꼴 미리 보기 사용 + Show samples on white 흰색 바탕에 샘플 표시 Enable plaintext metadata 일반 텍스트 메타데이터 사용 Enable text access for screen readers 화면 읽기 프로그램의 텍스트 접근 허용 Encoding 인코딩 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index a19e190ed6..323d3ec16a 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3528,6 +3528,7 @@ theme Appearance Vormgeving Enable Background Export Achtergrondexport inschakelen Enable Background Save Achtergrondopslag inschakelen Enable in-menu font previews Lettertypevoorbeelden in het menu inschakelen + Show samples on white Voorbeelden op wit tonen Enable Missing Glyph Protection Beveiliging tegen ontbrekende glyphs inschakelen Enable Rubber Band for Rubberband inschakelen voor Enable Touch Gestures Touchgebaren inschakelen diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index a232ac4e8d..70b2cdd4b0 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -3658,6 +3658,7 @@ theme Appearance Wygląd Enable Background Export Włącz eksport w tle Enable Background Save Włącz zapis w tle Enable in-menu font previews Włącz podgląd czcionek w menu + Show samples on white Pokaż próbki na białym tle Enable Missing Glyph Protection Włącz ochronę przed brakującymi glifami Enable Rubber Band for Włącz gumkę podglądu dla Enable Touch Gestures Włącz gesty dotykowe diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 68a225625d..a054a6b9f3 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -932,6 +932,7 @@ antiAlias Sharp Nítido Enable Touch Workspace Habilitar Espaço de Trabalho de Toque Enable copying of text, images and other content Habilitar cópia de texto, imagens e outro conteúdo Enable in-menu font previews Habilitar visualizações de fonte no menu + Show samples on white Mostrar amostras sobre branco Enable plaintext metadata Habilitar metadados de texto simples Enable text access for screen readers Habilitar acesso a texto para leitores de tela Encoding Codificação diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index a54d1eed2c..10c7c2ca4d 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1030,6 +1030,7 @@ font-technology Variable Вариативные Enable Contextual Task Bar Включить контекстную панель задач Enable copying of text, images and other content Разрешить копирование текста, изображений и другого содержимого Enable in-menu font previews Включить предварительный просмотр шрифтов в меню + Show samples on white Показывать образцы на белом Enable Missing Glyph Protection Включить защиту отсутствующих глифов Enable Opacity Mask Включить непрозрачную маску Enable plaintext metadata Включить метаданные в виде простого текста diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index d0648b5946..375c0d4341 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -1011,6 +1011,7 @@ font-technology Variable Variabla Enable Contextual Task Bar Aktivera sammanhangsberoende aktivitetsfält Enable copying of text, images and other content Tillåt kopiering av text, bilder och annat innehåll Enable in-menu font previews Aktivera förhandsgranskning av typsnitt i menyer + Show samples on white Visa exempel på vitt Enable Missing Glyph Protection Aktivera skydd mot saknade glyfer Enable Opacity Mask Aktivera opacitetsmask Enable plaintext metadata Aktivera metadata i klartext diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 915a244b43..82ce395fe2 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -1003,6 +1003,7 @@ font-technology Variable Değişken Enable Contextual Task Bar Bağlamsal Görev Çubuğunu Etkinleştir Enable copying of text, images and other content Metin, resim ve diğer içeriklerin kopyalanmasını etkinleştirin Enable in-menu font previews Menü içi yazı tipi önizlemelerini etkinleştir + Show samples on white Örnekleri beyaz üzerinde göster Enable Missing Glyph Protection Eksik Glif Korumasını Etkinleştir Enable Opacity Mask Opaklık Maskesini Etkinleştir Enable plaintext metadata Düz metin meta verilerini etkinleştir diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index e8b5445a50..40ac641066 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1035,6 +1035,7 @@ font-technology Variable Варіативні Enable Contextual Task Bar Увімкнути контекстну панель завдань Enable copying of text, images and other content Дозволити копіювання тексту, зображень та іншого вмісту Enable in-menu font previews Увімкнути попередній перегляд шрифтів у меню + Show samples on white Показувати зразки на білому Enable Missing Glyph Protection Увімкнути захист відсутніх гліфів Enable Opacity Mask Увімкнути маску непрозорості Enable plaintext metadata Увімкнути метадані у вигляді простого тексту diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 51681bfed5..b44a98ca1f 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1198,6 +1198,7 @@ font-technology Variable Biến thiên Enable Contextual Task Bar Bật thanh tác vụ theo ngữ cảnh Enable copying of text, images and other content Bật sao chép văn bản, ảnh và nội dung khác Enable in-menu font previews Bật xem trước phông trong menu + Show samples on white Hiển thị mẫu trên nền trắng Enable Missing Glyph Protection Bật bảo vệ glyph bị thiếu Enable Opacity Mask Bật mặt nạ độ mờ Enable plaintext metadata Bật siêu dữ liệu văn bản thuần diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index d1e4de6bc6..ce930f0fc0 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -973,6 +973,7 @@ antiAlias Sharp 锐利 Enable Contextual Task Bar 启用上下文任务栏 Enable copying of text, images and other content 启用复制文字、图像与其他内容 Enable in-menu font previews 启用菜单内字体预览 + Show samples on white 在白色背景上显示样本 Enable Missing Glyph Protection 启用缺字保护 Enable Opacity Mask 启用不透明度蒙版 Enable plaintext metadata 启用纯文本元数据 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 8e49ed109a..731075937c 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1003,6 +1003,7 @@ font-technology Variable 可變 Enable Contextual Task Bar 啟用快捷工作列 Enable copying of text, images and other content 啟用複製文字、影像與其他內容 Enable in-menu font previews 啟用選單內字體預覽 + Show samples on white 在白色背景上顯示樣本 Enable Missing Glyph Protection 啟用遺失字符保護 Enable Opacity Mask 啟用不透明度遮色片 Enable plaintext metadata 啟用純文字中繼資料 diff --git a/crates/ui-egui/src/menus/bar.rs b/crates/ui-egui/src/menus/bar.rs index 3f34af13a3..575f11f74f 100644 --- a/crates/ui-egui/src/menus/bar.rs +++ b/crates/ui-egui/src/menus/bar.rs @@ -179,7 +179,7 @@ fn render_row(app: &VectorcraftApp, ui: &mut egui::Ui, row: &Row, checks: bool, let button = egui::Button::new(text).right_text(egui::Atom::custom(slot, crate::font_menu::MENU_SAMPLE_SIZE)); let out = ui.add_enabled_ui(row.enabled, |ui| button.atom_ui(ui)).inner; if let Some(rect) = out.rect(slot) { - crate::font_menu::menu_item_sample(ui, rect, family); + crate::font_menu::menu_item_sample(ui, rect, family, app.session.prefs.font_samples_on_white); } out.response } diff --git a/crates/ui-egui/src/prefs_dialog.rs b/crates/ui-egui/src/prefs_dialog.rs index c9662109ad..a6d1828ac1 100644 --- a/crates/ui-egui/src/prefs_dialog.rs +++ b/crates/ui-egui/src/prefs_dialog.rs @@ -519,6 +519,11 @@ fn category_fields(ui: &mut egui::Ui, app: &mut VectorcraftApp, d: &mut Dialog, PrefKind::Bool if sp.key == "animatedZoom" => { ui.add_enabled_ui(d.bool("gpuPerformance"), |ui| bool_row(ui, d, sp.key, sp.label)); } + // Type › Show samples on white draws the samples that Enable in-menu font previews + // shows: dimmed while previews are off. + PrefKind::Bool if sp.key == "fontSamplesOnWhite" => { + ui.add_enabled_ui(d.bool("fontPreview"), |ui| bool_row(ui, d, sp.key, sp.label)); + } // Windows and Linux only: macOS always has the system's title bar, the web has none. PrefKind::Bool if sp.key == "systemTitleBar" => { if cfg!(all(not(target_arch = "wasm32"), not(target_os = "macos"))) { diff --git a/crates/ui-egui/src/prefs_dialog/tests.rs b/crates/ui-egui/src/prefs_dialog/tests.rs index 95fa8085fd..fe8dc72aa3 100644 --- a/crates/ui-egui/src/prefs_dialog/tests.rs +++ b/crates/ui-egui/src/prefs_dialog/tests.rs @@ -962,6 +962,44 @@ fn scroll_sideways_with_cmd_wheel_dims_with_zoom_with_mouse_wheel() { } } +/// Type › Show samples on white: its checkbox is dimmed while Enable in-menu font previews is +/// unchecked in the dialog, and a click on it then leaves the setting as it was. +#[test] +fn show_samples_on_white_dims_without_in_menu_font_previews() { + fn label(s: &egui::Shape, text: &str) -> Option<(egui::Color32, egui::Rect)> { + match s { + egui::Shape::Text(t) if t.galley.text() == text => t.galley.job.sections.first().map(|s| (s.format.color, t.visual_bounding_rect())), + egui::Shape::Vec(v) => v.iter().find_map(|s| label(s, text)), + _ => None, + } + } + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let mut a = app(); + let mut frame = |d: &mut Dialog, events: Vec| { + let raw = egui::RawInput { + screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(600.0, 1400.0))), + events, + ..Default::default() + }; + let mut out = ctx.run_ui(raw, |ui| category_fields(ui, &mut a, d, "Type")); + out.textures_delta.clear(); + out.shapes.iter().find_map(|c| label(&c.shape, "Show samples on white")) + }; + let t = Tokens::get(&ctx); + for (preview, color, what) in [(true, t.text, "enabled while previews are on"), (false, t.text_disabled, "dimmed while they are off")] { + let mut d = Dialog::new("preferences", json!({"fontPreview": preview, "fontSamplesOnWhite": false})); + let (shown, at) = frame(&mut d, vec![]).unwrap_or_else(|| panic!("no label ({what})")); + assert_eq!(shown, color, "{what}"); + let press = + |pressed| egui::Event::PointerButton { pos: at.center(), button: egui::PointerButton::Primary, pressed, modifiers: Default::default() }; + frame(&mut d, vec![egui::Event::PointerMoved(at.center()), press(true)]); + frame(&mut d, vec![press(false)]); + // A click turns it on while it is enabled, and leaves it off while it is dimmed. + assert_eq!(d.fields["fontSamplesOnWhite"], json!(preview), "after a click, {what}"); + } +} + /// 0.5.0 saved `powerSaving` for everyone (its default): it reads as Automatic, while the /// choices this version offers are kept (#502). #[test] diff --git a/crates/ui-egui/src/tests/font_menu.rs b/crates/ui-egui/src/tests/font_menu.rs index 109122308f..47d6258a1d 100644 --- a/crates/ui-egui/src/tests/font_menu.rs +++ b/crates/ui-egui/src/tests/font_menu.rs @@ -961,10 +961,14 @@ fn a_preview_stays_put_while_the_menu_is_still() { } /// What an open font menu drew: each text with its rectangle and clip rectangle, the rectangles of -/// the samples, and the menu's rectangle. +/// the samples, each sample's tint, the filled rectangles with their fill, each text's color, and +/// the menu's rectangle. struct Drawn { texts: Vec<(String, egui::Rect, egui::Rect)>, samples: Vec, + tints: Vec, + fills: Vec<(egui::Rect, egui::Color32)>, + text_colors: Vec<(String, egui::Color32)>, menu: egui::Rect, } @@ -996,14 +1000,22 @@ impl Drawn { /// size `screen`, draws it until it settles and its samples are rendered, and returns what the /// last frame drew. fn open_menu(id: &str, left: f32, width: f32, screen: egui::Vec2) -> Drawn { + open_menu_with(id, left, width, screen, MenuLook::default(), None) +} + +/// [`open_menu`] with the menu's look `look`, in the interface colors `tokens` when given. +fn open_menu_with(id: &str, left: f32, width: f32, screen: egui::Vec2, look: MenuLook, tokens: Option) -> Drawn { let ctx = egui::Context::default(); crate::theme::install_fonts(&ctx); + if let Some(t) = tokens { + ctx.data_mut(|d| d.insert_temp(egui::Id::NULL, t)); + } let field = egui::Rect::from_min_size(egui::pos2(left, 10.0), egui::vec2(width, 30.0)); let frame = |events: Vec| { let input = egui::RawInput { events, screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, screen)), ..Default::default() }; let mut out = ctx.run_ui(input, |ui| { ui.scope_builder(egui::UiBuilder::new().max_rect(field), |ui| { - font_menu(ui, id, "Inter", width, None, MenuLook::default()); + font_menu(ui, id, "Inter", width, None, look); }); }); out.textures_delta.clear(); @@ -1014,21 +1026,33 @@ fn open_menu(id: &str, left: f32, width: f32, screen: egui::Vec2) -> Drawn { frame(vec![]); frame(vec![egui::Event::PointerMoved(click), button(true)]); frame(vec![button(false)]); - for _ in 0..5 { + // With interface colors given, enough frames for the menu to finish fading in, so the + // samples' tints are whole. + for _ in 0..if tokens.is_some() { 15 } else { 5 } { frame(vec![]); } crate::font_menu::wait_for_samples(&ctx); let shapes = frame(vec![]); let mut texts = vec![]; let mut samples = vec![]; + let mut tints = vec![]; + let mut fills = vec![]; + let mut text_colors = vec![]; for s in &shapes { match &s.shape { - egui::Shape::Text(t) => texts.push((t.galley.text().to_string(), egui::Rect::from_min_size(t.pos, t.galley.size()), s.clip_rect)), - egui::Shape::Mesh(m) if m.texture_id != egui::TextureId::default() => samples.push(m.calc_bounds()), + egui::Shape::Text(t) => { + texts.push((t.galley.text().to_string(), egui::Rect::from_min_size(t.pos, t.galley.size()), s.clip_rect)); + text_colors.push((t.galley.text().to_string(), t.fallback_color)); + } + egui::Shape::Mesh(m) if m.texture_id != egui::TextureId::default() => { + samples.push(m.calc_bounds()); + tints.push(m.vertices.first().map_or(egui::Color32::TRANSPARENT, |v| v.color)); + } + egui::Shape::Rect(r) => fills.push((r.rect, r.fill)), _ => {} } } - let mut drawn = Drawn { texts, samples, menu: egui::Rect::NOTHING }; + let mut drawn = Drawn { texts, samples, tints, fills, text_colors, menu: egui::Rect::NOTHING }; let filter = drawn.text(crate::i18n::t("Any Kind")).center(); drawn.menu = ctx.layer_id_at(filter).and_then(|l| ctx.memory(|m| m.area_rect(l.id))).expect("the menu is open"); drawn @@ -1098,3 +1122,74 @@ fn samples_fill_their_rows_height() { assert!((s.height() - row).abs() < 0.5, "a sample {} high in a row {row} high", s.height()); } } + +/// Show samples on white: in every brightness each sample is drawn in the Light brightness's +/// text color on a white area, with the row's highlight above and below it, while the families' +/// names keep the interface's text color. Off +/// (the default), the samples take the interface's text color with nothing white behind them. +#[test] +fn show_samples_on_white_draws_dark_type_on_white_in_every_brightness() { + use crate::theme::{Brightness, Tokens}; + let dark = Tokens::for_brightness(Brightness::Light).text; + let families = FontDb::global().menu_family_list(); + for b in Brightness::ALL { + let t = Tokens::for_brightness(b); + for on_white in [false, true] { + let look = MenuLook { on_white, ..Default::default() }; + let d = open_menu_with(&format!("test-on-white-{}-{on_white}", b.id()), 20.0, 260.0, egui::vec2(1200.0, 800.0), look, Some(t)); + assert!(!d.samples.is_empty(), "{}: samples are drawn", b.id()); + for (sample, tint) in d.samples.iter().zip(&d.tints) { + let on_paper = d.fills.iter().any(|(r, fill)| *fill == egui::Color32::WHITE && r.expand(0.5).contains_rect(*sample)); + if on_white { + assert_eq!(*tint, dark, "{}: a sample on white is dark", b.id()); + assert!(on_paper, "{}: the sample at {sample:?} is on white", b.id()); + } else { + assert_eq!(*tint, t.text, "{}: a sample takes the interface's text color", b.id()); + assert!(!on_paper, "{}: nothing white behind the sample at {sample:?}", b.id()); + } + } + // The current family's row opens highlighted; on white, the highlight shows above and + // below its paper. + let lit = d.fills.iter().find(|(r, fill)| *fill == t.selection && d.menu.contains_rect(*r)).map(|(r, _)| *r); + let lit = lit.unwrap_or_else(|| panic!("{}: a row is highlighted", b.id())); + let paper = d.fills.iter().find(|(r, fill)| *fill == egui::Color32::WHITE && lit.contains_rect(*r)).map(|(r, _)| *r); + if on_white { + let paper = paper.unwrap_or_else(|| panic!("{}: the highlighted row has paper", b.id())); + let inset = crate::font_menu::PAPER_INSET; + assert!( + (paper.top() - lit.top() - inset).abs() < 0.01 && (lit.bottom() - paper.bottom() - inset).abs() < 0.01, + "{}: {paper:?} in {lit:?}", + b.id() + ); + } else { + assert_eq!(paper, None, "{}: no paper in the highlighted row", b.id()); + } + // The rows' names, not the closed field's. + let names: Vec<_> = d + .text_colors + .iter() + .zip(&d.texts) + .filter(|((name, _), (_, r, _))| d.menu.contains_rect(*r) && families.iter().any(|f| f == name)) + .map(|(named, _)| named) + .collect(); + assert!(!names.is_empty(), "{}: families are listed", b.id()); + assert!(names.iter().all(|(_, c)| *c == t.text), "{}: the names keep the interface's text color: {names:?}", b.id()); + } + } +} + +/// Show samples on white is a Type preference, off by default, saved with the other preferences +/// and passed to the font menus. +#[test] +fn show_samples_on_white_is_a_saved_preference_the_menus_follow() { + let mut app = VectorcraftApp::new(Session::new(), crate::Services::default()); + assert!(!MenuLook::of(&app).on_white, "off by default"); + assert!(app.run("prefs.set", json!({"key": "fontSamplesOnWhite", "value": 1})).is_err(), "a value that is not a boolean is refused"); + assert!(!app.session.prefs.font_samples_on_white); + app.run("prefs.set", json!({"key": "fontSamplesOnWhite", "value": true})).unwrap(); + assert!(MenuLook::of(&app).on_white); + let saved: vectorcraft_engine::Prefs = serde_json::from_str(&serde_json::to_string(&app.session.prefs).unwrap()).unwrap(); + assert!(saved.font_samples_on_white, "saved and read back"); + let older: vectorcraft_engine::Prefs = serde_json::from_str("{}").unwrap(); + assert!(!older.font_samples_on_white, "preferences saved before it read as off"); +} diff --git a/docs/mcp.md b/docs/mcp.md index 2c8afc7aea..5fd5aebad6 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -5604,6 +5604,13 @@ Pen's second anchor at (150, 431), in line with the square's centre. Type → Font menus label a family by its native-language name when the font has one. `text.fontList` and `text.setStyle {font}` always use the English (canonical) family name; a document may still name a font by any of its aliases (`ヒラギノ角ゴシック` resolves to Hiragino Sans). +- `fontSamplesOnWhite` (off by default): Preferences › Type › Show samples on white. On, every font menu (the + Character panel, Control bar, Properties panel, Glyphs panel, the Composite Fonts and Dimension Tool Options dialogs, and Type → Font + in the in-window menu bar) draws each family's sample in dark type on a white area in every interface brightness. In + a menu row the white area leaves 2 pt above and below it, where a highlighted row's color shows; the names and the + rest of the menu keep the interface's colors. It has an effect only while `fontPreview` is on, and Preferences dims + its checkbox while previews are off. It is VectorCraft's own option. `prefs.set` refuses a value that is not a + boolean. - `hyphenationExceptions` (empty by default): Preferences › Hyphenation › Exceptions. A comma-separated list of words, 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. diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 1ce5c5fda5..5685690004 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1203,6 +1203,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Manual kerning in right-to-left text:** a manual kerning value on a Hebrew or Arabic character changes the space between it and the next character in reading order, the one drawn to its left, under Auto and Optical kerning. The character before it keeps its place, and the caret between the pair stays beside the next character. The side the space goes on is VectorCraft's own rule. Behavior reference: [Line and character spacing](https://web.archive.org/web/20260916050333/https://helpx.adobe.com/illustrator/using/line-character-spacing.html). Test: `rtl_manual_kerning_spaces_the_character_from_the_next_in_reading_order`. - **Manual kerning on inline graphics:** a manual kerning value on an inline graphic's character spaces it from the next character in reading order by the same distance as on a letter of its run's size, in left-to-right, right-to-left and vertical type; Auto and Optical kerning still apply no font pairs to it. This is VectorCraft's own rule. Test: `manual_kerning_on_an_inline_graphic_spaces_it_like_a_letter`. - **Recent font pickers (#3515):** The shared Character, Properties, Glyphs and Find Font family picker lists recently applied families first, once each, in most-recent order and limited by Preferences › Type › Number of Recent Fonts. Search and filters apply to both sections; unavailable families are omitted. Section headings cannot be selected or previewed. Confirmed character-range font edits update the same saved history; previews and failed edits do not. Requirement: [issue #3515](https://github.com/storytold/vectorcraft/issues/3515). +- **Show samples on white (a VectorCraft addition):** Preferences › Type › Show samples on white (`fontSamplesOnWhite`, off by default) draws each family's sample in every font menu, and in Type → Font in the in-window menu bar, in dark type on a white area in every interface brightness. A highlighted row's color shows above and below the white area, and the names and the rest of the menu keep the interface's colors. The checkbox is dimmed while Enable in-menu font previews is off. Tests: `crates/ui-egui/src/tests/font_menu.rs`, `crates/ui-egui/src/font_menu/tests.rs`, `crates/ui-egui/src/prefs_dialog/tests.rs`. - **Find Font: Replace With Font From:** a dropdown under Replace With Font chooses the list the replacement font comes from: Document lists the families the document uses, once each; Recent lists the recently applied families that are installed (fonts applied with Find Font's Change and Change All included), newest first, as many as Preferences › Type › Number of Recent Fonts allows; System, the default, lists the installed families available in the font menu. The dialog opens on System with Source Sans 3 chosen, or the font menu's first family when Source Sans 3 is not installed. Choosing a list keeps the chosen font when the list has it and it is installed; otherwise Document and Recent choose the list's first installed font, and System chooses Source Sans 3 or the font menu's first family. Change and Change All are unavailable when the list has no installed font (Recent before any font was applied) and when the chosen family is not installed or is not in the shown list. Behavior reference: [Find and replace fonts](https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/find-and-replace-fonts.html) (public documentation). Tests: `crates/ui-egui/src/find_font.rs`. - **Select Text by Font: scope (a VectorCraft addition, #5529):** `select.font` takes `scope`: `"document"` (the default, as before), `{artboards: [index, …]}` or `{layers: [id, …]}` (sublayers included). On an artboard means what All on Active Artboard takes (the bounds overlap it or the center is on it; one shared test, `select::on_artboard`); hidden and locked text stays out as before; a symbol instance counts where the instance is. Type › Find Font…'s Find In dropdown passes the active artboard or the selected layers (else the current layer). Tests: `crates/engine/src/cmd/fonts.rs`, `crates/ui-egui/src/find_font.rs`, `crates/engine/tests/command_sweep.rs`. - **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 and the objects painted with a pattern whose art uses it (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`.