use eframe::egui;
use std::sync::Arc;
fn candidates_for_locale(code: &str) -> &'static [&'static str] {
match code {
"zh" | "zh-cn" => &[
"Microsoft YaHei UI", "Microsoft YaHei",
"PingFang SC", "Noto Sans CJK SC", "Source Han Sans SC",
"WenQuanYi Zen Hei",
],
"zh-tw" | "zh-hk" => &[
"Microsoft JhengHei UI",
"Microsoft JhengHei",
"PingFang TC",
"Noto Sans CJK TC",
"Source Han Sans TC",
],
"ja" => &[
"Yu Gothic UI",
"Yu Gothic",
"Meiryo UI",
"Meiryo",
"Hiragino Sans",
"Hiragino Kaku Gothic ProN",
"Noto Sans CJK JP",
"IPAexGothic",
],
"ko" => &[
"Malgun Gothic",
"Apple SD Gothic Neo",
"AppleGothic",
"Noto Sans CJK KR",
"NanumGothic",
],
"ar" | "fa" | "ur" => &[
"Segoe UI", "Tahoma",
"Geeza Pro", "Damascus",
"Noto Sans Arabic", "Noto Naskh Arabic",
"Amiri",
],
"he" | "iw" => &["Segoe UI", "Arial Hebrew", "Noto Sans Hebrew"],
"hi" | "mr" => &[
"Mangal",
"Nirmala UI",
"Kohinoor Devanagari",
"Devanagari Sangam MN",
"Noto Sans Devanagari",
],
"bn" => &[
"Vrinda",
"Nirmala UI",
"Bangla Sangam MN",
"Noto Sans Bengali",
],
"ta" => &["Latha", "Nirmala UI", "Tamil Sangam MN", "Noto Sans Tamil"],
"te" => &[
"Gautami",
"Nirmala UI",
"Telugu Sangam MN",
"Noto Sans Telugu",
],
"th" => &[
"Leelawadee UI",
"Leelawadee",
"Thonburi",
"Noto Sans Thai",
"Garuda",
],
_ => &[],
}
}
pub fn load_for_locale(ctx: &egui::Context, code: &str) -> Result<usize, String> {
let candidates = candidates_for_locale(&code.to_lowercase());
if candidates.is_empty() {
return Ok(0);
}
let mut db = fontdb::Database::new();
db.load_system_fonts();
let mut defs = egui::FontDefinitions::default();
let mut added = Vec::new();
for &family_name in candidates {
let query = fontdb::Query {
families: &[fontdb::Family::Name(family_name)],
..fontdb::Query::default()
};
let id = match db.query(&query) {
Some(id) => id,
None => continue,
};
let bytes_opt: Option<Vec<u8>> = db.with_face_data(id, |data, _index| data.to_vec());
let bytes = match bytes_opt {
Some(b) => b,
None => continue,
};
let egui_name = format!("os:{family_name}");
defs.font_data.insert(
egui_name.clone(),
Arc::new(egui::FontData::from_owned(bytes)),
);
defs.families
.entry(egui::FontFamily::Proportional)
.or_default()
.push(egui_name.clone());
defs.families
.entry(egui::FontFamily::Monospace)
.or_default()
.push(egui_name.clone());
added.push(family_name);
}
if added.is_empty() {
tracing::warn!(
"no OS font found for locale '{code}'; non-Latin glyphs will render as tofu \
(expected candidates: {candidates:?})"
);
} else {
tracing::info!(
"loaded {} OS font(s) for locale '{code}': {:?}",
added.len(),
added
);
ctx.set_fonts(defs);
}
Ok(added.len())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn latin_locales_short_circuit_with_no_candidates() {
for code in ["en", "nl", "de", "fr", "es", "ru", "el", "uk", "sv"] {
assert!(
candidates_for_locale(code).is_empty(),
"{code} should not need extra fonts"
);
}
}
#[test]
fn cjk_locales_have_per_os_candidates() {
for code in ["zh", "zh-cn", "zh-tw", "ja", "ko"] {
assert!(
!candidates_for_locale(code).is_empty(),
"{code} needs CJK fonts"
);
}
}
#[test]
fn arabic_family_locales_share_candidates() {
let ar = candidates_for_locale("ar");
assert_eq!(ar, candidates_for_locale("fa"));
assert_eq!(ar, candidates_for_locale("ur"));
}
}