UI: a Simplified Chinese interface picks a face with the simplified hanzi (fixes #1141)

The Chinese UI fallback was chosen by probing 文, which Japanese faces
cover too. Fallback takes a face already loaded first, so after the
English interface had loaded a Japanese face for kana, switching to
Simplified Chinese reused it, and the simplified-only hanzi (选, 删,
层, 设, 页 ...) drew as boxes. A Simplified Chinese interface now probes
页, as wordcraft does for its interface font (storytold/wordcraft#248).
This commit is contained in:
Marco Arnulfo
2026-10-12 23:38:13 +02:00
parent 470ea7a3af
commit d85601f8eb
+39 -2
View File
@@ -397,7 +397,6 @@ fn install_script_fallbacks(family_fonts: &mut FontDefinitions, language: &str)
// Kana is unambiguously Japanese; Han ideographs are shared, so the order decides which
// language's face draws them.
const KANA: (char, &str) = ('あ', "japanese-system");
const HAN: (char, &str) = ('文', "chinese-system");
// Hangul is probed last: with no Korean font installed the lookup can land on a catch-all
// face (Unifont) that also covers Arabic or Indic text but can't shape it, so the
// script-specific faces must be registered ahead of it. A face already seen is skipped.
@@ -405,7 +404,8 @@ fn install_script_fallbacks(family_fonts: &mut FontDefinitions, language: &str)
// Han text follows the interface language, not the operating system's locale.
fonts::set_cjk_locale(cjk_locale(language));
let base = fonts::resolve("Inter", "Regular").face;
let cjk = if is_chinese(language) { [HAN, KANA] } else { [KANA, HAN] };
let han = (han_probe(language), "chinese-system");
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)) {
@@ -442,6 +442,15 @@ fn cjk_locale(language: &str) -> &str {
}
}
/// The Han character that picks the Chinese fallback face. A Simplified Chinese interface asks for
/// a simplified-only hanzi: a Japanese face covers the shared ideographs (文) but not 页, 选 or 删,
/// and the fallback takes a face already loaded first, so the Japanese face loaded for kana would
/// draw the Chinese interface with boxes (#1141).
#[cfg(not(target_arch = "wasm32"))]
fn han_probe(language: &str) -> char {
if language == "zh-hans" { '页' } else { '文' }
}
/// Whether `language` is one of the Chinese catalogs.
#[cfg(not(target_arch = "wasm32"))]
fn is_chinese(language: &str) -> bool {
@@ -583,6 +592,34 @@ mod script_font_tests {
}
}
/// #1141: switching the interface to Simplified Chinese drew most labels as boxes. The English
/// interface had already loaded a Japanese face for kana, and that face covers the Han probe,
/// so it became the Chinese fallback too, without the simplified-only hanzi.
#[test]
fn simplified_chinese_interface_draws_simplified_hanzi_after_another_language() {
let ctx = egui::Context::default();
let tokens = super::Tokens::for_kind(super::ThemeKind::Dark);
super::install(&ctx, &tokens, "en");
super::install(&ctx, &tokens, "zh-hans");
let mut out = ctx.run_ui(egui::RawInput::default(), |_| {});
out.textures_delta.clear();
let font = egui::FontId::new(13.0, egui::FontFamily::Proportional);
let missing: String = ctx.fonts_mut(|fonts| {
let replacement = fonts.layout_no_wrap("\u{10ffff}".into(), font.clone(), egui::Color32::WHITE).rows[0].glyphs[0].uv_rect;
// Simplified-only hanzi of the zh-hans catalog (选择, 删除, 图层, 设置, 导入, 页面).
"选删层设导页"
.chars()
.filter(|ch| {
let uv = fonts.layout_no_wrap(ch.to_string(), font.clone(), egui::Color32::WHITE).rows[0].glyphs[0].uv_rect;
(uv.min, uv.max) == (replacement.min, replacement.max)
})
.collect()
});
// Asked after the layout: the coverage lookup itself loads a face with these hanzi.
let drawable: String = missing.chars().filter(|&ch| installed(ch)).collect();
assert!(drawable.is_empty(), "an installed face covers {drawable}, but the Simplified Chinese interface draws boxes");
}
/// egui shapes each run of one face: Arabic letters take their joined forms, and an Arabic
/// word reads right to left (its first letter drawn rightmost).
#[test]