read_fonts/tables/
trak.rs

1//! The [tracking (trak)](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6trak.html) table.
2
3include!("../../generated/generated_trak.rs");
4
5impl<'a> TrackData<'a> {
6    /// Returns the size table for this set of tracking data.
7    ///
8    /// The `offset_data` parameter comes from the [`Trak`] table.
9    pub fn size_table(
10        &self,
11        offset_data: FontData<'a>,
12    ) -> Result<&'a [BigEndian<Fixed>], ReadError> {
13        let mut cursor = offset_data
14            .split_off(self.size_table_offset() as usize)
15            .ok_or(ReadError::OutOfBounds)?
16            .cursor();
17        cursor.read_array(self.n_sizes() as usize)
18    }
19}
20
21impl TrackTableEntry {
22    /// Returns the list of per-size tracking values for this entry.
23    ///
24    /// The `offset_data` parameter comes from the [`Trak`] table and `n_sizes`
25    /// parameter comes from the parent [`TrackData`] table.
26    pub fn per_size_values<'a>(
27        &self,
28        offset_data: FontData<'a>,
29        n_sizes: u16,
30    ) -> Result<&'a [BigEndian<i16>], ReadError> {
31        let mut cursor = offset_data
32            .split_off(self.offset() as usize)
33            .ok_or(ReadError::OutOfBounds)?
34            .cursor();
35        cursor.read_array(n_sizes as usize)
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use font_test_data::bebuffer::BeBuffer;
43
44    #[test]
45    fn parse_header() {
46        let table_data = example_track_table();
47        let trak = Trak::read(FontData::new(&table_data)).unwrap();
48        assert_eq!(trak.version(), MajorMinor::VERSION_1_0);
49        let _ = trak.horiz().unwrap().unwrap();
50        assert!(trak.vert().is_none());
51    }
52
53    #[test]
54    fn parse_tracks() {
55        let table_data = example_track_table();
56        let trak = Trak::read(FontData::new(&table_data)).unwrap();
57        let horiz = trak.horiz().unwrap().unwrap();
58        let track_table = horiz.track_table();
59        let expected_tracks = [
60            (Fixed::from_i32(-1), NameId::new(256), 52),
61            (Fixed::from_i32(0), NameId::new(258), 60),
62            (Fixed::from_i32(1), NameId::new(257), 56),
63        ];
64        let tracks = track_table
65            .iter()
66            .map(|track| (track.track(), track.name_index(), track.offset()))
67            .collect::<Vec<_>>();
68        assert_eq!(tracks, expected_tracks);
69        let expected_per_size_tracking_values = [[-15i16, -7], [0, 0], [50, 20]];
70        for (track, expected_values) in track_table.iter().zip(expected_per_size_tracking_values) {
71            let values = track
72                .per_size_values(trak.offset_data(), horiz.n_sizes())
73                .unwrap()
74                .iter()
75                .map(|v| v.get())
76                .collect::<Vec<_>>();
77            assert_eq!(values, expected_values);
78        }
79    }
80
81    #[test]
82    fn parse_per_size_values() {
83        let table_data = example_track_table();
84        let trak = Trak::read(FontData::new(&table_data)).unwrap();
85        let horiz = trak.horiz().unwrap().unwrap();
86        let track_table = horiz.track_table();
87        let expected_per_size_tracking_values = [[-15i16, -7], [0, 0], [50, 20]];
88        for (track, expected_values) in track_table.iter().zip(expected_per_size_tracking_values) {
89            let values = track
90                .per_size_values(trak.offset_data(), horiz.n_sizes())
91                .unwrap()
92                .iter()
93                .map(|v| v.get())
94                .collect::<Vec<_>>();
95            assert_eq!(values, expected_values);
96        }
97    }
98
99    #[test]
100    fn parse_sizes() {
101        let table_data = example_track_table();
102        let trak = Trak::read(FontData::new(&table_data)).unwrap();
103        let horiz = trak.horiz().unwrap().unwrap();
104        let size_table = horiz
105            .size_table(trak.offset_data())
106            .unwrap()
107            .iter()
108            .map(|v| v.get())
109            .collect::<Vec<_>>();
110        let expected_sizes = [Fixed::from_i32(12), Fixed::from_i32(24)];
111        assert_eq!(size_table, expected_sizes);
112    }
113
114    #[test]
115    fn insufficient_data() {
116        let mut table_data = example_track_table();
117        // drop the last byte from the final per size value entry
118        table_data.pop();
119        let trak = Trak::read(FontData::new(&table_data)).unwrap();
120        let horiz = trak.horiz().unwrap().unwrap();
121        let track_table = horiz.track_table();
122        // The values for the second track will fail to parse
123        let expected_per_size_tracking_values = [Some([-15i16, -7]), None, Some([50, 20])];
124        for (track, expected_values) in track_table.iter().zip(expected_per_size_tracking_values) {
125            let values = track
126                .per_size_values(trak.offset_data(), horiz.n_sizes())
127                .ok()
128                .map(|values| values.iter().map(|v| v.get()).collect::<Vec<_>>());
129            assert_eq!(
130                values,
131                expected_values.map(|value| value.into_iter().collect())
132            );
133        }
134    }
135
136    #[test]
137    fn bad_offset() {
138        let mut table_data = example_track_table();
139        // modify offset of first track table entry to be OOB
140        table_data[26] = 255;
141        let trak = Trak::read(FontData::new(&table_data)).unwrap();
142        let horiz = trak.horiz().unwrap().unwrap();
143        let track_table = horiz.track_table();
144        assert!(matches!(
145            track_table[0].per_size_values(trak.offset_data(), horiz.n_sizes()),
146            Err(ReadError::OutOfBounds)
147        ));
148    }
149
150    /// From <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6trak.html>
151    fn example_track_table() -> Vec<u8> {
152        let mut buf = BeBuffer::new();
153        // header
154        buf = buf.push(MajorMinor::VERSION_1_0);
155        buf = buf.extend([0u16, 12, 0, 0]);
156        // TrackData
157        buf = buf.extend([3u16, 2]);
158        buf = buf.push(44u32);
159        // Three sorted TrackTableEntry records
160        buf = buf.push(0xFFFF0000u32).extend([256u16, 52]);
161        buf = buf.push(0x00000000u32).extend([258u16, 60]);
162        buf = buf.push(0x00010000u32).extend([257u16, 56]);
163        // Size subtable
164        buf = buf.push(0x000C0000u32);
165        buf = buf.push(0x00180000u32);
166        // Per-size tracking data values
167        buf = buf.extend([-15i16, -7, 50, 20, 0, 0]);
168        buf.to_vec()
169    }
170}