ttf_parser/tables/
hmtx.rs
1use 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, }
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 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 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 let number_of_hmetrics = self.metrics.len();
91
92 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, 0x00, 0x02, ];
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, 0x00, 0x02, 0x00, 0x03, ];
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, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, ];
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, 0x00, 0x02, ];
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, 0x00, 0x02, 0x00, 0x03, ];
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}