use std::fmt::{Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::Arc;
use skrifa::instance::{Location, LocationRef, Size};
use skrifa::metrics::GlyphMetrics;
use skrifa::raw::types::NameId;
use skrifa::raw::TableProvider;
use skrifa::{FontRef, MetadataProvider, OutlineGlyphCollection};
use tiny_skia_path::FiniteF32;
use yoke::{Yoke, Yokeable};
use crate::geom::Rect;
use crate::text::GlyphId;
use crate::util::Prehashed;
use crate::Data;
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct Font(Arc<Prehashed<Repr>>);
impl Font {
pub fn new(data: Data, index: u32) -> Option<Self> {
Self::new_variable(data, index, &[])
}
pub fn new_variable(data: Data, index: u32, variation_coords: &[(Tag, f32)]) -> Option<Self> {
let font_info = FontInfo::new(data.as_ref(), index, variation_coords)?;
Font::new_with_info(data.clone(), Arc::new(font_info))
}
pub(crate) fn new_with_info(data: Data, font_info: Arc<FontInfo>) -> Option<Self> {
let yoke_data = YokeData {
data: data.0.clone(),
location: font_info.location.clone(),
};
let font_ref_yoke = Yoke::<FontRefYoke<'static>, Box<YokeData>>::attach_to_cart(
Box::new(yoke_data),
|data| {
let font_ref =
FontRef::from_index(data.data.as_ref().as_ref(), font_info.index).unwrap();
FontRefYoke {
font_ref: font_ref.clone(),
glyph_metrics: font_ref.glyph_metrics(Size::unscaled(), &data.location),
outline_glyphs: font_ref.outline_glyphs(),
}
},
);
Some(Font(Arc::new(Prehashed::new(Repr {
font_data: data,
font_ref_yoke,
font_info,
}))))
}
pub(crate) fn postscript_name(&self) -> Option<&str> {
self.0.font_info.postscript_name.as_deref()
}
pub(crate) fn index(&self) -> u32 {
self.font_info().index
}
pub(crate) fn font_info(&self) -> Arc<FontInfo> {
self.0.font_info.clone()
}
pub(crate) fn variation_coordinates(&self) -> &[(Tag, FiniteF32)] {
&self.0.font_info.var_coords
}
pub(crate) fn cap_height(&self) -> Option<f32> {
self.0.font_info.cap_height.map(|n| n.get())
}
pub(crate) fn ascent(&self) -> f32 {
self.0.font_info.ascent.get()
}
pub(crate) fn weight(&self) -> f32 {
self.0.font_info.weight.get()
}
pub(crate) fn stretch(&self) -> f32 {
self.0.font_info.stretch.get()
}
pub(crate) fn descent(&self) -> f32 {
self.0.font_info.descent.get()
}
pub(crate) fn num_glyphs(&self) -> u32 {
self.0.font_info.num_glyphs
}
pub(crate) fn is_monospaced(&self) -> bool {
self.0.font_info.is_monospaced
}
pub(crate) fn italic_angle(&self) -> f32 {
self.0.font_info.italic_angle.get()
}
pub fn units_per_em(&self) -> f32 {
self.0.font_info.units_per_em as f32
}
pub(crate) fn bbox(&self) -> Rect {
self.0.font_info.global_bbox
}
pub(crate) fn location_ref(&self) -> LocationRef<'_> {
(&self.0.font_info.location).into()
}
pub(crate) fn font_ref(&self) -> &FontRef<'_> {
&self.0.font_ref_yoke.get().font_ref
}
pub(crate) fn glyph_metrics(&self) -> &GlyphMetrics<'_> {
&self.0.font_ref_yoke.get().glyph_metrics
}
pub(crate) fn outline_glyphs(&self) -> &OutlineGlyphCollection<'_> {
&self.0.font_ref_yoke.get().outline_glyphs
}
pub(crate) fn font_data(&self) -> Data {
self.0.font_data.clone()
}
#[inline]
pub(crate) fn advance_width(&self, glyph_id: GlyphId) -> Option<f32> {
self.glyph_metrics().advance_width(glyph_id.to_skrifa())
}
}
#[derive(Copy, Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Tag([u8; 4]);
impl Tag {
pub fn new(tag: &[u8; 4]) -> Self {
Self(*tag)
}
pub fn try_from_str(s: &str) -> Option<Self> {
let tag: [u8; 4] = s.as_bytes().try_into().ok()?;
Some(Self(tag))
}
pub fn get(&self) -> &[u8; 4] {
&self.0
}
}
impl Debug for Font {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Font {{..}}")
}
}
struct YokeData {
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
location: Location,
}
impl Deref for YokeData {
type Target = YokeData;
fn deref(&self) -> &Self::Target {
self
}
}
#[derive(Debug, Hash, Eq, PartialEq)]
pub(crate) struct FontInfo {
index: u32,
checksum: u32,
data_len: usize,
var_coords: Vec<(Tag, FiniteF32)>,
location: Location,
units_per_em: u16,
global_bbox: Rect,
num_glyphs: u32,
postscript_name: Option<String>,
ascent: FiniteF32,
descent: FiniteF32,
cap_height: Option<FiniteF32>,
is_monospaced: bool,
italic_angle: FiniteF32,
weight: FiniteF32,
has_glyf: bool,
has_cff: bool,
has_cff2: bool,
stretch: FiniteF32,
}
struct Repr {
font_info: Arc<FontInfo>,
font_data: Data,
font_ref_yoke: Yoke<FontRefYoke<'static>, Box<YokeData>>,
}
impl Hash for Repr {
fn hash<H: Hasher>(&self, state: &mut H) {
self.font_info.hash(state);
}
}
impl FontInfo {
pub(crate) fn new(data: &[u8], index: u32, var_coords: &[(Tag, f32)]) -> Option<Self> {
let font_ref = FontRef::from_index(data, index).ok()?;
let location = font_ref.axes().location(
var_coords
.iter()
.map(|i| (skrifa::Tag::new(i.0.get()), i.1)),
);
let data_len = data.len();
let checksum = font_ref.head().ok()?.checksum_adjustment();
let num_glyphs = font_ref.glyph_names().num_glyphs();
let metrics = font_ref.metrics(Size::unscaled(), &location);
let os_2 = font_ref.os2().ok();
let ascent = FiniteF32::new(
os_2.as_ref()
.map(|s| s.s_typo_ascender() as f32)
.unwrap_or(metrics.ascent),
)?;
let descent = FiniteF32::new(
os_2.as_ref()
.map(|s| s.s_typo_descender() as f32)
.unwrap_or(metrics.descent),
)?;
let is_monospaced = metrics.is_monospace;
let cap_height = metrics.cap_height.map(|n| FiniteF32::new(n).unwrap());
let italic_angle = FiniteF32::new(metrics.italic_angle).unwrap();
let weight = FiniteF32::new(font_ref.attributes().weight.value())?;
let stretch = FiniteF32::new(font_ref.attributes().stretch.ratio())?;
let units_per_em = metrics.units_per_em;
let global_bbox = metrics
.bounds
.and_then(|b| Rect::from_ltrb(b.x_min, b.y_min, b.x_max, b.y_max))
.unwrap_or(Rect::from_xywh(
0.0,
0.0,
units_per_em as f32,
units_per_em as f32,
)?);
let postscript_name = {
if let Ok(name) = font_ref.name() {
name.name_record().iter().find_map(|n| {
if n.name_id.get() == NameId::POSTSCRIPT_NAME {
if let Ok(string) = n.string(name.string_data()) {
return Some(string.to_string());
}
}
None
})
} else {
None
}
};
let has_glyf = font_ref.glyf().is_ok();
let has_cff = font_ref.cff().is_ok();
let has_cff2 = font_ref.cff2().is_ok();
Some(FontInfo {
index,
data_len,
checksum,
var_coords: var_coords
.iter()
.map(|v| (v.0, FiniteF32::new(v.1).unwrap_or_default()))
.collect(),
location,
num_glyphs,
units_per_em,
postscript_name,
ascent,
cap_height,
has_glyf,
has_cff,
has_cff2,
descent,
is_monospaced,
weight,
stretch,
italic_angle,
global_bbox,
})
}
pub(crate) fn can_be_cid_font(&self) -> bool {
self.has_cff || self.has_glyf || self.has_cff2
}
}
#[derive(Yokeable, Clone)]
struct FontRefYoke<'a> {
pub font_ref: FontRef<'a>,
pub glyph_metrics: GlyphMetrics<'a>,
pub outline_glyphs: OutlineGlyphCollection<'a>,
}