use crate::device::paths::{FONTS_DIR, SYSTEM_FONTS_DIR, USER_FONTS_DIR};
use crate::rendering::text_render::{
detect_script, font_covers, has_font_for, install_font, Script,
};
use std::fs;
use std::path::PathBuf;
struct FontSpec {
script: Script,
filename: &'static str,
label: &'static str,
probe: &'static str,
}
const FONT_SPECS: &[FontSpec] = &[
FontSpec {
script: Script::Bengali,
filename: "NotoSansBengali.ttf",
label: "Bengali",
probe: "অবংশ",
},
FontSpec {
script: Script::Devanagari,
filename: "NotoSansDevanagari.ttf",
label: "Hindi",
probe: "अकमश",
},
FontSpec {
script: Script::Arabic,
filename: "NotoSansArabic.ttf",
label: "Arabic",
probe: "ابتة",
},
FontSpec {
script: Script::Hebrew,
filename: "NotoSansHebrew.ttf",
label: "Hebrew",
probe: "אבגד",
},
FontSpec {
script: Script::Greek,
filename: "NotoSans.ttf",
label: "Greek",
probe: "αβγδ",
},
FontSpec {
script: Script::Cyrillic,
filename: "NotoSans.ttf",
label: "Cyrillic",
probe: "абвг",
},
FontSpec {
script: Script::Georgian,
filename: "NotoSansGeorgian.ttf",
label: "Georgian",
probe: "აბგდ",
},
FontSpec {
script: Script::Armenian,
filename: "NotoSansArmenian.ttf",
label: "Armenian",
probe: "աբգդ",
},
FontSpec {
script: Script::Ethiopic,
filename: "NotoSansEthiopic.ttf",
label: "Amharic",
probe: "ሀለሐመ",
},
FontSpec {
script: Script::Gujarati,
filename: "NotoSansGujarati.ttf",
label: "Gujarati",
probe: "અકગમ",
},
FontSpec {
script: Script::Gurmukhi,
filename: "NotoSansGurmukhi.ttf",
label: "Punjabi",
probe: "ਅਕਗਮ",
},
FontSpec {
script: Script::Tamil,
filename: "NotoSansTamil.ttf",
label: "Tamil",
probe: "அகசத",
},
FontSpec {
script: Script::Telugu,
filename: "NotoSansTelugu.ttf",
label: "Telugu",
probe: "అకగమ",
},
FontSpec {
script: Script::Kannada,
filename: "NotoSansKannada.ttf",
label: "Kannada",
probe: "ಅಕಗಮ",
},
FontSpec {
script: Script::Malayalam,
filename: "NotoSansMalayalam.ttf",
label: "Malayalam",
probe: "അകഗമ",
},
FontSpec {
script: Script::Sinhala,
filename: "NotoSansSinhala.ttf",
label: "Sinhala",
probe: "අකගම",
},
FontSpec {
script: Script::Thai,
filename: "NotoSansThai.ttf",
label: "Thai",
probe: "กขคง",
},
FontSpec {
script: Script::Lao,
filename: "NotoSansLao.ttf",
label: "Lao",
probe: "ກຂຄງ",
},
FontSpec {
script: Script::Khmer,
filename: "NotoSansKhmer.ttf",
label: "Khmer",
probe: "កខគង",
},
FontSpec {
script: Script::Myanmar,
filename: "NotoSansMyanmar.ttf",
label: "Burmese",
probe: "ကခဂဃ",
},
FontSpec {
script: Script::Japanese,
filename: "NotoSansJP.ttf",
label: "Japanese",
probe: "あいカキ",
},
FontSpec {
script: Script::Korean,
filename: "NotoSansKR.ttf",
label: "Korean",
probe: "가나다라",
},
FontSpec {
script: Script::Chinese,
filename: "NotoSansSC.ttf",
label: "Chinese",
probe: "这说门车",
},
];
fn font_path(filename: &str) -> PathBuf {
PathBuf::from(FONTS_DIR).join(filename)
}
fn spec_for_script(script: Script) -> Option<&'static FontSpec> {
FONT_SPECS.iter().find(|s| s.script == script)
}
fn is_lazy_script(script: Script) -> bool {
matches!(
script,
Script::Japanese | Script::Korean | Script::Chinese
)
}
fn try_load_from_disk(spec: &FontSpec) -> bool {
let path = font_path(spec.filename);
match fs::read(&path) {
Ok(data) => {
log::info!(
"font: loaded {} from disk ({} bytes)",
spec.filename,
data.len()
);
install_font(spec.script, data)
}
Err(_) => false,
}
}
fn lang_to_script(lang: &str) -> Script {
let lower = lang.to_lowercase();
let prefix = lower.split('-').next().unwrap_or(&lower);
match prefix {
"bn" => Script::Bengali,
"hi" | "mr" | "ne" => Script::Devanagari,
"ar" | "ur" | "fa" | "ps" => Script::Arabic,
"he" | "iw" => Script::Hebrew,
"el" => Script::Greek,
"ru" | "uk" | "bg" | "mk" | "sr" | "kk" | "mn" | "be" => Script::Cyrillic,
"ka" => Script::Georgian,
"hy" => Script::Armenian,
"am" | "ti" => Script::Ethiopic,
"gu" => Script::Gujarati,
"pa" => Script::Gurmukhi,
"ta" => Script::Tamil,
"te" => Script::Telugu,
"kn" => Script::Kannada,
"ml" => Script::Malayalam,
"si" => Script::Sinhala,
"th" => Script::Thai,
"lo" => Script::Lao,
"km" => Script::Khmer,
"my" => Script::Myanmar,
"ja" => Script::Japanese,
"ko" => Script::Korean,
"zh" => Script::Chinese,
_ => Script::Latin,
}
}
const FONT_SEARCH_DIRS: &[&str] = &[FONTS_DIR, USER_FONTS_DIR, SYSTEM_FONTS_DIR];
pub fn log_available_fonts() {
for dir in FONT_SEARCH_DIRS {
match fs::read_dir(dir) {
Ok(entries) => {
let mut names: Vec<String> = entries
.flatten()
.filter_map(|e| {
let p = e.path();
let ext = p
.extension()
.and_then(|x| x.to_str())
.map(|s| s.to_ascii_lowercase())
.unwrap_or_default();
if matches!(ext.as_str(), "ttf" | "otf" | "ttc") {
p.file_name()
.and_then(|n| n.to_str())
.map(|s| s.to_string())
} else {
None
}
})
.collect();
names.sort();
log::debug!("font dir {}: {} file(s) {:?}", dir, names.len(), names);
}
Err(_) => log::debug!("font dir {}: (not present)", dir),
}
}
}
fn find_covering_font(spec: &FontSpec) -> Option<PathBuf> {
for dir in FONT_SEARCH_DIRS {
if *dir == FONTS_DIR {
continue; }
let entries = match fs::read_dir(dir) {
Ok(e) => e.flatten().collect::<Vec<_>>(),
Err(_) => continue,
};
for entry in entries {
let p = entry.path();
let ext = p
.extension()
.and_then(|x| x.to_str())
.map(|s| s.to_ascii_lowercase())
.unwrap_or_default();
if !matches!(ext.as_str(), "ttf" | "otf" | "ttc") {
continue;
}
if let Ok(data) = fs::read(&p) {
if font_covers(&data, spec.probe) {
return Some(p);
}
}
}
}
None
}
pub fn load_cached_fonts() {
let mut loaded = 0;
for spec in FONT_SPECS {
if is_lazy_script(spec.script) {
continue;
}
if try_load_from_disk(spec) {
loaded += 1;
continue;
}
if let Some(path) = find_covering_font(spec) {
log::info!(
"font: {} -> using {} (covers {})",
spec.label,
path.display(),
spec.label
);
if let Ok(data) = fs::read(&path) {
if install_font(spec.script, data) {
loaded += 1;
continue;
}
}
}
log::warn!("font: no {} font found on device", spec.label);
}
if loaded > 0 {
log::info!("font: {loaded} loaded");
}
}
pub fn ensure_font_for_script(lang: Option<&str>, sample_text: &str) -> Option<String> {
let script = lang
.map(lang_to_script)
.unwrap_or_else(|| detect_script(sample_text));
if has_font_for(script) {
return None;
}
let spec = spec_for_script(script)?;
if try_load_from_disk(spec) {
return None;
}
if let Some(path) = find_covering_font(spec) {
log::info!(
"font: {} -> using {} (covers {})",
spec.label,
path.display(),
spec.label
);
if let Ok(data) = fs::read(&path) {
if install_font(spec.script, data) {
return None;
}
}
}
Some(format!("Reinstall to add {} font", spec.label))
}