mod cache;
pub mod catalog;
pub mod face;
mod instance_bake;
pub mod opentype;
pub mod request;
pub mod resolve;
mod substitutes;
pub use cache::FontCache;
pub use catalog::{FaceCatalog, FaceLookup, FaceRef};
pub use face::{FaceIdentity, FaceName, FaceRecord, IntrinsicStyle, NameKind, VariationInstance};
pub use instance_bake::InstanceBakeFailure;
pub use request::{EffectiveStyle, FaceRequest, Synthesis, Toggle};
pub use resolve::{FaceResolution, FallbackReason, ResolutionStep, Selection};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use skia_safe::font_style::{Slant, Weight};
use skia_safe::{FontMgr, FontStyle, Typeface};
use crate::model::{EmbeddedFont, EmbeddedFontVariant};
use opentype::FontFormat;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct EmbeddedFontId(u32);
impl EmbeddedFontId {
pub fn raw(self) -> u32 {
self.0
}
#[cfg(test)]
pub(crate) fn from_raw(raw: u32) -> Self {
Self(raw)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TypefaceId(pub u32);
impl From<&Typeface> for TypefaceId {
fn from(tf: &Typeface) -> Self {
Self(tf.unique_id())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TypefaceOrigin {
Embedded {
id: EmbeddedFontId,
collection_index: u32,
},
System { typeface_id: TypefaceId },
}
#[derive(Clone, Debug)]
pub struct TypefaceEntry {
pub typeface: Typeface,
pub origin: TypefaceOrigin,
pub instance: InstanceState,
pub synthesis: Synthesis,
}
impl TypefaceEntry {
fn system(typeface: Typeface) -> Self {
let typeface_id = TypefaceId::from(&typeface);
Self {
typeface,
origin: TypefaceOrigin::System { typeface_id },
instance: InstanceState::NotInstanced,
synthesis: Synthesis::default(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum InstanceState {
NotInstanced,
Baked(VariationInstance),
Unbaked {
instance: VariationInstance,
reason: InstanceBakeFailure,
},
}
#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub struct FaceRequestKey {
pub name_lc: String,
pub bold: Toggle,
pub italic: Toggle,
}
impl FaceRequestKey {
pub fn new(request: &FaceRequest<'_>) -> Self {
Self {
name_lc: face::fold_name(request.name),
bold: request.bold,
italic: request.italic,
}
}
}
#[derive(thiserror::Error, Debug)]
pub enum RegisterError {
#[error("invalid embedded font data for '{family}' ({variant:?})")]
InvalidFontData {
family: String,
variant: EmbeddedFontVariant,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct OpenFaceKey {
identity: FaceIdentity,
location: Vec<([u8; 4], u32)>,
}
impl OpenFaceKey {
fn new(record: &FaceRecord) -> Self {
Self {
identity: record.identity.clone(),
location: record
.instance
.as_ref()
.map(|instance| {
instance
.coords
.iter()
.map(|coord| (coord.axis.0, coord.value.to_bits()))
.collect()
})
.unwrap_or_default(),
}
}
}
#[derive(Debug, Clone)]
struct EmbeddedRecord {
family: String,
variant: EmbeddedFontVariant,
bytes: Vec<u8>,
}
pub struct FontRegistry {
font_mgr: FontMgr,
embedded: Vec<EmbeddedRecord>,
catalog: FaceCatalog,
typefaces: RefCell<HashMap<FaceRequestKey, TypefaceEntry>>,
faces: RefCell<HashMap<OpenFaceKey, TypefaceEntry>>,
warned: RefCell<HashSet<String>>,
}
impl FontRegistry {
pub fn new(font_mgr: FontMgr) -> Self {
Self {
catalog: FaceCatalog::build(font_mgr.clone()),
font_mgr,
embedded: Vec::new(),
typefaces: RefCell::new(HashMap::new()),
faces: RefCell::new(HashMap::new()),
warned: RefCell::new(HashSet::new()),
}
}
pub fn build(
font_mgr: FontMgr,
embedded: &[EmbeddedFont],
families: &[String],
) -> Result<Self, crate::render::error::RenderError> {
if font_mgr
.legacy_make_typeface(None::<&str>, FontStyle::normal())
.is_none()
{
return Err(crate::render::error::RenderError::NoFontsAvailable);
}
let mut reg = Self::new(font_mgr);
for ef in embedded {
if let Err(err) = reg.register_embedded(&ef.family, ef.variant, ef.data.clone()) {
log::warn!("{err}");
}
}
reg.preload(families);
Ok(reg)
}
pub fn font_mgr(&self) -> &FontMgr {
&self.font_mgr
}
pub fn catalog(&self) -> &FaceCatalog {
&self.catalog
}
pub fn embedded_font_count(&self) -> usize {
self.embedded.len()
}
pub fn cached_typeface_count(&self) -> usize {
self.typefaces.borrow().len()
}
pub fn register_embedded(
&mut self,
family: &str,
variant: EmbeddedFontVariant,
bytes: Vec<u8>,
) -> Result<EmbeddedFontId, RegisterError> {
let invalid = || RegisterError::InvalidFontData {
family: family.to_string(),
variant,
};
let face_count = match FontFormat::detect(&bytes) {
Ok(FontFormat::Ttc { face_count }) => face_count,
Ok(_) => 1,
Err(err) => {
log::debug!("[font] embedded '{family}' has unrecognised bytes ({err})");
1
}
};
let id = EmbeddedFontId(self.embedded.len() as u32);
let mut registered = 0;
for collection_index in 0..face_count {
let Some(typeface) = self.open_embedded_typeface(&bytes, collection_index) else {
continue;
};
self.catalog
.add_embedded_face(id, collection_index, family, &typeface);
registered += 1;
}
if registered == 0 {
return Err(invalid());
}
self.embedded.push(EmbeddedRecord {
family: family.to_string(),
variant,
bytes,
});
log::debug!(
"[font] registered embedded '{family}' {variant:?} ({registered} face(s), id {})",
id.0
);
Ok(id)
}
fn open_embedded_typeface(&self, bytes: &[u8], collection_index: u32) -> Option<Typeface> {
let face_count = match FontFormat::detect(bytes) {
Ok(FontFormat::Ttc { face_count }) => face_count,
_ => 1,
};
if face_count <= 1 {
return self.font_mgr.new_from_data(bytes, 0);
}
match opentype::carve_collection_face(bytes, collection_index, face_count) {
Ok(carved) => self.font_mgr.new_from_data(&carved.bytes, 0),
Err(err) => {
log::debug!(
"[font] could not carve collection face {collection_index} of \
{face_count} ({err})"
);
None
}
}
}
pub fn embedded_bytes(&self, id: EmbeddedFontId) -> &[u8] {
&self.embedded[id.0 as usize].bytes
}
pub fn embedded_meta(&self, id: EmbeddedFontId) -> (&str, EmbeddedFontVariant) {
let r = &self.embedded[id.0 as usize];
(&r.family, r.variant)
}
pub fn resolve(&self, request: &FaceRequest<'_>) -> TypefaceEntry {
let key = FaceRequestKey::new(request);
if let Some(entry) = self.typefaces.borrow().get(&key) {
return entry.clone();
}
let entry = self.resolve_uncached(request);
self.typefaces.borrow_mut().insert(key, entry.clone());
entry
}
pub fn resolve_flags(&self, family: &str, bold: bool, italic: bool) -> TypefaceEntry {
self.resolve(&FaceRequest::new(
family,
if bold { Toggle::On } else { Toggle::Absent },
if italic { Toggle::On } else { Toggle::Absent },
))
}
fn resolve_uncached(&self, request: &FaceRequest<'_>) -> TypefaceEntry {
match resolve::resolve(request, &self.catalog) {
resolve::FaceResolution::Selected(selection) => {
if let Some(mut entry) = self.open(&selection.record) {
log::debug!(
"[font] '{}' {:?}/{:?} → {} '{}'{}",
request.name,
request.bold,
request.italic,
selection.step.label(),
selection.record.canonical_family,
selection
.record
.instance
.as_ref()
.map(|i| format!(" instance '{}'", i.subfamily))
.unwrap_or_default(),
);
entry.synthesis = Synthesis::compute(request, &selection.record.intrinsic);
return entry;
}
self.warn_once(
request.name,
format_args!(
"matched '{}' but the font system would not open it",
selection.record.canonical_family
),
);
self.host_default(request)
}
resolve::FaceResolution::Default { reason } => {
match &reason {
resolve::FallbackReason::NoCandidate => self.warn_once(
request.name,
format_args!("no installed font and no substitute matched"),
),
resolve::FallbackReason::AmbiguousName { step, candidates } => self.warn_once(
request.name,
format_args!(
"{candidates} different faces answer to this name at the {} step, \
so no face could be chosen",
step.label()
),
),
}
self.host_default(request)
}
}
}
fn host_default(&self, request: &FaceRequest<'_>) -> TypefaceEntry {
let style = EffectiveStyle::resolve(request, IntrinsicStyle::NEUTRAL);
let tf = self
.font_mgr
.legacy_make_typeface(None::<&str>, style.font_style())
.expect("FontRegistry::build guarantees a last-resort typeface");
log::debug!(
"[font] '{}' → host default '{}'",
request.name,
tf.family_name()
);
let mut entry = TypefaceEntry::system(tf);
let selected = IntrinsicStyle::from_font_style(entry.typeface.font_style());
entry.synthesis = Synthesis::compute(request, &selected);
entry
}
fn warn_once(&self, family: &str, detail: std::fmt::Arguments<'_>) {
if self.warned.borrow_mut().insert(family.to_lowercase()) {
log::warn!("[font] '{family}': {detail}; falling back to the host default");
}
}
fn open(&self, record: &FaceRecord) -> Option<TypefaceEntry> {
let key = OpenFaceKey::new(record);
if let Some(entry) = self.faces.borrow().get(&key) {
return Some(entry.clone());
}
let entry = self.open_uncached(record)?;
self.faces.borrow_mut().insert(key, entry.clone());
Some(entry)
}
fn open_uncached(&self, record: &FaceRecord) -> Option<TypefaceEntry> {
let (base, origin) = match &record.identity {
FaceIdentity::Embedded {
font,
collection_index,
} => {
let bytes = self.embedded_bytes(*font);
let typeface = self.open_embedded_typeface(bytes, *collection_index)?;
(
typeface,
TypefaceOrigin::Embedded {
id: *font,
collection_index: *collection_index,
},
)
}
FaceIdentity::System { family, face_index } => {
let mut style_set = self.font_mgr.match_family(family);
let typeface = style_set.new_typeface(*face_index)?;
let typeface_id = TypefaceId::from(&typeface);
(typeface, TypefaceOrigin::System { typeface_id })
}
};
let Some(instance) = &record.instance else {
return Some(TypefaceEntry {
typeface: base,
origin,
instance: InstanceState::NotInstanced,
synthesis: Synthesis::default(),
});
};
let bake_result = match &origin {
TypefaceOrigin::Embedded {
id,
collection_index,
} => {
instance_bake::bake_instance(self.embedded_bytes(*id), *collection_index, instance)
}
TypefaceOrigin::System { .. } => match base.to_font_data() {
Some((bytes, collection_index)) => {
instance_bake::bake_instance(&bytes, collection_index as u32, instance)
}
None => Err(InstanceBakeFailure::Failed(
"the font system returned no data for this typeface".to_string(),
)),
},
};
let baked = bake_result.and_then(|bytes| {
let data = skia_safe::Data::new_copy(&bytes);
self.font_mgr.new_from_data(&data, 0).ok_or_else(|| {
InstanceBakeFailure::Failed(
"the font system declined to open the baked font".to_string(),
)
})
});
match baked {
Ok(baked_tf) => {
let typeface_id = TypefaceId::from(&baked_tf);
Some(TypefaceEntry {
typeface: baked_tf,
origin: TypefaceOrigin::System { typeface_id },
instance: InstanceState::Baked(instance.clone()),
synthesis: Synthesis::default(),
})
}
Err(reason) => {
self.warn_once(
&record.canonical_family,
format_args!(
"'{}' is a variable instance ({reason}); the embedded \
font will carry the default location",
instance.subfamily
),
);
match apply_variation(&base, instance) {
Some(instanced) => {
let origin = match origin {
TypefaceOrigin::System { .. } => TypefaceOrigin::System {
typeface_id: TypefaceId::from(&instanced),
},
embedded => embedded,
};
Some(TypefaceEntry {
typeface: instanced,
origin,
instance: InstanceState::Unbaked {
instance: instance.clone(),
reason,
},
synthesis: Synthesis::default(),
})
}
None => {
log::debug!(
"[font] '{}': the font system declined to instance '{}'; \
using the default location",
record.canonical_family,
instance.subfamily
);
Some(TypefaceEntry {
typeface: base,
origin,
instance: InstanceState::NotInstanced,
synthesis: Synthesis::default(),
})
}
}
}
}
}
pub fn resolve_exact(&self, family: &str, style: FontStyle) -> Option<TypefaceEntry> {
let request = request_from_style(family, style);
match resolve::resolve(&request, &self.catalog) {
resolve::FaceResolution::Selected(selection) if is_exact_step(&selection.step) => {
self.open(&selection.record)
}
_ => None,
}
}
pub fn resolve_system_only(&self, family: &str, style: FontStyle) -> Option<TypefaceEntry> {
let request = request_from_style(family, style);
let faces = self.catalog.family_faces(family)?;
let effective = EffectiveStyle::resolve(&request, IntrinsicStyle::NEUTRAL);
let best = faces
.iter()
.min_by_key(|(_, record)| {
(
u8::from(record.intrinsic.is_italic() != effective.is_italic()),
(record.intrinsic.weight - effective.weight).unsigned_abs(),
)
})
.map(|(_, record)| record.clone())?;
self.open(&best)
}
pub fn preload(&self, families: &[String]) {
for family in families {
for (bold, italic) in [
(Toggle::Absent, Toggle::Absent),
(Toggle::On, Toggle::Absent),
(Toggle::Absent, Toggle::On),
(Toggle::On, Toggle::On),
] {
self.resolve(&FaceRequest::new(family, bold, italic));
}
}
}
pub fn replace_typeface_by_id(
&mut self,
old_id: TypefaceId,
new_typeface: Typeface,
new_origin: TypefaceOrigin,
) -> usize {
let mut count = 0;
for entry in self.typefaces.get_mut().values_mut() {
if TypefaceId::from(&entry.typeface) == old_id {
entry.typeface = new_typeface.clone();
entry.origin = new_origin.clone();
count += 1;
}
}
count
}
pub fn cached_entries(&self) -> Vec<(FaceRequestKey, TypefaceEntry)> {
self.typefaces
.borrow()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
}
fn request_from_style(family: &str, style: FontStyle) -> FaceRequest<'_> {
FaceRequest::new(
family,
if *style.weight() >= *Weight::BOLD {
Toggle::On
} else {
Toggle::Absent
},
if matches!(style.slant(), Slant::Italic | Slant::Oblique) {
Toggle::On
} else {
Toggle::Absent
},
)
}
fn is_exact_step(step: &resolve::ResolutionStep) -> bool {
use resolve::ResolutionStep::*;
matches!(
step,
EmbeddedFace | EmbeddedFamily | SystemFamily | SystemFaceName | SystemMetadataAlias
)
}
fn apply_variation(base: &Typeface, instance: &VariationInstance) -> Option<Typeface> {
use skia_safe::font_arguments::{variation_position, VariationPosition};
use skia_safe::{FontArguments, FourByteTag};
let coordinates: Vec<variation_position::Coordinate> = instance
.coords
.iter()
.map(|coord| variation_position::Coordinate {
axis: FourByteTag::new(u32::from_be_bytes(coord.axis.0)),
value: coord.value,
})
.collect();
let arguments = FontArguments::new().set_variation_design_position(VariationPosition {
coordinates: &coordinates,
});
base.clone_with_arguments(&arguments)
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::render::fonts::opentype::NameId;
use crate::render::fonts::{FaceRequest, Toggle};
use skia_safe::font_style::Width;
fn fmgr() -> FontMgr {
FontMgr::new()
}
pub(crate) fn arbitrary_system_font_bytes() -> Vec<u8> {
let mgr = fmgr();
let tf = mgr
.legacy_make_typeface(None::<&str>, FontStyle::normal())
.expect("system has no default typeface — cannot run test");
let (bytes, _ttc_index) = tf
.to_font_data()
.expect("legacy default typeface lacks raw font bytes — cannot run test");
bytes
}
#[test]
fn registry_empty_after_construction() {
let r = FontRegistry::new(fmgr());
assert_eq!(r.embedded_font_count(), 0);
assert_eq!(r.cached_typeface_count(), 0);
}
#[test]
fn a_fresh_registry_shares_nothing_with_an_earlier_one() {
let r1 = FontRegistry::new(fmgr());
let _ = r1.resolve(&FaceRequest::plain("FamilyOne"));
assert_eq!(r1.cached_typeface_count(), 1);
drop(r1);
let r2 = FontRegistry::new(fmgr());
assert_eq!(
r2.cached_typeface_count(),
0,
"a fresh registry must not see typefaces cached by an earlier one"
);
}
#[test]
fn resolution_is_cached_and_idempotent() {
let r = FontRegistry::new(fmgr());
let request = FaceRequest::plain("DefinitelyNotInstalledXYZ");
let a = r.resolve(&request);
let b = r.resolve(&request);
assert_eq!(
TypefaceId::from(&a.typeface),
TypefaceId::from(&b.typeface),
"second resolution must hit the cache and yield the same typeface"
);
assert_eq!(r.cached_typeface_count(), 1);
assert!(matches!(a.origin, TypefaceOrigin::System { .. }));
}
#[test]
fn each_toggle_state_gets_its_own_cache_entry() {
let r = FontRegistry::new(fmgr());
for bold in [Toggle::Absent, Toggle::Off, Toggle::On] {
let _ = r.resolve(&FaceRequest::new("ToggleKeyProbe", bold, Toggle::Absent));
}
assert_eq!(r.cached_typeface_count(), 3);
let absent = r.resolve(&FaceRequest::new(
"ToggleKeyProbe",
Toggle::Absent,
Toggle::Absent,
));
let off = r.resolve(&FaceRequest::new(
"ToggleKeyProbe",
Toggle::Off,
Toggle::Absent,
));
assert_eq!(
TypefaceId::from(&absent.typeface),
TypefaceId::from(&off.typeface)
);
}
#[test]
fn the_request_key_folds_case_and_whitespace() {
let a = FaceRequestKey::new(&FaceRequest::plain(" Proxima Nova "));
let b = FaceRequestKey::new(&FaceRequest::plain("proxima nova"));
assert_eq!(a, b);
assert_ne!(
a,
FaceRequestKey::new(&FaceRequest::new(
"Proxima Nova",
Toggle::On,
Toggle::Absent
))
);
}
#[test]
fn an_embedded_font_outranks_the_host() {
let bytes = arbitrary_system_font_bytes();
let without = FontRegistry::new(fmgr());
let baseline = without.resolve(&FaceRequest::plain("NonexistentFamilyABC"));
assert!(
matches!(baseline.origin, TypefaceOrigin::System { .. }),
"without embedding, an unknown family must fall back to the system path"
);
let mut with = FontRegistry::new(fmgr());
let id = with
.register_embedded("NonexistentFamilyABC", EmbeddedFontVariant::Regular, bytes)
.expect("register_embedded should accept a valid system font's bytes");
let resolved = with.resolve(&FaceRequest::plain("NonexistentFamilyABC"));
assert_eq!(
resolved.origin,
TypefaceOrigin::Embedded {
id,
collection_index: 0
},
"after registration, resolution must return the embedded origin"
);
}
#[test]
fn embedded_bytes_are_stored_verbatim() {
let bytes = arbitrary_system_font_bytes();
let mut r = FontRegistry::new(fmgr());
let id = r
.register_embedded(
"ByteIdentityProbe",
EmbeddedFontVariant::Regular,
bytes.clone(),
)
.expect("registration should succeed for valid font bytes");
assert_eq!(r.embedded_bytes(id), bytes.as_slice());
assert_eq!(
r.embedded_meta(id),
("ByteIdentityProbe", EmbeddedFontVariant::Regular)
);
}
#[test]
fn every_face_of_an_embedded_collection_registers() {
let mut r = FontRegistry::new(fmgr());
let id = r
.register_embedded(
"DxCollection",
EmbeddedFontVariant::Regular,
fixture_bytes("DxCollection.ttc"),
)
.expect("a two-face collection must register");
for (collection_index, family) in [(0u32, "Dx Collection One"), (1, "Dx Collection Two")] {
let resolved = r.resolve(&FaceRequest::plain(family));
assert_eq!(
resolved.origin,
TypefaceOrigin::Embedded {
id,
collection_index
},
"face {collection_index} ('{family}') must resolve to its own record"
);
}
}
#[test]
fn an_embedded_face_answers_to_its_declared_family() {
let bytes = arbitrary_system_font_bytes();
let mut r = FontRegistry::new(fmgr());
r.register_embedded("DeclaredNameProbe", EmbeddedFontVariant::Regular, bytes)
.unwrap();
let faces = r.catalog().embedded_faces();
assert!(!faces.is_empty());
assert!(
faces[0].names.iter().any(|n| n.text == "DeclaredNameProbe"),
"the declared name must be indexed"
);
assert!(
faces[0].names.iter().any(|n| n.kind.is_asserted()),
"and so must the font's own, read from its tables"
);
}
#[test]
fn registering_unusable_bytes_reports_an_error() {
let mut r = FontRegistry::new(fmgr());
let err = r
.register_embedded("Garbage", EmbeddedFontVariant::Regular, vec![0u8; 32])
.expect_err("bytes that are not a font must be refused");
assert!(matches!(err, RegisterError::InvalidFontData { .. }));
assert_eq!(r.embedded_font_count(), 0, "and must not be recorded");
}
#[test]
fn every_resolution_records_an_origin() {
let bytes = arbitrary_system_font_bytes();
let mut r = FontRegistry::new(fmgr());
r.register_embedded("OriginEmbedded", EmbeddedFontVariant::Regular, bytes)
.unwrap();
let _ = r.resolve(&FaceRequest::plain("OriginEmbedded"));
let _ = r.resolve(&FaceRequest::plain("OriginSystemFallbackXYZ"));
for (_, entry) in r.cached_entries() {
match entry.origin {
TypefaceOrigin::Embedded { .. } | TypefaceOrigin::System { .. } => {}
}
}
}
#[test]
fn one_face_reached_two_ways_is_one_typeface() {
let bytes = arbitrary_system_font_bytes();
let mut r = FontRegistry::new(fmgr());
r.register_embedded("OneFaceProbe", EmbeddedFontVariant::Regular, bytes)
.unwrap();
let absent = r.resolve(&FaceRequest::new(
"OneFaceProbe",
Toggle::Absent,
Toggle::Absent,
));
let off = r.resolve(&FaceRequest::new("OneFaceProbe", Toggle::Off, Toggle::Off));
assert_eq!(
TypefaceId::from(&absent.typeface),
TypefaceId::from(&off.typeface),
"the same face must not be opened twice — subsetting deduplicates on this id"
);
assert_eq!(
r.cached_typeface_count(),
2,
"…while still being two distinct cache entries"
);
}
#[test]
fn a_host_face_reached_two_ways_is_one_typeface() {
let r = FontRegistry::new(fmgr());
let names: Vec<String> = r.font_mgr().family_names().collect();
let Some(family) = names.first() else { return };
let a = r.resolve(&FaceRequest::new(family, Toggle::Absent, Toggle::Absent));
let b = r.resolve(&FaceRequest::new(family, Toggle::Off, Toggle::Off));
assert_eq!(TypefaceId::from(&a.typeface), TypefaceId::from(&b.typeface));
}
#[test]
fn replace_typeface_by_id_updates_every_key_pointing_at_it() {
let mut r = FontRegistry::new(fmgr());
let a = r.resolve(&FaceRequest::plain("UnknownFamilyA"));
let b = r.resolve(&FaceRequest::plain("UnknownFamilyB"));
assert_eq!(
TypefaceId::from(&a.typeface),
TypefaceId::from(&b.typeface),
"test setup precondition — both unknowns must resolve to the same default"
);
let shared_id = TypefaceId::from(&a.typeface);
let bytes = arbitrary_system_font_bytes();
let replacement = r
.font_mgr()
.new_from_data(&bytes[..], 0)
.expect("replacement typeface should construct from valid bytes");
let replacement_origin = TypefaceOrigin::System {
typeface_id: TypefaceId::from(&replacement),
};
let updated = r.replace_typeface_by_id(shared_id, replacement, replacement_origin);
assert_eq!(
updated, 2,
"both shared-typeface entries must be updated in lockstep"
);
let after = r.resolve(&FaceRequest::plain("UnknownFamilyA"));
assert_ne!(
TypefaceId::from(&after.typeface),
shared_id,
"post-replace resolution must yield the new typeface"
);
}
#[test]
fn resolve_exact_invents_no_fallback() {
let r = FontRegistry::new(fmgr());
assert!(
r.resolve_exact("DefinitelyNotInstalledXYZ", FontStyle::normal())
.is_none(),
"unlike resolve(), resolve_exact must not invent a fallback"
);
}
#[test]
fn resolve_exact_finds_an_embedded_family() {
let bytes = arbitrary_system_font_bytes();
let mut r = FontRegistry::new(fmgr());
r.register_embedded("ExactProbe", EmbeddedFontVariant::Regular, bytes)
.expect("register_embedded should accept valid font bytes");
let entry = r
.resolve_exact("ExactProbe", FontStyle::normal())
.expect("embedded font must be resolvable via exact match");
assert!(matches!(entry.origin, TypefaceOrigin::Embedded { .. }));
}
#[test]
fn resolve_exact_refuses_a_substitution() {
let r = FontRegistry::new(fmgr());
let exact = r.resolve_exact("Calibri", FontStyle::normal());
if let Some(entry) = exact {
let family = entry.typeface.family_name();
assert!(
family.eq_ignore_ascii_case("Calibri"),
"resolve_exact returned '{family}' for Calibri"
);
}
}
#[test]
fn resolve_system_only_skips_embedded_fonts() {
let bytes = arbitrary_system_font_bytes();
let mut r = FontRegistry::new(fmgr());
r.register_embedded("SystemOnlyProbe", EmbeddedFontVariant::Regular, bytes)
.expect("register_embedded should accept valid font bytes");
let exact = r
.resolve_exact("SystemOnlyProbe", FontStyle::normal())
.expect("embedded font must be reachable via resolve_exact");
assert!(matches!(exact.origin, TypefaceOrigin::Embedded { .. }));
assert!(
r.resolve_system_only("SystemOnlyProbe", FontStyle::normal())
.is_none(),
"resolve_system_only must skip embedded fonts so emoji resolution \
can fall through to the host's color emoji typeface"
);
}
#[test]
fn preload_warms_four_combinations_per_family() {
let r = FontRegistry::new(fmgr());
r.preload(&["PreloadProbeA".to_string(), "PreloadProbeB".to_string()]);
assert_eq!(r.cached_typeface_count(), 8);
}
#[test]
fn opening_a_manager_face_reproduces_its_family_and_style() {
let r = FontRegistry::new(fmgr());
let names: Vec<String> = r.font_mgr().family_names().collect();
let Some(family) = names.first() else { return };
let Some(faces) = r.catalog().family_faces(family) else {
return;
};
for (_, record) in faces.iter().take(4) {
let entry = r.open(record).expect("an indexed face must reopen");
assert!(
entry
.typeface
.family_name()
.eq_ignore_ascii_case(&record.canonical_family)
|| entry.typeface.family_name().eq_ignore_ascii_case(family),
"reopened '{}' for a record of '{}'",
entry.typeface.family_name(),
record.canonical_family
);
}
}
#[test]
fn an_opened_face_reports_whether_it_is_an_instance() {
let r = FontRegistry::new(fmgr());
let names: Vec<String> = r.font_mgr().family_names().collect();
let Some(family) = names.first() else { return };
let Some(faces) = r.catalog().family_faces(family) else {
return;
};
let (_, record) = &faces[0];
let entry = r.open(record).expect("reopen");
assert_eq!(
entry.instance,
InstanceState::NotInstanced,
"a manager face carries no design-space location"
);
}
#[test]
fn a_named_instance_bakes_at_open_time() {
let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("test-files/fonts/DxVariable.ttf");
let bytes = std::fs::read(&fixture).unwrap_or_else(|e| {
panic!("fixture 'DxVariable.ttf' is missing ({e}) — run scripts/make_font_fixtures.py")
});
let mut registry = FontRegistry::new(fmgr());
registry
.register_embedded(
"Dx Variable",
crate::model::EmbeddedFontVariant::Regular,
bytes,
)
.expect("DxVariable.ttf must register");
let records = registry.catalog().embedded_faces();
let default_record = records
.iter()
.find(|r| r.instance.is_none())
.expect("the default location has its own record");
let semibold_record = records
.iter()
.find(|r| r.instance.as_ref().map(|i| i.subfamily.as_str()) == Some("SemiBold"))
.expect("the SemiBold instance has its own record");
let default_entry = registry.open(default_record).expect("open default");
let semibold_entry = registry.open(semibold_record).expect("open SemiBold");
match &semibold_entry.instance {
InstanceState::Baked(instance) => assert_eq!(instance.subfamily, "SemiBold"),
other => panic!("expected a baked instance, got {other:?}"),
}
assert_eq!(
default_entry.instance,
InstanceState::NotInstanced,
"the default location is not itself an instance to bake"
);
let default_bytes = default_entry
.typeface
.to_font_data()
.expect("default typeface must have data")
.0
.to_vec();
let semibold_bytes = semibold_entry
.typeface
.to_font_data()
.expect("baked typeface must have data")
.0
.to_vec();
assert_ne!(
default_bytes, semibold_bytes,
"the baked instance's embedded bytes must differ from the default location's"
);
}
fn fixture_bytes(name: &str) -> Vec<u8> {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("test-files/fonts")
.join(name);
std::fs::read(&path).unwrap_or_else(|e| {
panic!("fixture '{name}' is missing ({e}) — run scripts/make_font_fixtures.py")
})
}
#[test]
fn a_single_face_family_embolds_on_request() {
let mut registry = FontRegistry::new(fmgr());
registry
.register_embedded(
"Dx",
crate::model::EmbeddedFontVariant::Regular,
fixture_bytes("Dx-Regular.ttf"),
)
.expect("Dx-Regular.ttf must register");
let entry = registry.resolve(&FaceRequest::new("Dx", Toggle::On, Toggle::Absent));
assert!(entry.synthesis.embolden, "nothing bolder exists to select");
assert!(!entry.synthesis.oblique);
}
#[test]
fn a_family_with_a_real_bold_face_does_not_embold() {
let mut registry = FontRegistry::new(fmgr());
registry
.register_embedded(
"DxSans",
crate::model::EmbeddedFontVariant::Regular,
fixture_bytes("DxSans-Regular.ttf"),
)
.expect("DxSans-Regular.ttf must register");
registry
.register_embedded(
"DxSans",
crate::model::EmbeddedFontVariant::Bold,
fixture_bytes("DxSans-Bold.ttf"),
)
.expect("DxSans-Bold.ttf must register");
let entry = registry.resolve(&FaceRequest::new("DxSans", Toggle::On, Toggle::Absent));
assert!(
!entry.synthesis.embolden,
"ranking must have selected the family's real Bold face"
);
}
#[test]
fn an_already_oblique_face_does_not_double_slant() {
let mut registry = FontRegistry::new(fmgr());
registry
.register_embedded(
"Dx Oblique",
crate::model::EmbeddedFontVariant::Italic,
fixture_bytes("DxOblique.ttf"),
)
.expect("DxOblique.ttf must register");
let entry = registry.resolve(&FaceRequest::new("Dx Oblique", Toggle::Absent, Toggle::On));
assert!(
!entry.synthesis.oblique,
"the face is already Oblique, not Upright"
);
}
#[test]
fn a_font_style_maps_back_onto_toggles_for_the_narrow_resolvers() {
let bold = request_from_style("X", FontStyle::bold());
assert_eq!(bold.bold, Toggle::On);
assert_eq!(bold.italic, Toggle::Absent);
let italic = request_from_style("X", FontStyle::italic());
assert_eq!(italic.bold, Toggle::Absent);
assert_eq!(italic.italic, Toggle::On);
let plain = request_from_style("X", FontStyle::normal());
assert_eq!(plain.bold, Toggle::Absent);
assert_eq!(plain.italic, Toggle::Absent);
let semi = request_from_style(
"X",
FontStyle::new(Weight::SEMI_BOLD, Width::NORMAL, Slant::Upright),
);
assert_eq!(semi.bold, Toggle::Absent, "600 is below the BOLD threshold");
let seven_hundred = request_from_style(
"X",
FontStyle::new(Weight::BOLD, Width::NORMAL, Slant::Upright),
);
assert_eq!(seven_hundred.bold, Toggle::On);
}
#[test]
fn only_evidence_backed_steps_count_as_exact() {
use resolve::ResolutionStep::*;
assert!(is_exact_step(&EmbeddedFace));
assert!(is_exact_step(&EmbeddedFamily));
assert!(is_exact_step(&SystemFamily));
assert!(is_exact_step(&SystemFaceName));
assert!(is_exact_step(&SystemMetadataAlias));
assert!(!is_exact_step(&ParsedFaceName {
base_family: "X".into(),
weight: 300
}));
assert!(!is_exact_step(&Substitution {
via: "Calibri",
substitute: "Carlito"
}));
}
#[test]
fn a_missing_family_is_reported_once() {
let r = FontRegistry::new(fmgr());
r.preload(&["NoSuchFamily8f3a1c".to_string()]);
assert_eq!(
r.warned.borrow().len(),
1,
"four preloaded combinations must not produce four warnings"
);
let _ = r.resolve(&FaceRequest::plain("AnotherMissing8f3a1c"));
assert_eq!(r.warned.borrow().len(), 2);
}
#[test]
fn the_indexed_name_ids_are_the_ones_documents_use() {
assert_eq!(NameId::from_raw(4), NameId::Full);
assert_eq!(NameId::from_raw(6), NameId::PostScript);
}
}