mod matching;
mod parse;
mod system_fonts;
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct ID(usize);
pub(crate) struct Database {
faces: Vec<FaceInfo>,
family_serif: String,
family_sans_serif: String,
family_cursive: String,
family_fantasy: String,
family_monospace: String,
}
impl Database {
pub(crate) fn new() -> Self {
Database {
faces: Vec::new(),
family_serif: "Times New Roman".to_string(),
family_sans_serif: "Arial".to_string(),
family_cursive: "Comic Sans MS".to_string(),
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
family_fantasy: "Impact".to_string(),
#[cfg(any(target_os = "macos", target_os = "ios"))]
family_fantasy: "Papyrus".to_string(),
family_monospace: "Courier New".to_string(),
}
}
#[cfg_attr(not(feature = "cjk-render-fallback"), allow(dead_code))]
pub(crate) fn load_font_data(&mut self, data: Vec<u8>) {
self.load_font_source(Source::Binary(Arc::new(data)));
}
#[cfg_attr(not(feature = "cjk-render-fallback"), allow(dead_code))]
fn load_font_source(&mut self, source: Source) {
let source_for_log = source.clone();
source.with_data(|data| {
for (index, font_result) in skrifa::raw::FontRef::fonts(data).enumerate() {
let index = index as u32;
let parsed = match font_result {
Ok(font) => parse::parse_face_info(&font, source_for_log.clone(), index),
Err(_) => Err(parse::LoadError::Malformed),
};
match parsed {
Ok(info) => self.faces.push(info),
Err(error) => tracing::warn!(
target: "xberg_native_pdf::vendor::fontdb",
face_index = index,
%error,
"failed to load a font face from source; skipping",
),
}
}
});
}
fn family_name<'a>(&'a self, family: &'a Family<'a>) -> &'a str {
match family {
Family::Name(name) => name,
Family::Serif => self.family_serif.as_str(),
Family::SansSerif => self.family_sans_serif.as_str(),
Family::Cursive => self.family_cursive.as_str(),
Family::Fantasy => self.family_fantasy.as_str(),
Family::Monospace => self.family_monospace.as_str(),
}
}
pub(crate) fn query(&self, query: &Query) -> Option<ID> {
for family in query.families {
let name = self.family_name(family);
let candidates: Vec<(ID, &FaceInfo)> = self
.faces
.iter()
.enumerate()
.filter(|(_, face)| {
face.families
.iter()
.any(|(family_name, _)| family_name == name)
})
.map(|(index, info)| (ID(index), info))
.collect();
if !candidates.is_empty()
&& let Some(id) = matching::find_best_match(&candidates, query)
{
return Some(id);
}
}
None
}
pub(crate) fn face(&self, id: ID) -> Option<&FaceInfo> {
self.faces.get(id.0)
}
fn face_source(&self, id: ID) -> Option<(Source, u32)> {
self.face(id).map(|info| (info.source.clone(), info.index))
}
pub(crate) fn with_face_data<P, T>(&self, id: ID, p: P) -> Option<T>
where
P: FnOnce(&[u8], u32) -> T,
{
let (src, face_index) = self.face_source(id)?;
src.with_data(|data| p(data, face_index))
}
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
fn set_serif_family<S: Into<String>>(&mut self, family: S) {
self.family_serif = family.into();
}
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
fn set_sans_serif_family<S: Into<String>>(&mut self, family: S) {
self.family_sans_serif = family.into();
}
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
fn set_cursive_family<S: Into<String>>(&mut self, family: S) {
self.family_cursive = family.into();
}
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
fn set_fantasy_family<S: Into<String>>(&mut self, family: S) {
self.family_fantasy = family.into();
}
#[cfg(all(
unix,
not(any(target_os = "macos", target_os = "ios", target_os = "android"))
))]
fn set_monospace_family<S: Into<String>>(&mut self, family: S) {
self.family_monospace = family.into();
}
}
#[derive(Debug, Clone)]
pub(crate) struct FaceInfo {
pub(crate) source: Source,
pub(crate) index: u32,
pub(crate) families: Vec<(String, Option<String>)>,
pub(crate) style: Style,
pub(crate) weight: Weight,
pub(crate) stretch: Stretch,
}
#[derive(Clone)]
pub(crate) enum Source {
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
File(PathBuf),
#[cfg_attr(not(feature = "cjk-render-fallback"), allow(dead_code))]
Binary(Arc<Vec<u8>>),
}
impl core::fmt::Debug for Source {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Source::File(path) => f.debug_tuple("File").field(path).finish(),
Source::Binary(data) => f.debug_tuple("Binary").field(&data.len()).finish(),
}
}
}
impl Source {
fn with_data<P, T>(&self, p: P) -> Option<T>
where
P: FnOnce(&[u8]) -> T,
{
match self {
Source::File(path) => std::fs::read(path).ok().map(|data| p(&data)),
Source::Binary(data) => Some(p(data)),
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Query<'a> {
pub(crate) families: &'a [Family<'a>],
pub(crate) weight: Weight,
pub(crate) stretch: Stretch,
pub(crate) style: Style,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum Family<'a> {
Name(&'a str),
Serif,
SansSerif,
Cursive,
Fantasy,
Monospace,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Weight(pub(crate) u16);
impl Default for Weight {
fn default() -> Weight {
Weight::NORMAL
}
}
impl Weight {
pub(crate) const NORMAL: Weight = Weight(400);
pub(crate) const MEDIUM: Weight = Weight(500);
pub(crate) const BOLD: Weight = Weight(700);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum Style {
#[default]
Normal,
Italic,
Oblique,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub(crate) enum Stretch {
UltraCondensed,
ExtraCondensed,
Condensed,
SemiCondensed,
#[default]
Normal,
SemiExpanded,
Expanded,
ExtraExpanded,
UltraExpanded,
}
impl Stretch {
fn to_number(self) -> u16 {
match self {
Stretch::UltraCondensed => 1,
Stretch::ExtraCondensed => 2,
Stretch::Condensed => 3,
Stretch::SemiCondensed => 4,
Stretch::Normal => 5,
Stretch::SemiExpanded => 6,
Stretch::Expanded => 7,
Stretch::ExtraExpanded => 8,
Stretch::UltraExpanded => 9,
}
}
}