pub mod color;
mod book;
mod exceptions;
mod info;
mod metrics;
mod tag;
mod variant;
mod variations;
pub use self::book::FontBook;
pub use self::info::{Coverage, FontFlags, FontInfo};
pub use self::metrics::{
FontMetrics, LineMetrics, MathConstants, ScriptMetrics, TextEdgeBounds,
VerticalFontMetric,
};
pub use self::tag::Tag;
pub use self::variant::{FontStretch, FontStyle, FontVariant, FontWeight};
pub use self::variations::{AxisValue, FontAxis, FontVariations, StandardAxes};
use std::cell::OnceCell;
use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::Arc;
use ttf_parser::{GlyphId, name_id};
use self::exceptions::find_exception;
use self::info::find_name;
use crate::foundations::Bytes;
use crate::layout::{Abs, Em};
use crate::text::{BottomEdge, TopEdge};
#[derive(Clone)]
pub struct Font(Arc<FontInner>);
struct FontInner {
index: u32,
info: FontInfo,
ttf: ttf_parser::Face<'static>,
data: Bytes,
}
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 info = FontInfo::from_ttf(&ttf)?;
Some(Self(Arc::new(FontInner { index, info, ttf, data })))
}
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 post_script_name(&self) -> Option<String> {
find_name(&self.0.ttf, name_id::POST_SCRIPT_NAME)
}
#[comemo::memoize]
pub fn instantiate(
self,
variant: FontVariant,
size: Abs,
custom: &FontVariations,
) -> FontInstance {
let axes = &self.info().axes;
let automatic = FontVariations::resolve(axes, variant, size);
let full = automatic.chain(custom).normalized();
self.instantiate_impl(full)
}
#[comemo::memoize]
fn instantiate_impl(self, variations: FontVariations) -> FontInstance {
let data = self.data();
let index = self.index();
let slice: &'static [u8] =
unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
let mut rusty = rustybuzz::Face::from_slice(slice, index).unwrap();
for &(tag, value) in &variations.0 {
rusty.set_variation(tag.into(), value.0);
}
let metrics = FontMetrics::from_ttf(&rusty);
FontInstance(Arc::new(FontInstanceInner {
metrics,
rusty,
variations,
font: self,
}))
}
}
impl Debug for Font {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Font({}, {:?})", self.info().family, self.info().variant)
}
}
impl Hash for Font {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.data.hash(state);
self.0.index.hash(state);
}
}
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(Clone)]
pub struct FontInstance(Arc<FontInstanceInner>);
struct FontInstanceInner {
metrics: FontMetrics,
rusty: rustybuzz::Face<'static>,
variations: FontVariations,
font: Font,
}
impl FontInstance {
pub fn font(&self) -> &Font {
&self.0.font
}
pub fn variations(&self) -> &FontVariations {
&self.0.variations
}
pub fn metrics(&self) -> &FontMetrics {
&self.0.metrics
}
#[inline]
pub fn math(&self) -> &MathConstants {
self.0.metrics.math.get_or_init(|| MathConstants::new(self))
}
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 x_advance(&self, glyph: u16) -> Option<Em> {
self.0
.rusty
.glyph_hor_advance(GlyphId(glyph))
.map(|units| self.to_em(units))
}
pub fn y_advance(&self, glyph: u16) -> Option<Em> {
self.0
.rusty
.glyph_ver_advance(GlyphId(glyph))
.map(|units| self.to_em(units))
}
pub fn ttf(&self) -> &ttf_parser::Face<'_> {
&self.0.rusty
}
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 Deref for FontInstance {
type Target = Font;
fn deref(&self) -> &Self::Target {
self.font()
}
}
impl Debug for FontInstance {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("FontInstance")
.field("font", self.font())
.field("variations", self.variations())
.finish()
}
}
impl Hash for FontInstance {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.font.hash(state);
self.0.variations.hash(state);
}
}
impl Eq for FontInstance {}
impl PartialEq for FontInstance {
fn eq(&self, other: &Self) -> bool {
self.0.font == other.0.font && self.0.variations == other.0.variations
}
}