Merge pull request #7052 from dotCooCoo/feat/font-samples-on-white

Font menu: Show samples on white draws each sample in dark type on a white area
This commit is contained in:
BFlat
2026-11-13 13:17:48 +07:00
committed by GitHub
37 changed files with 300 additions and 19 deletions
+1
View File
@@ -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),
+4
View File
@@ -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,
+68 -12
View File
@@ -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<usize>, 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<usize>, 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<Color32>,
}
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);
+47
View File
@@ -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");
}
+1
View File
@@ -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 Разрешаване на метаданни в обикновен текст
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3733,6 +3733,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3787,6 +3787,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3520,6 +3520,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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 Ενεργοποίηση μεταδεδομένων απλού κειμένου
Can't render this file because it is too large.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3725,6 +3725,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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 सादाटेक्स्ट मेटाडाटा सक्षम करें
Can't render this file because it is too large.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3520,6 +3520,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
Can't render this file because it contains an unexpected character in line 2416 and column 5.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1642,6 +1642,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 エンコーディング
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3364,6 +3364,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 შეხების ჟესტების ჩართვა
Can't render this file because it is too large.
+1
View File
@@ -1632,6 +1632,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 인코딩
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3534,6 +3534,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3664,6 +3664,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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -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 Включить метаданные в виде простого текста
Can't render this file because it is too large.
+1
View File
@@ -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
Can't render this file because it is too large.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -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 Увімкнути метадані у вигляді простого тексту
Can't render this file because it is too large.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 204 and column 5.
+1
View File
@@ -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 启用纯文本元数据
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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 啟用純文字中繼資料
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1 -1
View File
@@ -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
}
+5
View File
@@ -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"))) {
+38
View File
@@ -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<egui::Event>| {
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]
+101 -6
View File
@@ -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<egui::Rect>,
tints: Vec<egui::Color32>,
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<crate::theme::Tokens>) -> 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<egui::Event>| {
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");
}
+7
View File
@@ -5656,6 +5656,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.
+1
View File
@@ -1207,6 +1207,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, the text whose inline graphics draw a symbol whose art uses it, and the objects painted with a pattern whose art uses it by a visible fill or stroke above 0% opacity (VectorCraft's own rule; a layer's mask and a swatch that nothing paints with select nothing); Change and Change All leave the type in the mask of a hidden or locked object in its font and count it in `skipped`, and Change does the same with the type of a pattern that only hidden or locked selected objects paint with. The help page (https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/find-and-replace-fonts.html) says the Document list holds all the fonts the document uses. Tests: `crates/engine/src/cmd/fonts.rs`, `crates/engine/src/cmd/addons/cleanup/text/tests.rs`, `crates/engine/src/cmd/docinfo.rs`.