use super::*;
use usvg::fontdb::{Database, FaceInfo, ID};
pub(super) enum FontCandidate {
Loaded(FaceInfo),
File {
path: PathBuf,
index: u32,
expected: ExpectedFace,
},
}
pub(super) enum ExpectedFace {
Bounded {
metadata: FaceInfo,
digest: Vec<u8>,
},
#[cfg(feature = "experimental-system-font-query")]
Platform(platform::PlatformCandidate),
}
impl ExpectedFace {
pub(super) fn validate(&self, face: &FaceInfo, path: &Path) -> Result<(), NativeFontError> {
match self {
Self::Bounded { metadata, .. } if system::same_face(metadata, face) => Ok(()),
Self::Bounded { .. } => Err(source_error(
path,
"font properties changed after metadata discovery; retry the operation",
)),
#[cfg(feature = "experimental-system-font-query")]
Self::Platform(candidate) => platform::validate_candidate(face, candidate, path),
}
}
}
#[derive(Clone)]
pub(super) enum DiscoveryProvider {
Bounded(Arc<system::SystemCatalog>),
#[cfg(feature = "experimental-system-font-query")]
Platform {
catalog: Arc<platform::PlatformCatalog>,
explicit: Arc<Database>,
},
}
impl DiscoveryProvider {
fn select(
&self,
font: &usvg::Font,
control: &OperationControl,
) -> Result<Option<FontCandidate>, NativeFontError> {
match self {
Self::Bounded(catalog) => catalog.select(font, control),
#[cfg(feature = "experimental-system-font-query")]
Self::Platform { catalog, explicit } => catalog.select(font, explicit, control),
}
}
fn fallback(
&self,
character: char,
base: &FaceInfo,
excluded: &[FaceInfo],
control: &OperationControl,
accept: &mut dyn FnMut(FontCandidate) -> Result<Option<ID>, NativeFontError>,
) -> Result<Option<ID>, NativeFontError> {
match self {
Self::Bounded(catalog) => catalog.fallback(character, base, excluded, control, accept),
#[cfg(feature = "experimental-system-font-query")]
Self::Platform { catalog, explicit } => {
for face in explicit.faces() {
control.terminal_checkpoint_at(OperationPhase::Layout)?;
if excluded.iter().any(|used| system::same_face(used, face)) {
continue;
}
if let Some(id) = accept(FontCandidate::Loaded(face.clone()))? {
return Ok(Some(id));
}
}
for candidate in catalog.fallback_candidates(character, base, control)? {
control.terminal_checkpoint_at(OperationPhase::Layout)?;
if let Some(id) = accept(platform::candidate(candidate)?)? {
return Ok(Some(id));
}
}
Ok(None)
}
}
}
pub(super) fn catalog_face_count(&self) -> Option<usize> {
match self {
Self::Bounded(catalog) => Some(catalog.database.faces().count()),
#[cfg(feature = "experimental-system-font-query")]
Self::Platform { .. } => None,
}
}
}
pub(super) fn resolver(
context: NativeFontContext,
control: OperationControl,
) -> usvg::FontResolver<'static> {
let select_context = context.clone();
let select_control = control.clone();
usvg::FontResolver {
select_font: Box::new(move |font, database| {
let result = (|| {
select_control.terminal_checkpoint_at(OperationPhase::Layout)?;
let provider = select_context
.0
.discovery
.as_ref()
.ok_or(NativeFontError::SystemDiscoveryUnavailable)?;
provider
.select(font, &select_control)?
.map(|candidate| {
select_context.0.sources.materialize(
candidate,
database,
&select_context.0.limits,
&select_control,
)
})
.transpose()
})();
resolve_or_record(&select_context, result)
}),
select_fallback: Box::new(move |character, excluded, database| {
let result = (|| {
control.terminal_checkpoint_at(OperationPhase::Layout)?;
let excluded_faces = excluded
.iter()
.filter_map(|id| database.face(*id))
.cloned()
.collect::<Vec<_>>();
let Some(base) = excluded_faces.first() else {
return Ok(None);
};
let provider = context
.0
.discovery
.as_ref()
.ok_or(NativeFontError::SystemDiscoveryUnavailable)?;
provider.fallback(
character,
base,
&excluded_faces,
&control,
&mut |candidate| {
let id = context.0.sources.materialize(
candidate,
database,
&context.0.limits,
&control,
)?;
if excluded.contains(&id) {
return Ok(None);
}
let mut probe = Database::new();
let probe_excluded = excluded_faces
.iter()
.cloned()
.map(|face| probe.push_face_info(face))
.collect::<Vec<_>>();
let face = database.face(id).cloned().ok_or_else(|| {
NativeFontError::Measurement("missing materialized fallback".to_owned())
})?;
let candidate_id = probe.push_face_info(face);
let selected = (usvg::FontResolver::default_fallback_selector())(
character,
&probe_excluded,
&mut Arc::new(probe),
);
Ok((selected == Some(candidate_id)).then_some(id))
},
)
})();
resolve_or_record(&context, result)
}),
}
}
fn resolve_or_record(
context: &NativeFontContext,
result: Result<Option<ID>, NativeFontError>,
) -> Option<ID> {
match result {
Ok(id) => id,
Err(error) => {
context.0.fatal.get_or_init(|| error);
None
}
}
}