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    /// The slant angle of the font.
104    ///
105    /// Returns `0.0` if no angle or this info is not provided.
106    ///
107    /// # Example
108    /// ```
109    /// # use ab_glyph::{Font, FontRef};
110    /// # fn main() -> Result<(), ab_glyph::InvalidFont> {
111    /// let italic_font =
112    ///     FontRef::try_from_slice(include_bytes!("../../dev/fonts/OpenSans-Italic.ttf"))?;
113    /// assert_eq!(italic_font.italic_angle(), -12.0);
114    ///
115    /// let other_font = FontRef::try_from_slice(include_bytes!("../../dev/fonts/Exo2-Light.otf"))?;
116    /// assert_eq!(other_font.italic_angle(), 0.0);
117    /// # Ok(()) }
118    /// ```
119    fn italic_angle(&self) -> f32 {
120        // Note: default impl prevents this method from breaking external
121        //       Font impls written before introduction
122        0.0
123    }
124
125    /// Lookup a `GlyphId` matching a given `char`.
126    ///
127    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
128    fn glyph_id(&self, c: char) -> GlyphId;
129
130    /// Unscaled horizontal advance for a given glyph id.
131    /// See [glyph layout concepts](Font#glyph-layout-concepts).
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 h_advance_unscaled(&self, id: GlyphId) -> f32;
137
138    /// Unscaled horizontal side bearing for a given glyph id.
139    /// See [glyph layout concepts](Font#glyph-layout-concepts).
140    ///
141    /// Returns `0.0` if the font does not define this value.
142    ///
143    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
144    fn h_side_bearing_unscaled(&self, id: GlyphId) -> f32;
145
146    /// Unscaled vertical advance for a given glyph id.
147    ///
148    /// Returns `0.0` if the font does not define this value.
149    ///
150    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
151    fn v_advance_unscaled(&self, id: GlyphId) -> f32;
152
153    /// Unscaled vertical side bearing for a given glyph id.
154    ///
155    /// Returns `0.0` if the font does not define this value.
156    ///
157    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
158    fn v_side_bearing_unscaled(&self, id: GlyphId) -> f32;
159
160    /// Returns additional unscaled kerning to apply for a particular pair of glyph ids.
161    ///
162    /// Scaling can be done with [`as_scaled`](Self::as_scaled).
163    fn kern_unscaled(&self, first: GlyphId, second: GlyphId) -> f32;
164
165    /// Compute unscaled glyph outline curves & bounding box.
166    fn outline(&self, id: GlyphId) -> Option<Outline>;
167
168    /// The number of glyphs present in this font. Glyph identifiers for this
169    /// font will always be in the range `0..self.glyph_count()`
170    fn glyph_count(&self) -> usize;
171
172    /// Returns an iterator of all distinct `(GlyphId, char)` pairs. Not ordered.
173    ///
174    /// # Example
175    /// ```
176    /// # use ab_glyph::{Font, FontRef, GlyphId};
177    /// # use std::collections::HashMap;
178    /// # fn main() -> Result<(), ab_glyph::InvalidFont> {
179    /// let font = FontRef::try_from_slice(include_bytes!("../../dev/fonts/Exo2-Light.otf"))?;
180    ///
181    /// // Iterate over pairs, each id will appear at most once.
182    /// let mut codepoint_ids = font.codepoint_ids();
183    /// assert_eq!(codepoint_ids.next(), Some((GlyphId(408), '\r')));
184    /// assert_eq!(codepoint_ids.next(), Some((GlyphId(1), ' ')));
185    /// assert_eq!(codepoint_ids.next(), Some((GlyphId(75), '!')));
186    ///
187    /// // Build a lookup map for all ids
188    /// let map: HashMap<_, _> = font.codepoint_ids().collect();
189    /// assert_eq!(map.get(&GlyphId(75)), Some(&'!'));
190    /// # assert_eq!(map.len(), 908);
191    /// # Ok(()) }
192    /// ```
193    fn codepoint_ids(&self) -> crate::CodepointIdIter<'_>;
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    #[allow(deprecated)]
203    #[deprecated(
204        since = "0.2.22",
205        note = "Deprecated in favor of `glyph_raster_image2`"
206    )]
207    fn glyph_raster_image(&self, id: GlyphId, pixel_size: u16) -> Option<crate::GlyphImage> {
208        self.glyph_raster_image2(id, pixel_size)
209            .map(|i| crate::GlyphImage {
210                origin: i.origin,
211                scale: i.pixels_per_em.into(),
212                data: i.data,
213                format: i.format,
214            })
215    }
216
217    /// Returns a pre-rendered image of the glyph.
218    ///
219    /// This is normally only present when an outline is not sufficient to describe the glyph, such
220    /// as emojis (particularly color ones).  The `pixel_size` parameter is in pixels per em, and will be
221    /// used to select between multiple possible images (if present); the returned image will
222    /// likely not match this value, requiring you to scale it to match the target resolution.
223    /// To get the largest image use `u16::MAX`.
224    fn glyph_raster_image2(&self, id: GlyphId, pixel_size: u16) -> Option<v2::GlyphImage>;
225
226    /// Returns raw SVG data of a range of glyphs which includes this one.
227    ///
228    /// Some fonts define their images as SVG rather than a raster format. SVG data here is raw and
229    /// should be rendered and/or decompressed by the caller, and scaled appropriately. The SVG file
230    /// might include a series of glyphs as nodes.
231    fn glyph_svg_image(&self, id: GlyphId) -> Option<GlyphSvg> {
232        _ = id;
233        None // Avoid breaking external Font impls.
234    }
235
236    /// Returns the layout bounds of this glyph.
237    ///
238    /// Horizontally: Glyph position +/- h_advance/h_side_bearing.
239    /// Vertically: Glyph position +/- ascent/descent.
240    ///
241    /// These are *not* the same as [`OutlinedGlyph::px_bounds`]. If you are drawing pixels
242    /// you should use `px_bounds` and not this method as outlines are not bound by layout
243    /// values.
244    #[inline]
245    fn glyph_bounds(&self, glyph: &Glyph) -> Rect
246    where
247        Self: Sized,
248    {
249        let sf = self.as_scaled(glyph.scale);
250        let pos = glyph.position;
251        Rect {
252            min: point(pos.x - sf.h_side_bearing(glyph.id), pos.y - sf.ascent()),
253            max: point(pos.x + sf.h_advance(glyph.id), pos.y - sf.descent()),
254        }
255    }
256
257    /// Compute glyph outline ready for drawing.
258    #[inline]
259    fn outline_glyph(&self, glyph: Glyph) -> Option<OutlinedGlyph>
260    where
261        Self: Sized,
262    {
263        let outline = self.outline(glyph.id)?;
264        let scale_factor = self.as_scaled(glyph.scale).scale_factor();
265        Some(OutlinedGlyph::new(glyph, outline, scale_factor))
266    }
267
268    /// Construct a [`PxScaleFont`] by associating with the given pixel `scale`.
269    ///
270    /// # Example
271    /// ```
272    /// # use ab_glyph::{Font, FontRef, PxScale, ScaleFont};
273    /// # fn main() -> Result<(), ab_glyph::InvalidFont> {
274    /// let font = FontRef::try_from_slice(include_bytes!("../../dev/fonts/Exo2-Light.otf"))?;
275    ///
276    /// assert_eq!(font.descent_unscaled(), -201.0);
277    ///
278    /// assert_eq!(font.as_scaled(24.0).descent(), -4.02);
279    /// assert_eq!(font.as_scaled(50.0).descent(), -8.375);
280    /// # Ok(()) }
281    /// ```
282    #[inline]
283    fn as_scaled<S: Into<PxScale>>(&self, scale: S) -> PxScaleFont<&'_ Self>
284    where
285        Self: Sized,
286    {
287        PxScaleFont {
288            font: self,
289            scale: scale.into(),
290        }
291    }
292
293    /// Move into a [`PxScaleFont`] associated with the given pixel `scale`.
294    #[inline]
295    fn into_scaled<S: Into<PxScale>>(self, scale: S) -> PxScaleFont<Self>
296    where
297        Self: core::marker::Sized,
298    {
299        PxScaleFont {
300            font: self,
301            scale: scale.into(),
302        }
303    }
304
305    /// Extracts a slice containing the data passed into e.g. [`FontArc::try_from_slice`].
306    ///
307    /// # Example
308    /// ```
309    /// # use ab_glyph::*;
310    /// # fn main() -> Result<(), InvalidFont> {
311    /// # let owned_font_data = include_bytes!("../../dev/fonts/Exo2-Light.otf");
312    /// let font = FontArc::try_from_slice(owned_font_data)?;
313    /// assert_eq!(font.font_data(), owned_font_data);
314    /// # Ok(()) }
315    /// ```
316    ///
317    /// [`FontArc::try_from_slice`]: crate::FontArc::try_from_slice
318    #[inline]
319    fn font_data(&self) -> &[u8] {
320        // panic impl prevents this method from breaking external Font impls
321        unimplemented!()
322    }
323}
324
325impl<F: Font> Font for &F {
326    #[inline]
327    fn units_per_em(&self) -> Option<f32> {
328        (*self).units_per_em()
329    }
330
331    #[inline]
332    fn ascent_unscaled(&self) -> f32 {
333        (*self).ascent_unscaled()
334    }
335
336    #[inline]
337    fn descent_unscaled(&self) -> f32 {
338        (*self).descent_unscaled()
339    }
340
341    #[inline]
342    fn line_gap_unscaled(&self) -> f32 {
343        (*self).line_gap_unscaled()
344    }
345
346    #[inline]
347    fn italic_angle(&self) -> f32 {
348        (*self).italic_angle()
349    }
350
351    #[inline]
352    fn glyph_id(&self, c: char) -> GlyphId {
353        (*self).glyph_id(c)
354    }
355
356    #[inline]
357    fn h_advance_unscaled(&self, id: GlyphId) -> f32 {
358        (*self).h_advance_unscaled(id)
359    }
360
361    #[inline]
362    fn h_side_bearing_unscaled(&self, id: GlyphId) -> f32 {
363        (*self).h_side_bearing_unscaled(id)
364    }
365
366    #[inline]
367    fn v_advance_unscaled(&self, id: GlyphId) -> f32 {
368        (*self).v_advance_unscaled(id)
369    }
370
371    #[inline]
372    fn v_side_bearing_unscaled(&self, id: GlyphId) -> f32 {
373        (*self).v_side_bearing_unscaled(id)
374    }
375
376    #[inline]
377    fn kern_unscaled(&self, first: GlyphId, second: GlyphId) -> f32 {
378        (*self).kern_unscaled(first, second)
379    }
380
381    #[inline]
382    fn outline(&self, glyph: GlyphId) -> Option<Outline> {
383        (*self).outline(glyph)
384    }
385
386    #[inline]
387    fn glyph_count(&self) -> usize {
388        (*self).glyph_count()
389    }
390
391    #[inline]
392    fn codepoint_ids(&self) -> crate::CodepointIdIter<'_> {
393        (*self).codepoint_ids()
394    }
395
396    #[inline]
397    fn glyph_raster_image2(&self, id: GlyphId, size: u16) -> Option<v2::GlyphImage> {
398        (*self).glyph_raster_image2(id, size)
399    }
400
401    #[inline]
402    fn glyph_svg_image(&self, id: GlyphId) -> Option<GlyphSvg> {
403        (*self).glyph_svg_image(id)
404    }
405
406    #[inline]
407    fn font_data(&self) -> &[u8] {
408        (*self).font_data()
409    }
410}