use crate::compat::{OnceLock, String, ToString, Vec};
use crate::platform::SystemFont;
use alloc::boxed::Box;
static SNAPSHOT: OnceLock<Vec<SystemFont>> = OnceLock::new();
pub fn system_fonts() -> &'static [SystemFont] {
SNAPSHOT.get_or_init(|| crate::platform::platform_facts().system_fonts())
}
pub fn refresh_system_fonts() -> &'static [SystemFont] {
let fresh: &'static OnceLock<Vec<SystemFont>> = Box::leak(Box::new(OnceLock::new()));
fresh.get_or_init(|| crate::platform::platform_facts().system_fonts())
}
pub fn families() -> Vec<String> {
let mut families: Vec<String> = system_fonts()
.iter()
.map(|font| font.family.trim().to_string())
.filter(|family| !family.is_empty())
.collect();
families.sort_unstable_by_key(|family| family.to_lowercase());
families.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
families
}
#[cfg(feature = "text-shaping")]
pub fn bundled_families() -> Vec<String> {
let mut families: Vec<String> = crate::render::text::font_assets::active_faces()
.iter()
.map(|face| face.name.trim().to_string())
.filter(|family| !family.is_empty())
.collect();
families.sort_unstable_by_key(|family| family.to_lowercase());
families.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
families
}
pub fn chooser_families() -> Vec<String> {
let system = families();
if !system.is_empty() {
return system;
}
#[cfg(feature = "text-shaping")]
{
bundled_families()
}
#[cfg(not(feature = "text-shaping"))]
{
Vec::new()
}
}
#[cfg(all(feature = "runtime-fonts", not(alloc_frugal)))]
pub fn register_family(family: &str) -> bool {
let (faces, len) = crate::render::text::runtime_fonts::active_runtime_faces();
if faces[..len].iter().any(|face| face.name.eq_ignore_ascii_case(family.trim())) {
return true;
}
if crate::render::text::registered_face_count() >= crate::render::text::MAX_RUNTIME_FACES {
return false;
}
let Some(font) =
system_fonts().iter().find(|font| font.family.eq_ignore_ascii_case(family.trim()))
else {
return false;
};
let Some(path) = font.source.as_ref() else {
return false;
};
let Ok(bytes) = std::fs::read(path) else {
return false;
};
let name: &'static str = Box::leak(font.family.clone().into_boxed_str());
let bytes: &'static [u8] = Box::leak(bytes.into_boxed_slice());
crate::render::text::register_face(name, bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn families_are_sorted_and_have_no_blank_or_duplicate_entries() {
let families = families();
assert!(families.windows(2).all(|pair| pair[0].to_lowercase() <= pair[1].to_lowercase()));
assert!(families.iter().all(|family| !family.trim().is_empty()));
let mut unique = families.clone();
unique.dedup_by(|a, b| a.eq_ignore_ascii_case(b));
assert_eq!(unique.len(), families.len());
}
#[cfg(all(feature = "runtime-fonts", not(alloc_frugal)))]
#[test]
fn an_unknown_family_is_not_registered() {
assert!(!register_family("This Family Does Not Exist 12345"));
}
#[cfg(feature = "text-shaping")]
#[test]
fn bundled_families_are_sorted_and_blank_free() {
let families = bundled_families();
assert!(families.windows(2).all(|pair| pair[0].to_lowercase() <= pair[1].to_lowercase()));
assert!(families.iter().all(|family| !family.trim().is_empty()));
}
}