use crate::runtime::Params;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub(crate) enum Typeface {
GlyphMono,
GlyphPlex,
#[default]
System,
}
impl Typeface {
pub(crate) fn wire(self) -> &'static str {
match self {
Self::GlyphMono => "glyphMono",
Self::GlyphPlex => "glyphPlex",
Self::System => "system",
}
}
fn from_wire(raw: &str) -> Self {
match raw {
"glyphMono" => Self::GlyphMono,
"glyphPlex" => Self::GlyphPlex,
_ => Self::System,
}
}
}
pub(crate) fn decode(params: &Params<'_>, key: &str) -> Typeface {
params
.string(key)
.map(|raw| Typeface::from_wire(&raw))
.unwrap_or_default()
}
pub(crate) fn cache_key(face_id: &str, bytes: &[u8]) -> String {
format!("frust-native-widgets-{face_id}-{:016x}.ttf", fnv1a64(bytes))
}
fn fnv1a64(bytes: &[u8]) -> u64 {
const OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
const PRIME: u64 = 0x0000_0100_0000_01b3;
bytes.iter().fold(OFFSET_BASIS, |hash, &b| {
(hash ^ b as u64).wrapping_mul(PRIME)
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum RegistrationAction {
ReuseExisting,
WriteThenCreate,
}
pub(crate) fn plan_registration(cache_file_exists: bool) -> RegistrationAction {
if cache_file_exists {
RegistrationAction::ReuseExisting
} else {
RegistrationAction::WriteThenCreate
}
}
pub(crate) fn degrade_on_failure<T, E>(
result: Result<T, E>,
mut warn: impl FnMut(&E),
) -> Option<T> {
match result {
Ok(value) => Some(value),
Err(error) => {
warn(&error);
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::with_identity;
#[test]
fn wire_round_trips_every_variant() {
for tf in [Typeface::GlyphMono, Typeface::GlyphPlex, Typeface::System] {
assert_eq!(Typeface::from_wire(tf.wire()), tf);
}
}
#[test]
fn decode_defaults_to_system_when_absent_or_unrecognized() {
let absent = with_identity("k", 1, "\"text\":\"hi\"");
assert_eq!(decode(&Params::new(&absent), "typeface"), Typeface::System);
let unrecognized = with_identity("k", 1, "\"typeface\":\"nonsense\"");
assert_eq!(
decode(&Params::new(&unrecognized), "typeface"),
Typeface::System
);
}
#[test]
fn decode_reads_a_recognized_value() {
let raw = with_identity("k", 1, "\"typeface\":\"glyphMono\"");
assert_eq!(decode(&Params::new(&raw), "typeface"), Typeface::GlyphMono);
let raw = with_identity("k", 1, "\"typeface\":\"glyphPlex\"");
assert_eq!(decode(&Params::new(&raw), "typeface"), Typeface::GlyphPlex);
}
#[test]
fn cache_key_is_stable_for_identical_bytes() {
let bytes = b"same font bytes".to_vec();
assert_eq!(
cache_key("glyphMono", &bytes),
cache_key("glyphMono", &bytes),
"identical bytes must derive the same cache key across calls (and \
therefore across launches)"
);
}
#[test]
fn cache_key_differs_for_different_bytes() {
assert_ne!(
cache_key("glyphMono", b"one"),
cache_key("glyphMono", b"two"),
);
}
#[test]
fn cache_key_differs_for_different_faces_given_the_same_bytes() {
let bytes = b"shared bytes".to_vec();
assert_ne!(
cache_key("glyphMono", &bytes),
cache_key("glyphPlex", &bytes)
);
}
#[test]
fn plan_registration_writes_once_then_reuses() {
assert_eq!(
plan_registration(false),
RegistrationAction::WriteThenCreate,
"no cache file yet: write it"
);
assert_eq!(
plan_registration(true),
RegistrationAction::ReuseExisting,
"the cache file already exists: never rewrite it"
);
}
#[test]
fn degrade_on_failure_passes_ok_through_without_warning() {
let mut warned = 0;
let result: Result<i32, &str> = Ok(42);
assert_eq!(degrade_on_failure(result, |_| warned += 1), Some(42));
assert_eq!(warned, 0, "a successful registration never warns");
}
#[test]
fn degrade_on_failure_degrades_to_none_and_warns_exactly_once() {
let mut warned = 0;
let result: Result<i32, &str> = Err("boom");
assert_eq!(
degrade_on_failure(result, |_| warned += 1),
None,
"a failed registration degrades to None (the caller's System fallback)"
);
assert_eq!(warned, 1, "exactly one warning for the one failure");
}
}