From d4f06b5a9d6a9828b841315a03b1dcab8396e328 Mon Sep 17 00:00:00 2001 From: Yura Izmailov Date: Tue, 20 Oct 2026 20:43:14 +0200 Subject: [PATCH] UI-FONT: cover the Chinese interface catalog with system fallback faces --- crates/ui-egui/src/i18n.rs | 2 ++ crates/ui-egui/src/i18n/ui.rs | 7 +++++++ crates/ui-egui/src/theme.rs | 12 ++++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 8b8b01b5..ce22433b 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -10,6 +10,8 @@ pub(crate) mod effects; /// Translations for the panels, tooltips and dialogs below the menu bar (see the module docs). mod ui; use effectcraft_engine::menus::{MenuEntry, MenuNode}; +#[cfg(not(target_arch = "wasm32"))] +pub(crate) use ui::catalog_characters; pub(crate) use ui::{set_current, tr, tr_args}; /// Every language with a catalog, in the order Settings ▸ General ▸ Language lists them. diff --git a/crates/ui-egui/src/i18n/ui.rs b/crates/ui-egui/src/i18n/ui.rs index 434f2b79..79a8c8a5 100644 --- a/crates/ui-egui/src/i18n/ui.rs +++ b/crates/ui-egui/src/i18n/ui.rs @@ -60,6 +60,13 @@ fn catalog(language: &str) -> Option<&'static Index> { Some(index.get_or_init(|| rows.iter().copied().collect())) } +/// Characters required by the fixed panel catalog, for system-font coverage selection. +/// Sorted and deduplicated so fallback registration is deterministic. +#[cfg(not(target_arch = "wasm32"))] +pub(crate) fn catalog_characters(language: &str) -> Vec { + catalog(language).into_iter().flat_map(|c| c.values()).flat_map(|s| s.chars()).collect::>().into_iter().collect() +} + /// Translate one interface string: the current language's row for `text`, or `text` itself. pub(crate) fn tr(text: &str) -> &str { catalog(current()).and_then(|c| c.get(text)).copied().unwrap_or(text) diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index f5a0664c..7f9018fc 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -408,7 +408,14 @@ fn install_script_fallbacks(family_fonts: &mut FontDefinitions, language: &str) let cjk = if is_chinese(language) { [han, KANA] } else { [KANA, han] }; let mut names = Vec::new(); let mut seen = Vec::new(); - for &(probe, name) in cjk.iter().chain(SCRIPT_PROBES).chain(std::iter::once(&HANGUL)) { + // A font covering one Han probe may be a sparse subset. Register additional faces + // needed by the actual interface catalog instead of assuming whole-script coverage. + let extra = if is_chinese(language) { crate::i18n::catalog_characters(language) } else { Vec::new() }; + let extra: Vec<_> = extra.into_iter().filter(|ch| *ch >= '\u{2e80}').map(|ch| (ch, "catalog-system")).collect(); + for &(probe, name) in cjk.iter().chain(SCRIPT_PROBES).chain(std::iter::once(&HANGUL)).chain(&extra) { + if name == "catalog-system" && seen.iter().any(|&id| fonts::face(id).has_char(probe)) { + continue; + } let id = fonts::fallback_for(probe, base); if seen.contains(&id) { continue; @@ -422,7 +429,8 @@ fn install_script_fallbacks(family_fonts: &mut FontDefinitions, language: &str) // Use the already-read, parsed bytes rather than reading a font file twice. let mut data = FontData::from_owned(font.data().as_bytes().to_vec()); data.index = face.info.index; - family_fonts.font_data.insert(name.to_string(), Arc::new(data)); + let name = if name == "catalog-system" { format!("{name}-{:x}", probe as u32) } else { name.to_string() }; + family_fonts.font_data.insert(name.clone(), Arc::new(data)); for family in family_fonts.families.values_mut() { family.push(name.to_string()); }