blockworx_text/
metrics.rs1use blockworx_paint::{Font, FontChoice};
4
5#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct Metrics {
14 units_per_em: f32,
15 ascent: f32,
16 descent: f32,
17 line_gap: f32,
18}
19
20impl Metrics {
21 #[expect(clippy::expect_used)]
25 #[must_use]
26 pub fn of(typeface: FontChoice) -> Self {
27 use skrifa::MetadataProvider as _;
28 let font =
29 skrifa::FontRef::from_index(typeface.bytes(), 0).expect("embedded font is valid");
30 let metrics = font.metrics(
31 skrifa::instance::Size::unscaled(),
32 skrifa::instance::LocationRef::default(),
33 );
34 Self {
35 units_per_em: f32::from(metrics.units_per_em),
36 ascent: metrics.ascent,
37 descent: metrics.descent,
38 line_gap: metrics.leading,
39 }
40 }
41
42 #[must_use]
43 pub fn units_per_em(self) -> f32 {
44 self.units_per_em
45 }
46
47 #[must_use]
49 pub fn scale(self, font: &Font) -> f32 {
50 font.size / self.units_per_em
51 }
52
53 #[must_use]
55 pub fn ascent(self, font: &Font) -> f32 {
56 self.ascent * self.scale(font)
57 }
58
59 #[must_use]
62 pub fn descent(self, font: &Font) -> f32 {
63 self.descent * self.scale(font)
64 }
65
66 #[must_use]
68 pub fn row_height(self, font: &Font) -> f32 {
69 (self.ascent - self.descent + self.line_gap) * self.scale(font)
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn every_bundled_face_has_a_positive_em_and_an_ascending_baseline() {
79 for choice in FontChoice::ALL {
80 let metrics = Metrics::of(choice);
81 let font = Font::canvas(12.0);
82 assert!(
83 metrics.units_per_em() > 0.0,
84 "{} has no em square",
85 choice.display_name()
86 );
87 assert!(
88 metrics.ascent(&font) > 0.0 && metrics.descent(&font) < 0.0,
89 "{} ascends above and descends below the baseline",
90 choice.display_name()
91 );
92 assert!(
93 metrics.row_height(&font) >= metrics.ascent(&font) - metrics.descent(&font),
94 "{} rows are at least the face's extent tall",
95 choice.display_name()
96 );
97 }
98 }
99
100 #[test]
101 fn metrics_scale_linearly_with_the_font_size() {
102 let metrics = Metrics::of(FontChoice::Basic);
103 let small = metrics.row_height(&Font::canvas(10.0));
104 let large = metrics.row_height(&Font::canvas(20.0));
105 assert!((large - 2.0 * small).abs() < 1e-3, "{small} {large}");
106 }
107}