Merge #2453: Font menu: a name the interface fonts cannot draw shows in English

This commit is contained in:
dotCooCoo
2026-10-25 20:17:15 -07:00
2 changed files with 69 additions and 2 deletions
+23 -2
View File
@@ -702,10 +702,11 @@ pub(super) fn font_picker(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: egui:
let resp = ui.add_sized([row_width, 24.0], egui::Button::selectable(index == state.selected, ""));
resp.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, ui.is_enabled(), index == state.selected, &r.display));
let label_rect = Rect::from_min_size(resp.rect.min + vec2(5.0, 0.0), vec2(label_width - 5.0, resp.rect.height()));
let name = drawn_font_name(ui.ctx(), r);
ui.painter().with_clip_rect(label_rect.intersect(ui.clip_rect())).text(
label_rect.left_center(),
Align2::LEFT_CENTER,
&r.display,
name,
Tokens::ui(12.0),
t.text,
);
@@ -739,7 +740,7 @@ pub(super) fn font_picker(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: egui:
if resp.clicked() {
chosen = Some(r.family.clone());
}
resp.on_hover_text(&r.display);
resp.on_hover_text(name);
}
});
});
@@ -886,6 +887,13 @@ fn align_glyph(p: &egui::Painter, r: Rect, op: &str, distribute: bool, t: &Token
}
}
/// The name a font menu row draws: the font's own-language name when the interface fonts have a
/// glyph for each of its characters, else its English family name. A Simplified Chinese name
/// such as 微软雅黑 can have characters that the Han face of an English interface lacks.
fn drawn_font_name<'a>(ctx: &egui::Context, row: &'a effectcraft_engine::FontRow) -> &'a str {
if ctx.fonts_mut(|f| f.has_glyphs(&Tokens::ui(12.0), &row.display)) { &row.display } else { &row.family }
}
/// Where a font's "Sample" outlines (`lines`, baseline at y = 0) go in `room`: `(scale, origin)`,
/// a point `p` landing at `origin + p * scale`. At their natural size on `baseline` when they fit;
/// fonts with large or odd metrics are scaled down (never up) and moved inside, so no preview
@@ -944,4 +952,17 @@ mod tests {
assert!(fit_preview(&[], room, baseline).is_none());
assert!(fit_preview(&[vec![[f32::NAN, 1.0]]], room, baseline).is_none());
}
/// A font menu row draws the font's own-language name when the interface fonts have its
/// glyphs, and the English family name when they do not.
#[test]
fn font_menu_rows_draw_a_name_the_interface_fonts_have() {
let ctx = egui::Context::default();
crate::theme::install(&ctx, &Tokens::for_kind(Default::default()), "en");
ctx.run_ui(Default::default(), |_| {}).textures_delta.clear();
let row = |display: &str| effectcraft_engine::FontRow { family: "Menu Name Test".into(), display: display.into(), recent: false, variable: false };
// U+F1234 is in a private use plane that no interface font maps.
assert_eq!(drawn_font_name(&ctx, &row("Menu \u{F1234}")), "Menu Name Test");
assert_eq!(drawn_font_name(&ctx, &row("Nom du menu")), "Nom du menu");
}
}
+46
View File
@@ -106,6 +106,52 @@ fn font_search_preserves_edited_text_and_applies_only_to_selected_characters() {
assert_eq!(doc.style_at(8).font, "Inter");
}
/// A font file of just `name`: the family and Regular in English, and the family again in
/// Simplified Chinese (language 0x804).
fn font_with_native_name(family: &str, native: &str) -> Vec<u8> {
let u16s = |v: &[u16]| -> Vec<u8> { v.iter().flat_map(|x| x.to_be_bytes()).collect() };
let records = [(0x409, 1, family), (0x409, 2, "Regular"), (0x804, 1, native)];
let strings: Vec<Vec<u8>> = records.iter().map(|(_, _, s)| s.encode_utf16().flat_map(|u| u.to_be_bytes()).collect()).collect();
let mut name = u16s(&[0, records.len() as u16, 6 + 12 * records.len() as u16]);
let mut off = 0;
for ((lang, id, _), st) in records.iter().zip(&strings) {
name.extend(u16s(&[3, 1, *lang, *id, st.len() as u16, off]));
off += st.len() as u16;
}
strings.iter().for_each(|st| name.extend(st));
let mut out = u16s(&[1, 0, 1, 0, 0, 0]);
out.extend(b"name");
out.extend([0u8; 4]);
out.extend(28u32.to_be_bytes());
out.extend((name.len() as u32).to_be_bytes());
out.extend(name);
out
}
/// A family whose own-language name has a character no interface font has is listed under its
/// English name: the harness fails on a missing glyph, and the app would draw boxes. Picking the
/// row still applies the family.
#[test]
fn font_menu_lists_a_name_the_interface_fonts_lack_in_english() {
const FAMILY: &str = "Native Name Menu Test";
// U+F1234 is in a private use plane that no interface font maps.
const NATIVE: &str = "中文\u{F1234}字体";
assert_eq!(effectcraft_engine::text::fonts::add_font_data(font_with_native_name(FAMILY, NATIVE)).len(), 1);
assert_eq!(effectcraft_engine::text::fonts::native_families().get(FAMILY).map(String::as_str), Some(NATIVE));
let mut s = Session::default();
s.execute("comp.new", json!({"width": 320, "height": 180})).unwrap();
let lid = s.execute("layer.newText", json!({"text": "Native", "font": "Inter"})).unwrap()["layer"].as_u64().unwrap();
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(move |_| EffectcraftApp::new(s));
h.state_mut().show_panel(PanelKind::Character);
h.run_steps(3);
click(&mut h, "character.font");
type_text(&mut h, "native name menu");
let shown: Vec<String> = h.state().auto.query("character.font.option.").iter().map(|e| e.id.clone()).collect();
assert_eq!(shown, vec![format!("character.font.option.{FAMILY}")]);
click(&mut h, &format!("character.font.option.{FAMILY}"));
assert_eq!(layer_doc(&h.state().session, LayerId(lid)).unwrap().font, FAMILY);
}
/// A font file of just `name` (the family, Regular) and an `fvar` table with one axis pinned at
/// 400: a variable font as far as the font menu's filter is concerned.
fn pinned_variable_font(family: &str) -> Vec<u8> {