use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
pub(crate) fn register_font_from_memory(bytes: &[u8]) -> Option<Vec<String>> {
let path = unique_temp_font_path();
std::fs::write(&path, bytes).ok()?;
let c_path = match path.to_str().and_then(|s| CString::new(s).ok()) {
Some(c) => c,
None => {
let _ = std::fs::remove_file(&path);
return None;
}
};
let config = unsafe { fontconfig_sys::FcConfigGetCurrent() };
if config.is_null() {
let _ = std::fs::remove_file(&path);
return None;
}
let before = app_font_count(config);
let added = unsafe { fontconfig_sys::FcConfigAppFontAddFile(config, c_path.as_ptr().cast()) };
if added == 0 {
let _ = std::fs::remove_file(&path);
return None;
}
let names = new_app_font_family_names(config, before);
if names.is_empty() {
return None;
}
notify_fontmap_config_changed();
Some(names)
}
fn unique_temp_font_path() -> PathBuf {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!(
"quadraui-1013-appfont-{}-{n}.font",
std::process::id()
))
}
fn app_font_count(config: *mut fontconfig_sys::FcConfig) -> usize {
let set = unsafe { fontconfig_sys::FcConfigGetFonts(config, fontconfig_sys::FcSetApplication) };
if set.is_null() {
return 0;
}
unsafe { (*set).nfont.max(0) as usize }
}
fn new_app_font_family_names(config: *mut fontconfig_sys::FcConfig, before: usize) -> Vec<String> {
let set = unsafe { fontconfig_sys::FcConfigGetFonts(config, fontconfig_sys::FcSetApplication) };
if set.is_null() {
return Vec::new();
}
let (nfont, fonts) = unsafe { ((*set).nfont.max(0) as usize, (*set).fonts) };
if fonts.is_null() || nfont <= before {
return Vec::new();
}
let mut names = Vec::with_capacity(nfont - before);
for i in before..nfont {
let pattern = unsafe { *fonts.add(i) };
if !pattern.is_null() {
if let Some(name) = pattern_family_name(pattern) {
names.push(name);
}
}
}
names
}
fn pattern_family_name(pattern: *mut fontconfig_sys::FcPattern) -> Option<String> {
let mut out: *mut fontconfig_sys::FcChar8 = std::ptr::null_mut();
let result = unsafe {
fontconfig_sys::FcPatternGetString(
pattern,
fontconfig_sys::constants::FC_FAMILY.as_ptr(),
0,
&mut out,
)
};
if result != fontconfig_sys::FcResultMatch || out.is_null() {
return None;
}
let c = unsafe { CStr::from_ptr(out as *const c_char) };
Some(c.to_string_lossy().into_owned())
}
pub(crate) fn has_font_family(family: &str) -> bool {
let requested = family.trim();
if requested.is_empty() {
return false;
}
let Ok(c_family) = CString::new(requested) else {
return false;
};
let config = unsafe { fontconfig_sys::FcConfigGetCurrent() };
if config.is_null() {
return false;
}
let pattern = unsafe { fontconfig_sys::FcPatternCreate() };
if pattern.is_null() {
return false;
}
unsafe {
fontconfig_sys::FcPatternAddString(
pattern,
fontconfig_sys::constants::FC_FAMILY.as_ptr(),
c_family.as_ptr().cast(),
);
fontconfig_sys::FcConfigSubstitute(config, pattern, fontconfig_sys::FcMatchPattern);
fontconfig_sys::FcDefaultSubstitute(pattern);
}
let mut result: fontconfig_sys::FcResult = fontconfig_sys::FcResultNoMatch;
let matched = unsafe { fontconfig_sys::FcFontMatch(config, pattern, &mut result) };
unsafe { fontconfig_sys::FcPatternDestroy(pattern) };
if matched.is_null() {
return false;
}
let resolved = pattern_family_name(matched);
unsafe { fontconfig_sys::FcPatternDestroy(matched) };
resolved.is_some_and(|name| name.eq_ignore_ascii_case(requested))
}
fn notify_fontmap_config_changed() {
let fontmap = unsafe { pangocairo::ffi::pango_cairo_font_map_get_default() };
if fontmap.is_null() {
return;
}
let font_type: pangocairo::cairo::FontType =
unsafe { pangocairo::ffi::pango_cairo_font_map_get_font_type(fontmap.cast()) }.into();
if font_type != pangocairo::cairo::FontType::FontTypeFt {
return;
}
unsafe { pango_fc_font_map_config_changed(fontmap) };
}
extern "C" {
fn pango_fc_font_map_config_changed(fcfontmap: *mut pangocairo::pango::ffi::PangoFontMap);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn register_font_from_memory_rejects_bytes_that_are_not_a_font() {
let garbage = [0u8; 64];
assert!(
register_font_from_memory(&garbage).is_none(),
"64 zero bytes are not a parseable font — must report None, not a fabricated family"
);
}
#[test]
fn register_font_from_memory_rejects_empty_bytes() {
assert!(
register_font_from_memory(&[]).is_none(),
"empty bytes are not a parseable font — must report None"
);
}
#[test]
fn unique_temp_font_path_does_not_collide_within_a_process() {
let a = unique_temp_font_path();
let b = unique_temp_font_path();
assert_ne!(a, b, "consecutive calls must not reuse the same temp path");
}
#[test]
fn has_font_family_is_false_for_a_name_nothing_is_installed_under() {
assert!(!has_font_family(
"Definitely Not A Real Font Family Quadraui 1024"
));
}
#[test]
fn has_font_family_is_false_for_empty_or_blank_input() {
assert!(!has_font_family(""));
assert!(!has_font_family(" "));
}
#[test]
#[ignore]
fn manual_smoke_has_font_family_sees_a_just_registered_app_font() {
let path = std::env::var("QUADRAUI_SMOKE_FONT").expect("set QUADRAUI_SMOKE_FONT");
let bytes = std::fs::read(&path).expect("read font");
let names = register_font_from_memory(&bytes).expect("register_font_from_memory");
let family = names.first().expect("at least one family name");
assert!(
has_font_family(family),
"just-registered family {family:?} should be reported as installed"
);
}
#[test]
#[ignore]
fn manual_smoke_real_font() {
let path = std::env::var("QUADRAUI_SMOKE_FONT").expect("set QUADRAUI_SMOKE_FONT");
let bytes = std::fs::read(&path).expect("read font");
let names = register_font_from_memory(&bytes).expect("register_font_from_memory");
eprintln!("registered families: {names:?}");
assert!(!names.is_empty());
}
}