typst_library/text/font/
mod.rs

1//! Font handling.
2
3pub mod color;
4
5mod book;
6mod exceptions;
7mod variant;
8
9pub use self::book::{Coverage, FontBook, FontFlags, FontInfo};
10pub use self::variant::{FontStretch, FontStyle, FontVariant, FontWeight};
11
12use std::cell::OnceCell;
13use std::fmt::{self, Debug, Formatter};
14use std::hash::{Hash, Hasher};
15use std::sync::Arc;
16
17use ttf_parser::GlyphId;
18
19use self::book::find_name;
20use crate::foundations::{Bytes, Cast};
21use crate::layout::{Abs, Em, Frame};
22use crate::text::{BottomEdge, TopEdge};
23
24/// An OpenType font.
25///
26/// Values of this type are cheap to clone and hash.
27#[derive(Clone)]
28pub struct Font(Arc<Repr>);
29
30/// The internal representation of a font.
31struct Repr {
32    /// The font's index in the buffer.
33    index: u32,
34    /// Metadata about the font.
35    info: FontInfo,
36    /// The font's metrics.
37    metrics: FontMetrics,
38    /// The underlying ttf-parser face.
39    ttf: ttf_parser::Face<'static>,
40    /// The underlying rustybuzz face.
41    rusty: rustybuzz::Face<'static>,
42    // NOTE: `ttf` and `rusty` reference `data`, so it's important for `data`
43    // to be dropped after them or they will be left dangling while they're
44    // dropped. Fields are dropped in declaration order, so `data` needs to be
45    // declared after `ttf` and `rusty`.
46    /// The raw font data, possibly shared with other fonts from the same
47    /// collection. The vector's allocation must not move, because `ttf` points
48    /// into it using unsafe code.
49    data: Bytes,
50}
51
52impl Font {
53    /// Parse a font from data and collection index.
54    pub fn new(data: Bytes, index: u32) -> Option<Self> {
55        // Safety:
56        // - The slices's location is stable in memory:
57        //   - We don't move the underlying vector
58        //   - Nobody else can move it since we have a strong ref to the `Arc`.
59        // - The internal 'static lifetime is not leaked because its rewritten
60        //   to the self-lifetime in `ttf()`.
61        let slice: &'static [u8] =
62            unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
63
64        let ttf = ttf_parser::Face::parse(slice, index).ok()?;
65        let rusty = rustybuzz::Face::from_slice(slice, index)?;
66        let metrics = FontMetrics::from_ttf(&ttf);
67        let info = FontInfo::from_ttf(&ttf)?;
68
69        Some(Self(Arc::new(Repr { data, index, info, metrics, ttf, rusty })))
70    }
71
72    /// Parse all fonts in the given data.
73    pub fn iter(data: Bytes) -> impl Iterator<Item = Self> {
74        let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
75        (0..count).filter_map(move |index| Self::new(data.clone(), index))
76    }
77
78    /// The underlying buffer.
79    pub fn data(&self) -> &Bytes {
80        &self.0.data
81    }
82
83    /// The font's index in the buffer.
84    pub fn index(&self) -> u32 {
85        self.0.index
86    }
87
88    /// The font's metadata.
89    pub fn info(&self) -> &FontInfo {
90        &self.0.info
91    }
92
93    /// The font's metrics.
94    pub fn metrics(&self) -> &FontMetrics {
95        &self.0.metrics
96    }
97
98    /// The number of font units per one em.
99    pub fn units_per_em(&self) -> f64 {
100        self.0.metrics.units_per_em
101    }
102
103    /// Convert from font units to an em length.
104    pub fn to_em(&self, units: impl Into<f64>) -> Em {
105        Em::from_units(units, self.units_per_em())
106    }
107
108    /// Look up the horizontal advance width of a glyph.
109    pub fn advance(&self, glyph: u16) -> Option<Em> {
110        self.0
111            .ttf
112            .glyph_hor_advance(GlyphId(glyph))
113            .map(|units| self.to_em(units))
114    }
115
116    /// Lookup a name by id.
117    pub fn find_name(&self, id: u16) -> Option<String> {
118        find_name(&self.0.ttf, id)
119    }
120
121    /// A reference to the underlying `ttf-parser` face.
122    pub fn ttf(&self) -> &ttf_parser::Face<'_> {
123        // We can't implement Deref because that would leak the
124        // internal 'static lifetime.
125        &self.0.ttf
126    }
127
128    /// A reference to the underlying `rustybuzz` face.
129    pub fn rusty(&self) -> &rustybuzz::Face<'_> {
130        // We can't implement Deref because that would leak the
131        // internal 'static lifetime.
132        &self.0.rusty
133    }
134
135    /// Resolve the top and bottom edges of text.
136    pub fn edges(
137        &self,
138        top_edge: TopEdge,
139        bottom_edge: BottomEdge,
140        font_size: Abs,
141        bounds: TextEdgeBounds,
142    ) -> (Abs, Abs) {
143        let cell = OnceCell::new();
144        let bbox = |gid, f: fn(ttf_parser::Rect) -> i16| {
145            cell.get_or_init(|| self.ttf().glyph_bounding_box(GlyphId(gid)))
146                .map(|bbox| self.to_em(f(bbox)).at(font_size))
147                .unwrap_or_default()
148        };
149
150        let top = match top_edge {
151            TopEdge::Metric(metric) => match metric.try_into() {
152                Ok(metric) => self.metrics().vertical(metric).at(font_size),
153                Err(_) => match bounds {
154                    TextEdgeBounds::Zero => Abs::zero(),
155                    TextEdgeBounds::Frame(frame) => frame.ascent(),
156                    TextEdgeBounds::Glyph(gid) => bbox(gid, |b| b.y_max),
157                },
158            },
159            TopEdge::Length(length) => length.at(font_size),
160        };
161
162        let bottom = match bottom_edge {
163            BottomEdge::Metric(metric) => match metric.try_into() {
164                Ok(metric) => -self.metrics().vertical(metric).at(font_size),
165                Err(_) => match bounds {
166                    TextEdgeBounds::Zero => Abs::zero(),
167                    TextEdgeBounds::Frame(frame) => frame.descent(),
168                    TextEdgeBounds::Glyph(gid) => -bbox(gid, |b| b.y_min),
169                },
170            },
171            BottomEdge::Length(length) => -length.at(font_size),
172        };
173
174        (top, bottom)
175    }
176}
177
178impl Hash for Font {
179    fn hash<H: Hasher>(&self, state: &mut H) {
180        self.0.data.hash(state);
181        self.0.index.hash(state);
182    }
183}
184
185impl Debug for Font {
186    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
187        write!(f, "Font({}, {:?})", self.info().family, self.info().variant)
188    }
189}
190
191impl Eq for Font {}
192
193impl PartialEq for Font {
194    fn eq(&self, other: &Self) -> bool {
195        self.0.data == other.0.data && self.0.index == other.0.index
196    }
197}
198
199/// Metrics of a font.
200#[derive(Debug, Copy, Clone)]
201pub struct FontMetrics {
202    /// How many font units represent one em unit.
203    pub units_per_em: f64,
204    /// The distance from the baseline to the typographic ascender.
205    pub ascender: Em,
206    /// The approximate height of uppercase letters.
207    pub cap_height: Em,
208    /// The approximate height of non-ascending lowercase letters.
209    pub x_height: Em,
210    /// The distance from the baseline to the typographic descender.
211    pub descender: Em,
212    /// Recommended metrics for a strikethrough line.
213    pub strikethrough: LineMetrics,
214    /// Recommended metrics for an underline.
215    pub underline: LineMetrics,
216    /// Recommended metrics for an overline.
217    pub overline: LineMetrics,
218}
219
220impl FontMetrics {
221    /// Extract the font's metrics.
222    pub fn from_ttf(ttf: &ttf_parser::Face) -> Self {
223        let units_per_em = f64::from(ttf.units_per_em());
224        let to_em = |units| Em::from_units(units, units_per_em);
225
226        let ascender = to_em(ttf.typographic_ascender().unwrap_or(ttf.ascender()));
227        let cap_height = ttf.capital_height().filter(|&h| h > 0).map_or(ascender, to_em);
228        let x_height = ttf.x_height().filter(|&h| h > 0).map_or(ascender, to_em);
229        let descender = to_em(ttf.typographic_descender().unwrap_or(ttf.descender()));
230        let strikeout = ttf.strikeout_metrics();
231        let underline = ttf.underline_metrics();
232
233        let strikethrough = LineMetrics {
234            position: strikeout.map_or(Em::new(0.25), |s| to_em(s.position)),
235            thickness: strikeout
236                .or(underline)
237                .map_or(Em::new(0.06), |s| to_em(s.thickness)),
238        };
239
240        let underline = LineMetrics {
241            position: underline.map_or(Em::new(-0.2), |s| to_em(s.position)),
242            thickness: underline
243                .or(strikeout)
244                .map_or(Em::new(0.06), |s| to_em(s.thickness)),
245        };
246
247        let overline = LineMetrics {
248            position: cap_height + Em::new(0.1),
249            thickness: underline.thickness,
250        };
251
252        Self {
253            units_per_em,
254            ascender,
255            cap_height,
256            x_height,
257            descender,
258            strikethrough,
259            underline,
260            overline,
261        }
262    }
263
264    /// Look up a vertical metric.
265    pub fn vertical(&self, metric: VerticalFontMetric) -> Em {
266        match metric {
267            VerticalFontMetric::Ascender => self.ascender,
268            VerticalFontMetric::CapHeight => self.cap_height,
269            VerticalFontMetric::XHeight => self.x_height,
270            VerticalFontMetric::Baseline => Em::zero(),
271            VerticalFontMetric::Descender => self.descender,
272        }
273    }
274}
275
276/// Metrics for a decorative line.
277#[derive(Debug, Copy, Clone)]
278pub struct LineMetrics {
279    /// The vertical offset of the line from the baseline. Positive goes
280    /// upwards, negative downwards.
281    pub position: Em,
282    /// The thickness of the line.
283    pub thickness: Em,
284}
285
286/// Identifies a vertical metric of a font.
287#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
288pub enum VerticalFontMetric {
289    /// The font's ascender, which typically exceeds the height of all glyphs.
290    Ascender,
291    /// The approximate height of uppercase letters.
292    CapHeight,
293    /// The approximate height of non-ascending lowercase letters.
294    XHeight,
295    /// The baseline on which the letters rest.
296    Baseline,
297    /// The font's ascender, which typically exceeds the depth of all glyphs.
298    Descender,
299}
300
301/// Defines how to resolve a `Bounds` text edge.
302#[derive(Debug, Copy, Clone)]
303pub enum TextEdgeBounds<'a> {
304    /// Set the bounds to zero.
305    Zero,
306    /// Use the bounding box of the given glyph for the bounds.
307    Glyph(u16),
308    /// Use the dimension of the given frame for the bounds.
309    Frame(&'a Frame),
310}