ttf_parser/tables/
hmtx.rs

1// https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx
2
3use core::num::NonZeroU16;
4
5use crate::GlyphId;
6use crate::parser::{Stream, FromData, LazyArray16};
7
8
9#[derive(Clone, Copy)]
10struct HorizontalMetrics {
11    advance_width: u16,
12    lsb: i16,
13}
14
15impl FromData for HorizontalMetrics {
16    const SIZE: usize = 4;
17
18    #[inline]
19    fn parse(data: &[u8]) -> Option<Self> {
20        let mut s = Stream::new(data);
21        Some(HorizontalMetrics {
22            advance_width: s.read::<u16>()?,
23            lsb: s.read::<i16>()?,
24        })
25    }
26}
27
28
29#[derive(Clone, Copy)]
30pub struct Table<'a> {
31    metrics: LazyArray16<'a, HorizontalMetrics>,
32    bearings: Option<LazyArray16<'a, i16>>,
33    number_of_metrics: u16, // Sum of long metrics + bearings.
34}
35
36impl<'a> Table<'a> {
37    pub fn parse(
38        data: &'a [u8],
39        number_of_hmetrics: NonZeroU16,
40        number_of_glyphs: NonZeroU16,
41    ) -> Option<Self> {
42        let mut s = Stream::new(data);
43        let metrics = s.read_array16::<HorizontalMetrics>(number_of_hmetrics.get())?;
44
45        let mut number_of_metrics = number_of_hmetrics.get();
46
47        // 'If the number_of_hmetrics is less than the total number of glyphs,
48        // then that array is followed by an array for the left side bearing values
49        // of the remaining glyphs.'
50        let bearings_count = number_of_glyphs.get().checked_sub(number_of_hmetrics.get());
51        let bearings = if let Some(count) = bearings_count {
52            number_of_metrics += count;
53            s.read_array16::<i16>(count)
54        } else {
55            None
56        };
57
58        Some(Table {
59            metrics,
60            bearings,
61            number_of_metrics,
62        })
63    }
64
65    #[inline]
66    pub fn advance(&self, glyph_id: GlyphId) -> Option<u16> {
67        if glyph_id.0 >= self.number_of_metrics {
68            return None;
69        }
70
71        if let Some(metrics) = self.metrics.get(glyph_id.0) {
72            Some(metrics.advance_width)
73        } else {
74            // 'As an optimization, the number of records can be less than the number of glyphs,
75            // in which case the advance width value of the last record applies
76            // to all remaining glyph IDs.'
77            self.metrics.last().map(|m| m.advance_width)
78        }
79    }
80
81    #[inline]
82    pub fn side_bearing(&self, glyph_id: GlyphId) -> Option<i16> {
83        if let Some(metrics) = self.metrics.get(glyph_id.0) {
84            Some(metrics.lsb)
85        } else if let Some(bearings) = self.bearings {
86            // 'If the number_of_hmetrics is less than the total number of glyphs,
87            // then that array is followed by an array for the left side bearing values
88            // of the remaining glyphs.'
89
90            let number_of_hmetrics = self.metrics.len();
91
92            // Check for overflow.
93            if glyph_id.0 < number_of_hmetrics {
94                return None;
95            }
96
97            bearings.get(glyph_id.0 - number_of_hmetrics)
98        } else {
99            None
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    macro_rules! nzu16 {
109        ($n:expr) => { NonZeroU16::new($n).unwrap() };
110    }
111
112    #[test]
113    fn simple_case() {
114        let data = &[
115            0x00, 0x01, // advance width [0]: 1
116            0x00, 0x02, // side bearing [0]: 2
117        ];
118
119        let table = Table::parse(data, nzu16!(1), nzu16!(1)).unwrap();
120        assert_eq!(table.advance(GlyphId(0)), Some(1));
121        assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
122    }
123
124    #[test]
125    fn empty() {
126        assert!(Table::parse(&[], nzu16!(1), nzu16!(1)).is_none());
127    }
128
129    #[test]
130    fn smaller_than_glyphs_count() {
131        let data = &[
132            0x00, 0x01, // advance width [0]: 1
133            0x00, 0x02, // side bearing [0]: 2
134
135            0x00, 0x03, // side bearing [1]: 3
136        ];
137
138        let table = Table::parse(data, nzu16!(1), nzu16!(2)).unwrap();
139        assert_eq!(table.advance(GlyphId(0)), Some(1));
140        assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
141        assert_eq!(table.advance(GlyphId(1)), Some(1));
142        assert_eq!(table.side_bearing(GlyphId(1)), Some(3));
143    }
144
145    #[test]
146    fn less_metrics_than_glyphs() {
147        let data = &[
148            0x00, 0x01, // advance width [0]: 1
149            0x00, 0x02, // side bearing [0]: 2
150
151            0x00, 0x03, // advance width [1]: 3
152            0x00, 0x04, // side bearing [1]: 4
153
154            0x00, 0x05, // side bearing [2]: 5
155        ];
156
157        let table = Table::parse(data, nzu16!(2), nzu16!(1)).unwrap();
158        assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
159        assert_eq!(table.side_bearing(GlyphId(1)), Some(4));
160        assert_eq!(table.side_bearing(GlyphId(2)), None);
161    }
162
163    #[test]
164    fn glyph_out_of_bounds_0() {
165        let data = &[
166            0x00, 0x01, // advance width [0]: 1
167            0x00, 0x02, // side bearing [0]: 2
168        ];
169
170        let table = Table::parse(data, nzu16!(1), nzu16!(1)).unwrap();
171        assert_eq!(table.advance(GlyphId(0)), Some(1));
172        assert_eq!(table.side_bearing(GlyphId(0)), Some(2));
173        assert_eq!(table.advance(GlyphId(1)), None);
174        assert_eq!(table.side_bearing(GlyphId(1)), None);
175    }
176
177    #[test]
178    fn glyph_out_of_bounds_1() {
179        let data = &[
180            0x00, 0x01, // advance width [0]: 1
181            0x00, 0x02, // side bearing [0]: 2
182
183            0x00, 0x03, // side bearing [1]: 3
184        ];
185
186        let table = Table::parse(data, nzu16!(1), nzu16!(2)).unwrap();
187        assert_eq!(table.advance(GlyphId(1)), Some(1));
188        assert_eq!(table.side_bearing(GlyphId(1)), Some(3));
189        assert_eq!(table.advance(GlyphId(2)), None);
190        assert_eq!(table.side_bearing(GlyphId(2)), None);
191    }
192}