| 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 | |