use std::{borrow::Cow, path::Path};
use fontdb::Source;
use ttf_parser::{
name::{name_id, Table as NameTable},
Language, RawFace,
};
use super::{
cmap::CMapTable,
error::{BROKEN_NAME_TABLE, MISSING_NAME_TABLE, NAME_TAG},
Error, Result,
};
use crate::loader::database;
pub struct FaceInfo {
pub id: fontdb::ID,
pub family: &'static str,
pub name: Cow<'static, str>,
pub path: &'static Path,
pub index: u32,
pub gid: u16,
}
enum FontFaceFullName {
Full(String),
SubFamily(String),
None,
}
impl FaceInfo {
pub fn parse_if_contains(face: &'static fontdb::FaceInfo, c: char) -> Result<Option<Self>> {
let path = match face.source {
Source::File(ref path) => path,
_ => unreachable!("we only load font file, so source must be File variant"),
};
let index = face.index;
let Some((gid, name)) = database()
.with_face_data(face.id, |data, index| -> Result<_> {
let rf = RawFace::parse(data, index)?;
let Some(gid) = CMapTable::parse(rf)?.glyph_index(c) else {
return Ok(None);
};
let name = Self::parse_full_name(rf)?;
Ok(Some((gid.0, name)))
})
.expect("we only load font from database so it must not None")?
else {
return Ok(None);
};
let family = face
.families
.get(0)
.map(|(s, _)| s.as_str())
.ok_or(Error::MissingFamilyName)?;
let name: Cow<'static, str> = match name {
FontFaceFullName::Full(full) => full.into(),
FontFaceFullName::SubFamily(sub) => {
log::info!(
"Font face {}:{} do not have a full name, uses family({}) + subfamily({})",
path.to_string_lossy(),
index,
family,
sub,
);
if sub.is_empty() {
Cow::Borrowed(family)
} else {
format!("{} {}", family, sub).into()
}
}
FontFaceFullName::None => {
log::info!(
"Font face {}:{} do not have a full name and subfamily, uses postscript \
name({})",
path.to_string_lossy(),
index,
face.post_script_name,
);
Cow::Borrowed(&face.post_script_name)
}
};
Ok(Some(FaceInfo {
id: face.id,
family,
name,
path,
index: face.index,
gid,
}))
}
fn parse_full_name(rf: RawFace<'_>) -> Result<FontFaceFullName> {
let name_data = rf.table(NAME_TAG).ok_or(MISSING_NAME_TABLE)?;
let name_table = NameTable::parse(name_data).ok_or(BROKEN_NAME_TABLE)?;
let mut sub_family: Option<String> = None;
for i in 0..name_table.names.len() {
let name = name_table.names.get(i).ok_or(BROKEN_NAME_TABLE)?;
if name.language() == Language::English_UnitedStates && name.is_unicode() {
if name.name_id == name_id::FULL_NAME {
if let Some(name) = name.to_string() {
return Ok(FontFaceFullName::Full(name));
}
}
if name.name_id == name_id::SUBFAMILY {
if let Some(name) = name.to_string() {
sub_family.replace(name);
}
}
}
}
if let Some(sub) = sub_family {
Ok(FontFaceFullName::SubFamily(sub))
} else {
Ok(FontFaceFullName::None)
}
}
}