ab_glyph/
font.rs

1use crate::{
2    point, v2, Glyph, GlyphId, GlyphSvg, Outline, OutlinedGlyph, PxScale, PxScaleFont, Rect,
3    ScaleFont,
4};
5
6/// Functionality required from font data.
7///
8/// See also [`FontArc`](crate::FontArc), [`FontRef`](crate::FontRef)
9/// and [`FontVec`](crate::FontVec).
10///
11/// ## Units
12///
13/// Units of unscaled accessors are "font units", which is an arbitrary unit
14/// defined by the font. See [`Font::units_per_em`].
15///
16/// ab_glyph uses a non-standard scale [`PxScale`] which is the pixel height
17/// of the text. See [`Font::pt_to_px_scale`] to convert standard point sizes.
18///
19/// ## Glyph layout concepts
20/// Fonts provide several properties to inform layout of glyphs.
21/// ```text
22///          ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
23///                   |  .:x++++==              |
24///                   | .#+                     |
25///                   | :@            =++=++x=: |
26///            ascent | +#       x:  +x     x+  |
27///                   | =#       #:  :#:---:#:  | height
28///                   | -@-      #:  .#--:--    |
29///                   |  =#:-.-==#:   #x+===:.  |
30/// baseline ____________ .-::-. ..  #:    .:@. |
31///                   |              #+--..-=#. |
32///           descent |               -::=::-   |
33///          ____________________________________
34///                 | |             |           | line_gap
35///                 | |  h_advance  |           ‾
36///                  ^                      
37///            h_side_bearing
38/// ```
39pub trait Font {
40    /// Get the size of the font unit
41    ///
42    /// This returns "font units per em", where 1em is a base unit of font scale
43    /// (typically the width of a capital 'M').
44    ///
45    /// Returns `None` in case the font unit size exceeds the expected range.
46    /// See [`Face::units_per_em`](https://docs.rs/ttf-parser/latest/ttf_parser/struct.Face.html#method.units_per_em).
47    ///
48    /// May be used to calculate [`PxScale`] from pt size, see [`Font::pt_to_px_scale`].
49    fn units_per_em(&self) -> Option<f32>;
50
51    /// Converts pt units into [`PxScale`].
52    ///
53    /// Note: To handle a screen scale factor multiply it to the `pt_size` argument.
54    ///
55    /// Returns `None` in case the [`Font::units_per_em`] unit size exceeds the expected range.
56    ///
57    /// ## Point size (pt)
58    ///
59    /// Font sizes are typically specified in "points". According to the modern
60    /// standard, 1pt = 1/72in. The "point size" of a font is the number of points
61    /// per em.
62    ///
63    /// The DPI (dots-per-inch) of a screen depends on the screen in question;
64    /// 96 DPI is often considered the "standard". For high-DPI displays the
65    /// DPI may be specified directly or one may multiply 96 by a scale-factor.
66    ///
67    /// Thus, for example, a 10pt font on a 96 pixels-per-inch display has
68    /// 10 / 72 * 96 = 13.333... pixels-per-em. If we divide this number by
69    /// `units_per_em` we then get a scaling factor: pixels-per-font-unit.
70    ///
71    /// Note however that since [`PxScale`] values are relative to the text height,
72    /// one further step is needed: multiply by [`Font::height_unscaled`].
73    fn pt_to_px_scale(&self, pt_size: f32) -> Option<PxScale> {
74        let px_per_em = pt_size * (96.0 / 72.0);
75        let units_per_em = self.units_per_em()?;
76        let height = self.height_unscaled();
77        Some(PxScale::from(px_per_em * height / units_per_em))
78    }
79
80    /// Unscaled glyph ascent. See [glyph layout concepts](Font#glyph-layout-concepts).
81    ///
82    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
83    fn ascent_unscaled(&self) -> f32;
84
85    /// Unscaled glyph descent. See [glyph layout concepts](Font#glyph-layout-concepts).
86    ///
87    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
88    fn descent_unscaled(&self) -> f32;
89
90    /// Unscaled height `ascent - descent`. See [glyph layout concepts](Font#glyph-layout-concepts).
91    ///
92    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
93    #[inline]
94    fn height_unscaled(&self) -> f32 {
95        self.ascent_unscaled() - self.descent_unscaled()
96    }
97
98    /// Unscaled line gap. See [glyph layout concepts](Font#glyph-layout-concepts).
99    ///
100    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
101    fn line_gap_unscaled(&self) -> f32;
102
103    /// Lookup a `GlyphId` matching a given `char`.
104    ///
105    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
106    fn glyph_id(&self, c: char) -> GlyphId;
107
108    /// Unscaled horizontal advance for a given glyph id.
109    /// See [glyph layout concepts](Font#glyph-layout-concepts).
110    ///
111    /// Returns `0.0` if the font does not define this value.
112    ///
113    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
114    fn h_advance_unscaled(&self, id: GlyphId) -> f32;
115
116    /// Unscaled horizontal side bearing for a given glyph id.
117    /// See [glyph layout concepts](Font#glyph-layout-concepts).
118    ///
119    /// Returns `0.0` if the font does not define this value.
120    ///
121    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
122    fn h_side_bearing_unscaled(&self, id: GlyphId) -> f32;
123
124    /// Unscaled vertical advance for a given glyph id.
125    ///
126    /// Returns `0.0` if the font does not define this value.
127    ///
128    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
129    fn v_advance_unscaled(&self, id: GlyphId) -> f32;
130
131    /// Unscaled vertical side bearing for a given glyph id.
132    ///
133    /// Returns `0.0` if the font does not define this value.
134    ///
135    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
136    fn v_side_bearing_unscaled(&self, id: GlyphId) -> f32;
137
138    /// Returns additional unscaled kerning to apply for a particular pair of glyph ids.
139    ///
140    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
141    fn kern_unscaled(&self, first: GlyphId, second: GlyphId) -> f32;
142
143    /// Compute unscaled glyph outline curves & bounding box.
144    fn outline(&self, id: GlyphId) -> Option<Outline>;
145
146    /// The number of glyphs present in this font. Glyph identifiers for this
147    /// font will always be in the range `0..self.glyph_count()`
148    fn glyph_count(&self) -> usize;
149
150    /// Returns an iterator of all distinct `(GlyphId, char)` pairs. Not ordered.
151    ///
152    /// # Example
153    /// ```
154    /// # use ab_glyph::{Font, FontRef, GlyphId};
155    /// # use std::collections::HashMap;
156    /// # fn main() -> Result<(), ab_glyph::InvalidFont> {
157    /// let font = FontRef::try_from_slice(include_bytes!("../../dev/fonts/Exo2-Light.otf"))?;
158    ///
159    /// // Iterate over pairs, each id will appear at most once.
160    /// let mut codepoint_ids = font.codepoint_ids();
161    /// assert_eq!(codepoint_ids.next(), Some((GlyphId(408), '\r')));
162    /// assert_eq!(codepoint_ids.next(), Some((GlyphId(1), ' ')));
163    /// assert_eq!(codepoint_ids.next(), Some((GlyphId(75), '!')));
164    ///
165    /// // Build a lookup map for all ids
166    /// let map: HashMap<_, _> = font.codepoint_ids().collect();
167    /// assert_eq!(map.get(&GlyphId(75)), Some(&'!'));
168    /// # assert_eq!(map.len(), 908);
169    /// # Ok(()) }
170    /// ```
171    fn codepoint_ids(&self) -> crate::CodepointIdIter<'_>;
172
173    /// Returns a pre-rendered image of the glyph.
174    ///
175    /// This is normally only present when an outline is not sufficient to describe the glyph, such
176    /// as emojis (particularly color ones).  The `pixel_size` parameter is in pixels per em, and will be
177    /// used to select between multiple possible images (if present); the returned image will
178    /// likely not match this value, requiring you to scale it to match the target resolution.
179    /// To get the largest image use `u16::MAX`.
180    #[allow(deprecated)]
181    #[deprecated(
182        since = "0.2.22",
183        note = "Deprecated in favor of `glyph_raster_image2`"
184    )]
185    fn glyph_raster_image(&self, id: GlyphId, pixel_size: u16) -> Option<crate::GlyphImage> {
186        self.glyph_raster_image2(id, pixel_size)
187            .map(|i| crate::GlyphImage {
188                origin: i.origin,
189                scale: i.pixels_per_em.into(),
190                data: i.data,
191                format: i.format,
192            })
193    }
194
195    /// Returns a pre-rendered image of the glyph.
196    ///
197    /// This is normally only present when an outline is not sufficient to describe the glyph, such
198    /// as emojis (particularly color ones).  The `pixel_size` parameter is in pixels per em, and will be
199    /// used to select between multiple possible images (if present); the returned image will
200    /// likely not match this value, requiring you to scale it to match the target resolution.
201    /// To get the largest image use `u16::MAX`.
202    fn glyph_raster_image2(&self, id: GlyphId, pixel_size: u16) -> Option<v2::GlyphImage>;
203
204    /// Returns raw SVG data of a range of glyphs which includes this one.
205    ///
206    /// Some fonts define their images as SVG rather than a raster format. SVG data here is raw and
207    /// should be rendered and/or decompressed by the caller, and scaled appropriately. The SVG file
208    /// might include a series of glyphs as nodes.
209    fn glyph_svg_image(&self, id: GlyphId) -> Option<GlyphSvg> {
210        _ = id;
211        None // Avoid breaking external Font impls.
212    }
213
214    /// Returns the layout bounds of this glyph.
215    ///
216    /// Horizontally: Glyph position +/- h_advance/h_side_bearing.
217    /// Vertically: Glyph position +/- ascent/descent.
218    ///
219    /// These are *not* the same as [`OutlinedGlyph::px_bounds`]. If you are drawing pixels
220    /// you should use `px_bounds` and not this method as outlines are not bound by layout
221    /// values.
222    #[inline]
223    fn glyph_bounds(&self, glyph: &Glyph) -> Rect
224    where
225        Self: Sized,
226    {
227        let sf = self.as_scaled(glyph.scale);
228        let pos = glyph.position;
229        Rect {
230            min: point(pos.x - sf.h_side_bearing(glyph.id), pos.y - sf.ascent()),
231            max: point(pos.x + sf.h_advance(glyph.id), pos.y - sf.descent()),
232        }
233    }
234
235    /// Compute glyph outline ready for drawing.
236    #[inline]
237    fn outline_glyph(&self, glyph: Glyph) -> Option<OutlinedGlyph>
238    where
239        Self: Sized,
240    {
241        let outline = self.outline(glyph.id)?;
242        let scale_factor = self.as_scaled(glyph.scale).scale_factor();
243        Some(OutlinedGlyph::new(glyph, outline, scale_factor))
244    }
245
246    /// Construct a [`PxScaleFont`] by associating with the given pixel `scale`.
247    ///
248    /// # Example
249    /// ```
250    /// # use ab_glyph::{Font, FontRef, PxScale, ScaleFont};
251    /// # fn main() -> Result<(), ab_glyph::InvalidFont> {
252    /// let font = FontRef::try_from_slice(include_bytes!("../../dev/fonts/Exo2-Light.otf"))?;
253    ///
254    /// assert_eq!(font.descent_unscaled(), -201.0);
255    ///
256    /// assert_eq!(font.as_scaled(24.0).descent(), -4.02);
257    /// assert_eq!(font.as_scaled(50.0).descent(), -8.375);
258    /// # Ok(()) }
259    /// ```
260    #[inline]
261    fn as_scaled<S: Into<PxScale>>(&self, scale: S) -> PxScaleFont<&'_ Self>
262    where
263        Self: Sized,
264    {
265        PxScaleFont {
266            font: self,
267            scale: scale.into(),
268        }
269    }
270
271    /// Move into a [`PxScaleFont`] associated with the given pixel `scale`.
272    #[inline]
273    fn into_scaled<S: Into<PxScale>>(self, scale: S) -> PxScaleFont<Self>
274    where
275        Self: core::marker::Sized,
276    {
277        PxScaleFont {
278            font: self,
279            scale: scale.into(),
280        }
281    }
282
283    /// Extracts a slice containing the data passed into e.g. [`FontArc::try_from_slice`].
284    ///
285    /// # Example
286    /// ```
287    /// # use ab_glyph::*;
288    /// # fn main() -> Result<(), InvalidFont> {
289    /// # let owned_font_data = include_bytes!("../../dev/fonts/Exo2-Light.otf");
290    /// let font = FontArc::try_from_slice(owned_font_data)?;
291    /// assert_eq!(font.font_data(), owned_font_data);
292    /// # Ok(()) }
293    /// ```
294    ///
295    /// [`FontArc::try_from_slice`]: crate::FontArc::try_from_slice
296    #[inline]
297    fn font_data(&self) -> &[u8] {
298        // panic impl prevents this method from breaking external Font impls
299        unimplemented!()
300    }
301}
302
303impl<F: Font> Font for &F {
304    #[inline]
305    fn units_per_em(&self) -> Option<f32> {
306        (*self).units_per_em()
307    }
308
309    #[inline]
310    fn ascent_unscaled(&self) -> f32 {
311        (*self).ascent_unscaled()
312    }
313
314    #[inline]
315    fn descent_unscaled(&self) -> f32 {
316        (*self).descent_unscaled()
317    }
318
319    #[inline]
320    fn line_gap_unscaled(&self) -> f32 {
321        (*self).line_gap_unscaled()
322    }
323
324    #[inline]
325    fn glyph_id(&self, c: char) -> GlyphId {
326        (*self).glyph_id(c)
327    }
328
329    #[inline]
330    fn h_advance_unscaled(&self, id: GlyphId) -> f32 {
331        (*self).h_advance_unscaled(id)
332    }
333
334    #[inline]
335    fn h_side_bearing_unscaled(&self, id: GlyphId) -> f32 {
336        (*self).h_side_bearing_unscaled(id)
337    }
338
339    #[inline]
340    fn v_advance_unscaled(&self, id: GlyphId) -> f32 {
341        (*self).v_advance_unscaled(id)
342    }
343
344    #[inline]
345    fn v_side_bearing_unscaled(&self, id: GlyphId) -> f32 {
346        (*self).v_side_bearing_unscaled(id)
347    }
348
349    #[inline]
350    fn kern_unscaled(&self, first: GlyphId, second: GlyphId) -> f32 {
351        (*self).kern_unscaled(first, second)
352    }
353
354    #[inline]
355    fn outline(&self, glyph: GlyphId) -> Option<Outline> {
356        (*self).outline(glyph)
357    }
358
359    #[inline]
360    fn glyph_count(&self) -> usize {
361        (*self).glyph_count()
362    }
363
364    #[inline]
365    fn codepoint_ids(&self) -> crate::CodepointIdIter<'_> {
366        (*self).codepoint_ids()
367    }
368
369    #[inline]
370    fn glyph_raster_image2(&self, id: GlyphId, size: u16) -> Option<v2::GlyphImage> {
371        (*self).glyph_raster_image2(id, size)
372    }
373
374    #[inline]
375    fn glyph_svg_image(&self, id: GlyphId) -> Option<GlyphSvg> {
376        (*self).glyph_svg_image(id)
377    }
378
379    #[inline]
380    fn font_data(&self) -> &[u8] {
381        (*self).font_data()
382    }
383}