1 | use 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 | /// ``` |
39 | pub 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 | |
303 | impl<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 | } |
384 | |