Skip to main content

ttf_view/tables/
hmtx.rs

1use crate::{
2    tables::{Table, TableDirectory, TableError, cmap::GlyphId},
3    types::{FWORD, Tag, UFWORD, tags},
4};
5
6// Note: HmtxRaw can't provide anything on its own. We need data from two other tables:
7// `number_of_h_metrics` from 'hhea' and `num_glyphs` from 'maxp' to slice the data correctly.
8#[repr(C)]
9struct HmtxRaw {
10    /// Note: It's a little bit faster to work with `&[FWORD]` than with separately typed slices.
11    /// See [`Hmtx::metric`] method for explanation.
12    raw_words: [FWORD; 0],
13    // : h_metrics: [LongHorMetricRaw; hhea().num_h_metrics],
14    // : left_side_bearings: [FWORD; maxp().num_glyphs - hhea().num_h_metrics],
15}
16
17#[repr(C)]
18pub struct LongHorMetricRaw {
19    pub advance_width: UFWORD,
20    pub lsb: FWORD,
21}
22
23impl<'a> Table<'a> for Hmtx<'a> {
24    const TAG: Tag = tags::hmtx;
25    fn new_in(dir: &'a TableDirectory) -> Result<Self, TableError> {
26        let rec = dir.table_record(Self::TAG).ok_or(TableError::NotFound)?;
27        let raw_words = rec.raw_as::<HmtxRaw>().unwrap().raw_words.as_ptr();
28
29        let num_h_metrics = dir
30            .hhea()
31            .map_err(|_| TableError::Dependency(tags::hhea))?
32            .number_of_h_metrics()
33            .ok_or(TableError::DependencyError(&"number_of_h_metrics not found"))?
34            .get() as usize;
35
36        let num_glyphs = dir
37            .maxp()
38            .map_err(|_| TableError::Dependency(tags::maxp))?
39            .num_glyphs()
40            .ok_or(TableError::DependencyError(&"num_glyphs not found"))?
41            .get() as usize;
42
43        let total_word_count = num_h_metrics + num_glyphs;
44        let raw_words = unsafe { std::slice::from_raw_parts(raw_words, total_word_count) };
45
46        if (rec.length.get() as usize) < total_word_count * size_of::<FWORD>() {
47            return Err(TableError::InvalidLen);
48        }
49
50        Ok(Self { raw_words, num_h_metrics })
51    }
52}
53
54#[derive(Copy)]
55#[derive_const(Clone)]
56pub struct Hmtx<'a> {
57    raw_words: &'a [FWORD],
58    num_h_metrics: usize,
59}
60
61#[derive(Debug, Copy, Hash)]
62#[derive_const(Clone, Default, PartialEq, Eq)]
63pub struct LongHorMetric {
64    pub aw: u16,
65    pub lsb: i16,
66}
67
68impl LongHorMetric {
69    pub const fn new(aw: u16, lsb: i16) -> Self {
70        Self { aw, lsb }
71    }
72}
73const impl From<&LongHorMetricRaw> for LongHorMetric {
74    fn from(value: &LongHorMetricRaw) -> Self {
75        Self { aw: value.advance_width.get(), lsb: value.lsb.get() }
76    }
77}
78
79impl<'a> Hmtx<'a> {
80    pub const fn num_h_metrics(&self) -> u16 {
81        self.num_h_metrics as u16
82    }
83    pub const fn num_glyphs(&self) -> u16 {
84        (self.raw_words.len() - self.num_h_metrics) as u16
85    }
86
87    const fn h_metrics(&self) -> &'a [LongHorMetricRaw] {
88        unsafe { std::slice::from_raw_parts(self.raw_words.as_ptr().cast(), self.num_h_metrics) }
89    }
90
91    pub const fn last_advance_width(&self) -> Option<u16> {
92        Some(self.h_metrics().last()?.advance_width.get())
93    }
94
95    pub const fn metric(&self, glyph_id: GlyphId) -> Option<LongHorMetric> {
96        /// Normally, you'd check if you need to access h_metrics() or lsbs(), and then either:
97        /// a) get both values from h_metrics(), or b) get lsb from lsbs(), and also maybe get
98        /// the advance from h_metrics().last(), with both of these operations involving bounds
99        /// checks. That's a total of 3 branches!
100        ///
101        /// But there's a way to combine 2 of them, leaving only 2 bounds checks:
102        ///
103        /// ```rs
104        /// idx <= hcount-1 {
105        ///     let min = idx;
106        ///     // (idx*2, idx*2+1)
107        ///     // (idx*2, idx+idx+1)
108        ///     (min*2, min+idx+1)
109        /// }
110        /// idx > hcount-1 {
111        ///     let min = hcount-1;
112        ///     // ((hcount-1)*2, (hcount*2)+(idx-hcount))
113        ///     // ((hcount-1)*2, (hcount-1)+idx+1)
114        ///     (min*2, min+idx+1)
115        /// }
116        ///
117        /// // No branching! 😎 (compiles to asm 'cmp, cmovge')
118        /// let min = idx.min(hcount-1);
119        /// (min*2, min+idx+1)
120        /// ```
121        ///
122        /// Now we only have 2 bounds checks: one to ensure the glyph is in range of this cmap,
123        /// and another checking if `hcount` is 0 - the only scenario in which `min` would be -1,
124        /// out of range. That, of course, would mean than `min` in `min+idx+1` is `-1` too, but
125        /// that's okay, - `min+1` would wrap around to 0, and all that'd remain would be `idx`.
126        ///
127        struct _CodeExplanation;
128
129        let idx: usize = glyph_id.into();
130        // Do the comparison as `isize`, to ensure that `-1` from `hcount-1` goes through to `min`
131        let min = (idx as isize).min(self.num_h_metrics.wrapping_sub(1) as isize) as usize;
132
133        Some(LongHorMetric {
134            // Do a bounds check on min+idx+1 to check if this glyph is even represented here
135            lsb: self.raw_words.get(min.wrapping_add(idx).wrapping_add(1))?.get(),
136
137            aw: {
138                if self.num_h_metrics != 0 {
139                    // Unless hcount is 0, min*2 is always in valid range
140                    unsafe { self.raw_words.get_unchecked(min.wrapping_mul(2)) }.get() as u16
141                } else {
142                    // Otherwise, return 0 as advance_width
143                    0
144                }
145            },
146        })
147    }
148
149    pub const fn iter(&self) -> Iter<'_> {
150        Iter::new(*self)
151    }
152}
153
154const impl<'a> IntoIterator for Hmtx<'a> {
155    type Item = (GlyphId, LongHorMetric);
156    type IntoIter = Iter<'a>;
157    fn into_iter(self) -> Self::IntoIter {
158        Iter::new(self)
159    }
160}
161const impl<'a> IntoIterator for &Hmtx<'a> {
162    type Item = (GlyphId, LongHorMetric);
163    type IntoIter = Iter<'a>;
164    fn into_iter(self) -> Self::IntoIter {
165        Iter::new(*self)
166    }
167}
168
169// TODO: When std::slice::Iter's Clone is constified, make the derive const
170#[derive(Clone)]
171pub struct Iter<'a> {
172    glyph_id: u16,
173    num_h_metrics: u16,
174    default_aw: u16,
175    raw_words: std::slice::Iter<'a, FWORD>,
176}
177
178impl<'a> Iter<'a> {
179    pub const fn new(hmtx: Hmtx<'a>) -> Self {
180        Self {
181            glyph_id: 0,
182            num_h_metrics: hmtx.num_h_metrics(),
183            default_aw: hmtx.last_advance_width().unwrap_or(0),
184            raw_words: hmtx.raw_words.iter(),
185        }
186    }
187}
188
189impl Iterator for Iter<'_> {
190    type Item = (GlyphId, LongHorMetric);
191
192    fn next(&mut self) -> Option<Self::Item> {
193        let aw = if self.glyph_id < self.num_h_metrics {
194            self.raw_words.next()?.get() as u16
195        } else {
196            self.default_aw
197        };
198        let lsb = self.raw_words.next()?.get();
199
200        let id = GlyphId::new(self.glyph_id);
201        self.glyph_id += 1;
202        Some((id, LongHorMetric::new(aw, lsb)))
203    }
204
205    fn size_hint(&self) -> (usize, Option<usize>) {
206        let len = self.len();
207        (len, Some(len))
208    }
209}
210impl ExactSizeIterator for Iter<'_> {
211    fn len(&self) -> usize {
212        let h_metrics_left = self.num_h_metrics.saturating_sub(self.glyph_id);
213        self.raw_words.len() - h_metrics_left as usize
214    }
215}
216impl std::iter::FusedIterator for Iter<'_> {}
217
218// TODO: impl Debug for Hmtx<'_>