#![cfg(feature = "runtime-fonts")]
use crate::compat::Mutex;
use crate::render::text::font_assets::FaceBytes;
pub const MAX_RUNTIME_FACES: usize = 4;
struct Registry {
faces: [Option<FaceBytes>; MAX_RUNTIME_FACES],
len: usize,
}
impl Registry {
const fn new() -> Self {
Self { faces: [None; MAX_RUNTIME_FACES], len: 0 }
}
}
static REGISTRY: Mutex<Registry> = Mutex::new(Registry::new());
pub fn register_face(name: &'static str, bytes: &'static [u8]) -> bool {
if name.is_empty() || bytes.is_empty() {
return false;
}
if ttf_parser::Face::parse(bytes, 0).is_err() {
return false;
}
let mut registry = crate::compat::lock(®ISTRY);
let len = registry.len;
for slot in registry.faces[..len].iter_mut() {
if let Some(existing) = slot {
if existing.name.eq_ignore_ascii_case(name) {
*slot = Some(FaceBytes { name, bytes });
drop(registry);
crate::render::text::font_assets::invalidate_face_cache();
return true;
}
}
}
if len >= MAX_RUNTIME_FACES {
return false;
}
registry.faces[len] = Some(FaceBytes { name, bytes });
registry.len = len + 1;
drop(registry);
crate::render::text::font_assets::invalidate_face_cache();
true
}
pub fn clear_faces() -> usize {
let mut registry = crate::compat::lock(®ISTRY);
let removed = registry.len;
registry.faces = [None; MAX_RUNTIME_FACES];
registry.len = 0;
drop(registry);
crate::render::text::font_assets::invalidate_face_cache();
removed
}
pub fn registered_face_count() -> usize {
crate::compat::lock(®ISTRY).len
}
pub(crate) fn active_runtime_faces() -> ([FaceBytes; MAX_RUNTIME_FACES], usize) {
let registry = crate::compat::lock(®ISTRY);
let mut out: [FaceBytes; MAX_RUNTIME_FACES] =
[FaceBytes { name: "", bytes: &[] }; MAX_RUNTIME_FACES];
let mut len = 0;
for slot in registry.faces[..registry.len].iter().rev().flatten() {
out[len] = *slot;
len += 1;
}
(out, len)
}
#[cfg(test)]
mod tests {
use super::*;
fn any_parsable_face() -> Option<&'static [u8]> {
crate::render::text::font_assets::active_faces().first().map(|face| face.bytes)
}
#[test]
fn registration_refuses_junk_and_honours_the_capacity_and_the_order() {
clear_faces();
assert_eq!(registered_face_count(), 0);
assert_eq!(active_runtime_faces().1, 0);
assert!(!register_face("junk", b"not a font"));
assert!(!register_face("empty", b""));
assert!(!register_face("", b"1234"));
assert_eq!(registry_len_for_test(), 0, "a refused face must not occupy a slot");
let Some(bytes) = any_parsable_face() else {
return;
};
assert!(register_face("First", bytes));
assert!(register_face("Second", bytes));
assert_eq!(registered_face_count(), 2);
let (faces, len) = active_runtime_faces();
assert_eq!(len, 2);
assert_eq!(faces[0].name, "Second");
assert_eq!(faces[1].name, "First");
assert!(register_face("First", bytes));
assert_eq!(registered_face_count(), 2, "a same-named registration replaces");
let (faces, len) = active_runtime_faces();
assert_eq!(len, 2);
assert_eq!(
faces[1].name, "First",
"a replacement keeps the slot the original occupied, so the order is unchanged"
);
assert_eq!(faces[0].name, "Second");
assert!(register_face("Third", bytes));
assert!(register_face("Fourth", bytes));
assert_eq!(registered_face_count(), MAX_RUNTIME_FACES);
assert!(!register_face("Fifth", bytes), "a full registry must refuse, not evict");
assert_eq!(clear_faces(), MAX_RUNTIME_FACES);
assert_eq!(registered_face_count(), 0);
}
fn registry_len_for_test() -> usize {
registered_face_count()
}
}