use crate::compat::{Mutex, OnceLock};
use crate::compat::{String, Vec};
use crate::platform::types::SystemFont;
use std::process::Command;
static CACHE: OnceLock<Mutex<Option<(u64, &'static [SystemFont])>>> = OnceLock::new();
pub(crate) fn system_fonts() -> Vec<SystemFont> {
let epoch = crate::platform::fonts::enumeration_epoch();
let slot = CACHE.get_or_init(|| Mutex::new(None));
{
let guard = crate::compat::lock(slot);
if let Some((cached_epoch, fonts)) = *guard {
if cached_epoch == epoch {
return fonts.to_vec();
}
}
}
let enumerated: &'static [SystemFont] =
crate::compat::Box::leak(enumerate().into_boxed_slice());
*crate::compat::lock(slot) = Some((epoch, enumerated));
enumerated.to_vec()
}
fn enumerate() -> Vec<SystemFont> {
let output =
match Command::new("fc-list").args(["--format", "%{family}\t%{style}\t%{file}\n"]).output()
{
Ok(output) => output,
Err(e) => {
log::warn!("[fonts] failed to run fc-list: {e}; reporting no installed fonts");
return Vec::new();
}
};
if !output.status.success() {
log::warn!("[fonts] fc-list exited with {}; using the output it produced", output.status);
}
let text = String::from_utf8_lossy(&output.stdout);
let mut fonts: Vec<SystemFont> = Vec::new();
for line in text.lines() {
let mut fields = line.split('\t');
let families = fields.next().unwrap_or("").trim();
let style = fields.next().unwrap_or("").trim();
let file = fields.next().unwrap_or("").trim();
let family = families.split(',').next().unwrap_or("").trim();
if family.is_empty() {
continue;
}
if fonts.iter().any(|known| known.family.eq_ignore_ascii_case(family)) {
continue;
}
fonts.push(SystemFont {
family: family.to_string(),
style: style.to_string(),
source: (!file.is_empty()).then(|| std::path::PathBuf::from(file)),
});
}
fonts
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_enumeration_is_well_formed() {
for font in system_fonts() {
assert!(!font.family.is_empty());
if let Some(source) = font.source {
assert!(source.is_absolute(), "fc-list reports an absolute font path");
}
}
}
#[test]
fn a_refresh_advances_the_enumeration_revision() {
let before = crate::platform::fonts::enumeration_epoch();
let _ = crate::platform::fonts::refresh_system_fonts();
assert!(
crate::platform::fonts::enumeration_epoch() > before,
"refresh_system_fonts must invalidate the backend's enumeration cache"
);
}
}