diff --git a/crates/ui-egui/src/panels/text_panels.rs b/crates/ui-egui/src/panels/text_panels.rs index 34dc4cce9..c8dd2b08d 100644 --- a/crates/ui-egui/src/panels/text_panels.rs +++ b/crates/ui-egui/src/panels/text_panels.rs @@ -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"); + } } diff --git a/crates/ui-egui/tests/it/ui_font_search.rs b/crates/ui-egui/tests/it/ui_font_search.rs index 6be69cb7a..42287645d 100644 --- a/crates/ui-egui/tests/it/ui_font_search.rs +++ b/crates/ui-egui/tests/it/ui_font_search.rs @@ -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 { + let u16s = |v: &[u16]| -> Vec { v.iter().flat_map(|x| x.to_be_bytes()).collect() }; + let records = [(0x409, 1, family), (0x409, 2, "Regular"), (0x804, 1, native)]; + let strings: Vec> = 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 = 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 {