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}