use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use tiny_skia::{Path, PathBuilder};
use xxhash_rust::xxh3::Xxh3;
use super::sdf::SdfFontStack;
#[derive(Debug, thiserror::Error)]
pub enum FontError {
#[error("font face #{index} failed to parse: {msg}")]
Parse { index: u32, msg: String },
}
pub struct Font {
bytes: Arc<[u8]>,
face_index: u32,
units_per_em: f32,
ascent_em: f32,
descent_em: f32,
content_hash: u64,
glyph_paths: RwLock<HashMap<u16, Option<Arc<Path>>>>,
}
impl Font {
pub fn from_bytes(bytes: Arc<[u8]>, face_index: u32) -> Result<Font, FontError> {
let face = ttf_parser::Face::parse(&bytes, face_index).map_err(|e| FontError::Parse {
index: face_index,
msg: e.to_string(),
})?;
let units_per_em = face.units_per_em() as f32;
let ascent_em = face.ascender() as f32 / units_per_em;
let descent_em = -(face.descender() as f32) / units_per_em;
if rustybuzz::Face::from_slice(&bytes, face_index).is_none() {
return Err(FontError::Parse {
index: face_index,
msg: "rustybuzz rejected the face".into(),
});
}
let mut hasher = Xxh3::new();
hasher.update(&face_index.to_le_bytes());
hasher.update(&bytes);
let content_hash = hasher.digest();
Ok(Font {
bytes,
face_index,
units_per_em,
ascent_em,
descent_em,
content_hash,
glyph_paths: RwLock::new(HashMap::new()),
})
}
pub fn content_hash(&self) -> u64 {
self.content_hash
}
pub fn units_per_em(&self) -> f32 {
self.units_per_em
}
pub fn ascent_em(&self) -> f32 {
self.ascent_em
}
pub fn descent_em(&self) -> f32 {
self.descent_em
}
pub fn face(&self) -> rustybuzz::Face<'_> {
rustybuzz::Face::from_slice(&self.bytes, self.face_index)
.expect("face validated in Font::from_bytes")
}
pub fn covers(&self, face: &rustybuzz::Face<'_>, c: char) -> bool {
face.glyph_index(c).is_some()
}
pub fn glyph_path(&self, face: &rustybuzz::Face<'_>, glyph_id: u16) -> Option<Arc<Path>> {
if let Some(cached) = self
.glyph_paths
.read()
.expect("glyph cache poisoned")
.get(&glyph_id)
{
return cached.clone();
}
let path = {
let mut builder = PathOutline {
pb: PathBuilder::new(),
};
face.outline_glyph(ttf_parser::GlyphId(glyph_id), &mut builder)
.and_then(|_| builder.pb.finish().map(Arc::new))
};
self.glyph_paths
.write()
.expect("glyph cache poisoned")
.insert(glyph_id, path.clone());
path
}
}
#[derive(Debug, Clone)]
pub enum StackEntry {
Outline(Arc<Font>),
Sdf(Arc<SdfFontStack>),
}
impl From<Arc<Font>> for StackEntry {
fn from(f: Arc<Font>) -> Self {
StackEntry::Outline(f)
}
}
impl From<Arc<SdfFontStack>> for StackEntry {
fn from(s: Arc<SdfFontStack>) -> Self {
StackEntry::Sdf(s)
}
}
#[allow(clippy::large_enum_variant)]
pub enum FaceEntry<'a> {
Outline {
font: &'a Font,
face: rustybuzz::Face<'a>,
},
Sdf(&'a SdfFontStack),
}
impl<'a> FaceEntry<'a> {
pub fn prepare(stack: &'a [StackEntry]) -> Vec<FaceEntry<'a>> {
stack.iter().map(FaceEntry::from_entry).collect()
}
pub fn from_entry(entry: &'a StackEntry) -> FaceEntry<'a> {
match entry {
StackEntry::Outline(f) => FaceEntry::Outline {
font: f,
face: f.face(),
},
StackEntry::Sdf(s) => FaceEntry::Sdf(s),
}
}
pub fn advance_em(&self, c: char) -> Option<f32> {
match self {
FaceEntry::Outline { font, face } => {
let gid = face.glyph_index(c)?;
let adv = face.glyph_hor_advance(gid)?;
Some(adv as f32 / font.units_per_em())
}
FaceEntry::Sdf(s) => Some(s.glyph(c)?.advance as f32 / super::sdf::SDF_EM_PX),
}
}
pub(crate) fn covers(&self, c: char) -> bool {
match self {
FaceEntry::Outline { font, face } => font.covers(face, c),
FaceEntry::Sdf(s) => s.coverage(c) == super::sdf::SdfCoverage::Present,
}
}
}
impl std::fmt::Debug for Font {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Font")
.field("face_index", &self.face_index)
.field("units_per_em", &self.units_per_em)
.finish()
}
}
struct PathOutline {
pb: PathBuilder,
}
impl ttf_parser::OutlineBuilder for PathOutline {
fn move_to(&mut self, x: f32, y: f32) {
self.pb.move_to(x, y);
}
fn line_to(&mut self, x: f32, y: f32) {
self.pb.line_to(x, y);
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.pb.quad_to(x1, y1, x, y);
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.pb.cubic_to(x1, y1, x2, y2, x, y);
}
fn close(&mut self) {
self.pb.close();
}
}