//! Font handling. 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}; /// An OpenType font. /// /// Values of this type are cheap to clone and hash. #[derive(Clone)] pub struct Font(Arc); /// The internal representation of a font. struct Repr { /// The font's index in the buffer. index: u32, /// Metadata about the font. info: FontInfo, /// The font's metrics. metrics: FontMetrics, /// The underlying ttf-parser face. ttf: ttf_parser::Face<'static>, /// The underlying rustybuzz face. rusty: rustybuzz::Face<'static>, // NOTE: `ttf` and `rusty` reference `data`, so it's important for `data` // to be dropped after them or they will be left dangling while they're // dropped. Fields are dropped in declaration order, so `data` needs to be // declared after `ttf` and `rusty`. /// The raw font data, possibly shared with other fonts from the same /// collection. The vector's allocation must not move, because `ttf` points /// into it using unsafe code. data: Bytes, } impl Font { /// Parse a font from data and collection index. pub fn new(data: Bytes, index: u32) -> Option { // Safety: // - The slices's location is stable in memory: // - We don't move the underlying vector // - Nobody else can move it since we have a strong ref to the `Arc`. // - The internal 'static lifetime is not leaked because its rewritten // to the self-lifetime in `ttf()`. 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 }))) } /// Parse all fonts in the given data. pub fn iter(data: Bytes) -> impl Iterator { let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1); (0..count).filter_map(move |index| Self::new(data.clone(), index)) } /// The underlying buffer. pub fn data(&self) -> &Bytes { &self.0.data } /// The font's index in the buffer. pub fn index(&self) -> u32 { self.0.index } /// The font's metadata. pub fn info(&self) -> &FontInfo { &self.0.info } /// The font's metrics. pub fn metrics(&self) -> &FontMetrics { &self.0.metrics } /// The number of font units per one em. pub fn units_per_em(&self) -> f64 { self.0.metrics.units_per_em } /// Convert from font units to an em length. pub fn to_em(&self, units: impl Into) -> Em { Em::from_units(units, self.units_per_em()) } /// Look up the horizontal advance width of a glyph. pub fn x_advance(&self, glyph: u16) -> Option { self.0 .ttf .glyph_hor_advance(GlyphId(glyph)) .map(|units| self.to_em(units)) } /// Look up the vertical advance width of a glyph. pub fn y_advance(&self, glyph: u16) -> Option { self.0 .ttf .glyph_ver_advance(GlyphId(glyph)) .map(|units| self.to_em(units)) } /// Look up the width of a space. pub fn space_width(&self) -> Option { self.0.ttf.glyph_index(' ').and_then(|id| self.x_advance(id.0)) } /// Lookup a name by id. pub fn find_name(&self, id: u16) -> Option { find_name(&self.0.ttf, id) } /// A reference to the underlying `ttf-parser` face. pub fn ttf(&self) -> &ttf_parser::Face<'_> { // We can't implement Deref because that would leak the // internal 'static lifetime. &self.0.ttf } /// A reference to the underlying `rustybuzz` face. pub fn rusty(&self) -> &rustybuzz::Face<'_> { // We can't implement Deref because that would leak the // internal 'static lifetime. &self.0.rusty } /// Resolve the top and bottom edges of text. 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(&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 } } /// Metrics of a font. #[derive(Debug, Copy, Clone)] pub struct FontMetrics { /// How many font units represent one em unit. pub units_per_em: f64, /// The distance from the baseline to the typographic ascender. pub ascender: Em, /// The approximate height of uppercase letters. pub cap_height: Em, /// The approximate height of non-ascending lowercase letters. pub x_height: Em, /// The distance from the baseline to the typographic descender. pub descender: Em, /// Recommended metrics for a strikethrough line. pub strikethrough: LineMetrics, /// Recommended metrics for an underline. pub underline: LineMetrics, /// Recommended metrics for an overline. pub overline: LineMetrics, } impl FontMetrics { /// Extract the font's metrics. 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, } } /// Look up a vertical metric. 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, } } } /// Metrics for a decorative line. #[derive(Debug, Copy, Clone)] pub struct LineMetrics { /// The vertical offset of the line from the baseline. Positive goes /// upwards, negative downwards. pub position: Em, /// The thickness of the line. pub thickness: Em, } /// Identifies a vertical metric of a font. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum VerticalFontMetric { /// The font's ascender, which typically exceeds the height of all glyphs. Ascender, /// The approximate height of uppercase letters. CapHeight, /// The approximate height of non-ascending lowercase letters. XHeight, /// The baseline on which the letters rest. Baseline, /// The font's ascender, which typically exceeds the depth of all glyphs. Descender, } /// Defines how to resolve a `Bounds` text edge. #[derive(Debug, Copy, Clone)] pub enum TextEdgeBounds<'a> { /// Set the bounds to zero. Zero, /// Use the bounding box of the given glyph for the bounds. Glyph(u16), /// Use the dimension of the given frame for the bounds. Frame(&'a Frame), }