pub mod color;
mod book;
mod exceptions;
mod variant;
pub use self::book::{Coverage, FontBook, FontFlags, FontInfo};
pub use self::variant::{FontStretch, FontStyle, FontVariant, FontWeight};
use std::cell::OnceCell;
use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use ttf_parser::GlyphId;
use self::book::find_name;
use crate::foundations::{Bytes, Cast};
use crate::layout::{Abs, Em, Frame};
use crate::text::{BottomEdge, TopEdge};
#[derive(Clone)]
pub struct Font(Arc<Repr>);
struct Repr {
data: Bytes,
index: u32,
info: FontInfo,
metrics: FontMetrics,
ttf: ttf_parser::Face<'static>,
rusty: rustybuzz::Face<'static>,
}
impl Font {
pub fn new(data: Bytes, index: u32) -> Option<Self> {
let slice: &'static [u8] =
unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
let ttf = ttf_parser::Face::parse(slice, index).ok()?;
let rusty = rustybuzz::Face::from_slice(slice, index)?;
let metrics = FontMetrics::from_ttf(&ttf);
let info = FontInfo::from_ttf(&ttf)?;
Some(Self(Arc::new(Repr { data, index, info, metrics, ttf, rusty })))
}
pub fn iter(data: Bytes) -> impl Iterator<Item = Self> {
let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
(0..count).filter_map(move |index| Self::new(data.clone(), index))
}
pub fn data(&self) -> &Bytes {
&self.0.data
}
pub fn index(&self) -> u32 {
self.0.index
}
pub fn info(&self) -> &FontInfo {
&self.0.info
}
pub fn metrics(&self) -> &FontMetrics {
&self.0.metrics
}
pub fn units_per_em(&self) -> f64 {
self.0.metrics.units_per_em
}
pub fn to_em(&self, units: impl Into<f64>) -> Em {
Em::from_units(units, self.units_per_em())
}
pub fn advance(&self, glyph: u16) -> Option<Em> {
self.0
.ttf
.glyph_hor_advance(GlyphId(glyph))
.map(|units| self.to_em(units))
}
pub fn find_name(&self, id: u16) -> Option<String> {
find_name(&self.0.ttf, id)
}
pub fn ttf(&self) -> &ttf_parser::Face<'_> {
&self.0.ttf
}
pub fn rusty(&self) -> &rustybuzz::Face<'_> {
&self.0.rusty
}
pub fn edges(
&self,
top_edge: TopEdge,
bottom_edge: BottomEdge,
font_size: Abs,
bounds: TextEdgeBounds,
) -> (Abs, Abs) {
let cell = OnceCell::new();
let bbox = |gid, f: fn(ttf_parser::Rect) -> i16| {
cell.get_or_init(|| self.ttf().glyph_bounding_box(GlyphId(gid)))
.map(|bbox| self.to_em(f(bbox)).at(font_size))
.unwrap_or_default()
};
let top = match top_edge {
TopEdge::Metric(metric) => match metric.try_into() {
Ok(metric) => self.metrics().vertical(metric).at(font_size),
Err(_) => match bounds {
TextEdgeBounds::Zero => Abs::zero(),
TextEdgeBounds::Frame(frame) => frame.ascent(),
TextEdgeBounds::Glyph(gid) => bbox(gid, |b| b.y_max),
},
},
TopEdge::Length(length) => length.at(font_size),
};
let bottom = match bottom_edge {
BottomEdge::Metric(metric) => match metric.try_into() {
Ok(metric) => -self.metrics().vertical(metric).at(font_size),
Err(_) => match bounds {
TextEdgeBounds::Zero => Abs::zero(),
TextEdgeBounds::Frame(frame) => frame.descent(),
TextEdgeBounds::Glyph(gid) => -bbox(gid, |b| b.y_min),
},
},
BottomEdge::Length(length) => -length.at(font_size),
};
(top, bottom)
}
}
impl Hash for Font {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.data.hash(state);
self.0.index.hash(state);
}
}
impl Debug for Font {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Font({}, {:?})", self.info().family, self.info().variant)
}
}
impl Eq for Font {}
impl PartialEq for Font {
fn eq(&self, other: &Self) -> bool {
self.0.data == other.0.data && self.0.index == other.0.index
}
}
#[derive(Debug, Copy, Clone)]
pub struct FontMetrics {
pub units_per_em: f64,
pub ascender: Em,
pub cap_height: Em,
pub x_height: Em,
pub descender: Em,
pub strikethrough: LineMetrics,
pub underline: LineMetrics,
pub overline: LineMetrics,
}
impl FontMetrics {
pub fn from_ttf(ttf: &ttf_parser::Face) -> Self {
let units_per_em = f64::from(ttf.units_per_em());
let to_em = |units| Em::from_units(units, units_per_em);
let ascender = to_em(ttf.typographic_ascender().unwrap_or(ttf.ascender()));
let cap_height = ttf.capital_height().filter(|&h| h > 0).map_or(ascender, to_em);
let x_height = ttf.x_height().filter(|&h| h > 0).map_or(ascender, to_em);
let descender = to_em(ttf.typographic_descender().unwrap_or(ttf.descender()));
let strikeout = ttf.strikeout_metrics();
let underline = ttf.underline_metrics();
let strikethrough = LineMetrics {
position: strikeout.map_or(Em::new(0.25), |s| to_em(s.position)),
thickness: strikeout
.or(underline)
.map_or(Em::new(0.06), |s| to_em(s.thickness)),
};
let underline = LineMetrics {
position: underline.map_or(Em::new(-0.2), |s| to_em(s.position)),
thickness: underline
.or(strikeout)
.map_or(Em::new(0.06), |s| to_em(s.thickness)),
};
let overline = LineMetrics {
position: cap_height + Em::new(0.1),
thickness: underline.thickness,
};
Self {
units_per_em,
ascender,
cap_height,
x_height,
descender,
strikethrough,
underline,
overline,
}
}
pub fn vertical(&self, metric: VerticalFontMetric) -> Em {
match metric {
VerticalFontMetric::Ascender => self.ascender,
VerticalFontMetric::CapHeight => self.cap_height,
VerticalFontMetric::XHeight => self.x_height,
VerticalFontMetric::Baseline => Em::zero(),
VerticalFontMetric::Descender => self.descender,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct LineMetrics {
pub position: Em,
pub thickness: Em,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum VerticalFontMetric {
Ascender,
CapHeight,
XHeight,
Baseline,
Descender,
}
#[derive(Debug, Copy, Clone)]
pub enum TextEdgeBounds<'a> {
Zero,
Glyph(u16),
Frame(&'a Frame),
}