read_fonts/tables/
kern.rs

1//! The kerning table.
2
3use super::aat::StateTable;
4pub use super::kerx::Subtable0Pair;
5
6include!("../../generated/generated_kern.rs");
7
8/// The kerning table.
9#[derive(Clone)]
10pub enum Kern<'a> {
11    Ot(OtKern<'a>),
12    Aat(AatKern<'a>),
13}
14
15impl TopLevelTable for Kern<'_> {
16    const TAG: Tag = Tag::new(b"kern");
17}
18
19impl<'a> FontRead<'a> for Kern<'a> {
20    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
21        // The Apple kern table has a 32-bit fixed version field set to
22        // 1.0 while the OpenType kern table has a 16-bit version field
23        // set to 0. Check the first 16-bit word to determine which
24        // version of the table we should parse.
25        if data.read_at::<u16>(0)? == 0 {
26            OtKern::read(data).map(Self::Ot)
27        } else {
28            AatKern::read(data).map(Self::Aat)
29        }
30    }
31}
32
33impl<'a> Kern<'a> {
34    /// Returns an iterator over all of the subtables in this `kern` table.
35    pub fn subtables(&self) -> impl Iterator<Item = Result<Subtable<'a>, ReadError>> + 'a + Clone {
36        let (data, is_aat, n_tables) = match self {
37            Self::Ot(table) => (table.subtable_data(), false, table.n_tables() as u32),
38            Self::Aat(table) => (table.subtable_data(), true, table.n_tables()),
39        };
40        let data = FontData::new(data);
41        Subtables {
42            data,
43            is_aat,
44            n_tables,
45        }
46    }
47}
48
49/// Iterator over the subtables of a `kern` table.
50#[derive(Clone)]
51struct Subtables<'a> {
52    data: FontData<'a>,
53    is_aat: bool,
54    n_tables: u32,
55}
56
57impl<'a> Iterator for Subtables<'a> {
58    type Item = Result<Subtable<'a>, ReadError>;
59
60    fn next(&mut self) -> Option<Self::Item> {
61        let len = if self.is_aat {
62            self.data.read_at::<u32>(0).ok()? as usize
63        } else if self.n_tables == 1 {
64            // For OT kern tables with a single subtable, ignore the length
65            // and allow the single subtable to extend to the end of the full
66            // table. Some fonts abuse this to bypass the 16-bit limit of the
67            // length field.
68            //
69            // This is why we don't use VarLenArray for this type.
70            self.data.len()
71        } else {
72            self.data.read_at::<u16>(2).ok()? as usize
73        };
74        if len == 0 {
75            return None;
76        }
77        let data = self.data.take_up_to(len)?;
78        Some(Subtable::read_with_args(data, &self.is_aat))
79    }
80}
81
82impl OtSubtable<'_> {
83    // version, length and coverage: all u16
84    const HEADER_LEN: usize = u16::RAW_BYTE_LEN * 3;
85}
86
87impl AatSubtable<'_> {
88    // length: u32, coverage and tuple_index: u16
89    const HEADER_LEN: usize = u32::RAW_BYTE_LEN + u16::RAW_BYTE_LEN * 2;
90}
91
92/// A subtable in the `kern` table.
93#[derive(Clone)]
94pub enum Subtable<'a> {
95    Ot(OtSubtable<'a>),
96    Aat(AatSubtable<'a>),
97}
98
99impl ReadArgs for Subtable<'_> {
100    // is_aat
101    type Args = bool;
102}
103
104impl<'a> FontReadWithArgs<'a> for Subtable<'a> {
105    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
106        let is_aat = *args;
107        if is_aat {
108            Ok(Self::Aat(AatSubtable::read(data)?))
109        } else {
110            Ok(Self::Ot(OtSubtable::read(data)?))
111        }
112    }
113}
114
115impl<'a> Subtable<'a> {
116    /// True if the table has vertical kerning values.
117    #[inline]
118    pub fn is_vertical(&self) -> bool {
119        match self {
120            Self::Ot(subtable) => subtable.coverage() & (1 << 0) == 0,
121            Self::Aat(subtable) => subtable.coverage() & 0x8000 != 0,
122        }
123    }
124
125    /// True if the table has horizontal kerning values.    
126    #[inline]
127    pub fn is_horizontal(&self) -> bool {
128        !self.is_vertical()
129    }
130
131    /// True if the table has cross-stream kerning values.
132    ///
133    /// If text is normally written horizontally, adjustments will be
134    /// vertical. If adjustment values are positive, the text will be
135    /// moved up. If they are negative, the text will be moved down.
136    /// If text is normally written vertically, adjustments will be
137    /// horizontal. If adjustment values are positive, the text will be
138    /// moved to the right. If they are negative, the text will be moved
139    /// to the left.
140    #[inline]
141    pub fn is_cross_stream(&self) -> bool {
142        match self {
143            Self::Ot(subtable) => subtable.coverage() & (1 << 2) != 0,
144            Self::Aat(subtable) => subtable.coverage() & 0x4000 != 0,
145        }
146    }
147
148    /// True if the table has variation kerning values.
149    #[inline]
150    pub fn is_variable(&self) -> bool {
151        match self {
152            Self::Ot(_) => false,
153            Self::Aat(subtable) => subtable.coverage() & 0x2000 != 0,
154        }
155    }
156
157    /// True if the table is represented by a state machine.
158    #[inline]
159    pub fn is_state_machine(&self) -> bool {
160        // Only format 1 is a state machine
161        self.data_and_format().1 == 1
162    }
163
164    /// Returns an enum representing the actual subtable data.    
165    pub fn kind(&self) -> Result<SubtableKind<'a>, ReadError> {
166        let (data, format) = self.data_and_format();
167        let is_aat = matches!(self, Self::Aat(_));
168        SubtableKind::read_with_args(FontData::new(data), &(format, is_aat))
169    }
170
171    fn data_and_format(&self) -> (&'a [u8], u8) {
172        match self {
173            Self::Ot(subtable) => (subtable.data(), ((subtable.coverage() & 0xFF00) >> 8) as u8),
174            Self::Aat(subtable) => (subtable.data(), subtable.coverage() as u8),
175        }
176    }
177}
178
179/// The various `kern` subtable formats.
180#[derive(Clone)]
181pub enum SubtableKind<'a> {
182    Format0(Subtable0<'a>),
183    Format1(StateTable<'a>),
184    Format2(Subtable2<'a>),
185    Format3(Subtable3<'a>),
186}
187
188impl ReadArgs for SubtableKind<'_> {
189    type Args = (u8, bool);
190}
191
192impl<'a> FontReadWithArgs<'a> for SubtableKind<'a> {
193    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
194        let (format, is_aat) = *args;
195        match format {
196            0 => Ok(Self::Format0(Subtable0::read(data)?)),
197            1 => Ok(Self::Format1(StateTable::read(data)?)),
198            2 => {
199                let header_len = if is_aat {
200                    AatSubtable::HEADER_LEN
201                } else {
202                    OtSubtable::HEADER_LEN
203                };
204                Ok(Self::Format2(Subtable2::read_with_args(data, &header_len)?))
205            }
206            3 => Ok(Self::Format3(Subtable3::read(data)?)),
207            _ => Err(ReadError::InvalidFormat(format as _)),
208        }
209    }
210}
211
212impl Subtable0<'_> {
213    /// Returns the kerning adjustment for the given pair.
214    pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
215        super::kerx::pair_kerning(self.pairs(), left, right)
216    }
217}
218
219/// The type 2 `kern` subtable.
220#[derive(Clone)]
221pub struct Subtable2<'a> {
222    pub data: FontData<'a>,
223    /// Size of the header of the containing subtable.
224    pub header_len: usize,
225    /// Left-hand offset table.
226    pub left_offset_table: Subtable2ClassTable<'a>,
227    /// Right-hand offset table.
228    pub right_offset_table: Subtable2ClassTable<'a>,
229    /// Offset to kerning value array.
230    pub array_offset: usize,
231}
232
233impl ReadArgs for Subtable2<'_> {
234    type Args = usize;
235}
236
237impl<'a> FontReadWithArgs<'a> for Subtable2<'a> {
238    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
239        let mut cursor = data.cursor();
240        let header_len = *args;
241        // Skip rowWidth field
242        cursor.advance_by(u16::RAW_BYTE_LEN);
243        // The offsets here are from the beginning of the subtable and not
244        // from the "data" section, so we need to hand parse and subtract
245        // the header size.
246        let left_offset = (cursor.read::<u16>()? as usize)
247            .checked_sub(header_len)
248            .ok_or(ReadError::OutOfBounds)?;
249        let right_offset = (cursor.read::<u16>()? as usize)
250            .checked_sub(header_len)
251            .ok_or(ReadError::OutOfBounds)?;
252        let array_offset = (cursor.read::<u16>()? as usize)
253            .checked_sub(header_len)
254            .ok_or(ReadError::OutOfBounds)?;
255        let left_offset_table =
256            Subtable2ClassTable::read(data.slice(left_offset..).ok_or(ReadError::OutOfBounds)?)?;
257        let right_offset_table =
258            Subtable2ClassTable::read(data.slice(right_offset..).ok_or(ReadError::OutOfBounds)?)?;
259        Ok(Self {
260            data,
261            header_len,
262            left_offset_table,
263            right_offset_table,
264            array_offset,
265        })
266    }
267}
268
269impl Subtable2<'_> {
270    /// Returns the kerning adjustment for the given pair.
271    pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
272        let left_offset = self.left_offset_table.value(left).unwrap_or(0) as usize;
273        let right_offset = self.right_offset_table.value(right).unwrap_or(0) as usize;
274        // "The left-hand class values are stored pre-multiplied by the number
275        // of bytes in one row and offset by the offset of the array from the
276        // start of the subtable."
277        let left_offset = left_offset.checked_sub(self.header_len)?;
278        // Make sure that the left offset is greater than the array base
279        // See <https://github.com/harfbuzz/harfbuzz/blob/6fb10ded54e4640f75f829acb754b05da5c26362/src/hb-aat-layout-common.hh#L1121>
280        if left_offset < self.array_offset {
281            return None;
282        }
283        // "The right-hand class values are stored pre-multiplied by the number
284        // of bytes in a single kerning value (i.e., two)"
285        let offset = left_offset.checked_add(right_offset)?;
286        self.data
287            .read_at::<i16>(offset)
288            .ok()
289            .map(|value| value as i32)
290    }
291}
292
293impl Subtable2ClassTable<'_> {
294    fn value(&self, glyph_id: GlyphId) -> Option<u16> {
295        let glyph_id: u16 = glyph_id.to_u32().try_into().ok()?;
296        let index = glyph_id.checked_sub(self.first_glyph().to_u16())?;
297        self.offsets()
298            .get(index as usize)
299            .map(|offset| offset.get())
300    }
301}
302
303impl Subtable3<'_> {
304    /// Returns the kerning adjustment for the given pair.
305    pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
306        let left_class = self.left_class().get(left.to_u32() as usize).copied()? as usize;
307        let right_class = self.right_class().get(right.to_u32() as usize).copied()? as usize;
308        let index = self
309            .kern_index()
310            .get(left_class * self.right_class_count() as usize + right_class)
311            .copied()? as usize;
312        self.kern_value().get(index).map(|value| value.get() as i32)
313    }
314}
315
316#[cfg(test)]
317mod tests {
318    use font_test_data::bebuffer::BeBuffer;
319
320    use super::*;
321
322    #[test]
323    fn ot_format_0() {
324        // from https://github.com/fonttools/fonttools/blob/729b3d2960efd3/Tests/ttLib/tables/_k_e_r_n_test.py#L9
325        #[rustfmt::skip]
326        const KERN_VER_0_FMT_0_DATA: &[u8] = &[
327            0x00, 0x00, // "0000 "  #  0: version=0
328            0x00, 0x01, // "0001 "  #  2: nTables=1
329            0x00, 0x00, // "0000 "  #  4: version=0 (bogus field, unused)
330            0x00, 0x20, // "0020 "  #  6: length=32
331            0x00,       // "00 "  #  8: format=0
332            0x01,       // "01 "  #  9: coverage=1
333            0x00, 0x03, // "0003 "  # 10: nPairs=3
334            0x00, 0x0C, // "000C "  # 12: searchRange=12
335            0x00, 0x01, // "0001 "  # 14: entrySelector=1
336            0x00, 0x06, // "0006 "  # 16: rangeShift=6
337            0x00, 0x04, 0x00, 0x0C, 0xFF, 0xD8, // "0004 000C FFD8 "  # 18: l=4, r=12, v=-40
338            0x00, 0x04, 0x00, 0x1C, 0x00, 0x28, // "0004 001C 0028 "  # 24: l=4, r=28, v=40
339            0x00, 0x05, 0x00, 0x28, 0xFF, 0xCE, // "0005 0028 FFCE "  # 30: l=5, r=40, v=-50
340        ];
341        let kern = Kern::read(FontData::new(KERN_VER_0_FMT_0_DATA)).unwrap();
342        let Kern::Ot(ot_kern) = &kern else {
343            panic!("Should be an OpenType kerning table");
344        };
345        assert_eq!(ot_kern.version(), 0);
346        assert_eq!(ot_kern.n_tables(), 1);
347        let subtables = kern.subtables().collect::<Vec<_>>();
348        assert_eq!(subtables.len(), 1);
349        let subtable = subtables.first().unwrap().as_ref().unwrap();
350        assert!(subtable.is_horizontal());
351        let Subtable::Ot(ot_subtable) = subtable else {
352            panic!("Should be an OpenType subtable");
353        };
354        assert_eq!(ot_subtable.coverage(), 1);
355        assert_eq!(ot_subtable.length(), 32);
356        check_format_0(subtable);
357    }
358
359    #[test]
360    fn aat_format_0() {
361        // As above, but modified for AAT
362        #[rustfmt::skip]
363        const KERN_VER_1_FMT_0_DATA: &[u8] = &[
364            0x00, 0x01, 0x00, 0x00, // "0001 0000"  #  0: version=1.0
365            0x00, 0x00, 0x00, 0x01, // "0000 0001 "  #  4: nTables=1
366            0x00, 0x00, 0x00, 0x22, // "0000 0020 "  #  8: length=34
367            0x00,       // "00 "  #  12: coverage=0
368            0x00,       // "00 "  #  13: format=0
369            0x00, 0x00, // "0000" #  14: tupleIndex=0
370            0x00, 0x03, // "0003 "  # 16: nPairs=3
371            0x00, 0x0C, // "000C "  # 18: searchRange=12
372            0x00, 0x01, // "0001 "  # 20: entrySelector=1
373            0x00, 0x06, // "0006 "  # 22: rangeShift=6
374            0x00, 0x04, 0x00, 0x0C, 0xFF, 0xD8, // "0004 000C FFD8 "  # 24: l=4, r=12, v=-40
375            0x00, 0x04, 0x00, 0x1C, 0x00, 0x28, // "0004 001C 0028 "  # 30: l=4, r=28, v=40
376            0x00, 0x05, 0x00, 0x28, 0xFF, 0xCE, // "0005 0028 FFCE "  # 36: l=5, r=40, v=-50
377        ];
378        let kern = Kern::read(FontData::new(KERN_VER_1_FMT_0_DATA)).unwrap();
379        let Kern::Aat(aat_kern) = &kern else {
380            panic!("Should be an AAT kerning table");
381        };
382        assert_eq!(aat_kern.version(), MajorMinor::VERSION_1_0);
383        assert_eq!(aat_kern.n_tables(), 1);
384        let subtables = kern.subtables().collect::<Vec<_>>();
385        assert_eq!(subtables.len(), 1);
386        let subtable = subtables.first().unwrap().as_ref().unwrap();
387        assert!(subtable.is_horizontal());
388        let Subtable::Aat(aat_subtable) = subtable else {
389            panic!("Should be an AAT subtable");
390        };
391        assert_eq!(aat_subtable.coverage(), 0);
392        assert_eq!(aat_subtable.length(), 34);
393        check_format_0(subtable);
394    }
395
396    fn check_format_0(subtable: &Subtable) {
397        let SubtableKind::Format0(format0) = subtable.kind().unwrap() else {
398            panic!("Should be a format 0 subtable");
399        };
400        const EXPECTED: &[(u32, u32, i32)] = &[(4, 12, -40), (4, 28, 40), (5, 40, -50)];
401        let pairs = format0
402            .pairs()
403            .iter()
404            .map(|pair| {
405                (
406                    pair.left().to_u32(),
407                    pair.right().to_u32(),
408                    pair.value() as i32,
409                )
410            })
411            .collect::<Vec<_>>();
412        assert_eq!(pairs, EXPECTED);
413        for (left, right, value) in EXPECTED.iter().copied() {
414            assert_eq!(
415                format0.kerning(left.into(), right.into()),
416                Some(value),
417                "left = {left}, right = {right}"
418            );
419        }
420    }
421
422    #[test]
423    fn format_2() {
424        let kern = Kern::read(FontData::new(KERN_FORMAT_2)).unwrap();
425        let subtables = kern.subtables().filter_map(|t| t.ok()).collect::<Vec<_>>();
426        assert_eq!(subtables.len(), 3);
427        // First subtable is format 0 so ignore it
428        check_format_2(
429            &subtables[1],
430            &[
431                (68, 60, -100),
432                (68, 61, -20),
433                (68, 88, -20),
434                (69, 67, -30),
435                (69, 69, -30),
436                (69, 70, -30),
437                (69, 71, -30),
438                (69, 73, -30),
439                (69, 81, -30),
440                (69, 83, -30),
441                (72, 67, -20),
442                (72, 69, -20),
443                (72, 70, -20),
444                (72, 71, -20),
445                (72, 73, -20),
446                (72, 81, -20),
447                (72, 83, -20),
448                (81, 60, -100),
449                (81, 61, -20),
450                (81, 88, -20),
451                (82, 60, -100),
452                (82, 61, -20),
453                (82, 88, -20),
454                (84, 67, -50),
455                (84, 69, -50),
456                (84, 70, -50),
457                (84, 71, -50),
458                (84, 73, -50),
459                (84, 81, -50),
460                (84, 83, -50),
461                (88, 67, -20),
462                (88, 69, -20),
463                (88, 70, -20),
464                (88, 71, -20),
465                (88, 73, -20),
466                (88, 81, -20),
467                (88, 83, -20),
468            ],
469        );
470        check_format_2(
471            &subtables[2],
472            &[
473                (60, 67, -100),
474                (60, 69, -100),
475                (60, 70, -100),
476                (60, 71, -100),
477                (60, 73, -100),
478                (60, 81, -100),
479                (60, 83, -100),
480            ],
481        );
482    }
483
484    fn check_format_2(subtable: &Subtable, expected: &[(u32, u32, i32)]) {
485        let SubtableKind::Format2(format2) = subtable.kind().unwrap() else {
486            panic!("Should be a format 2 subtable");
487        };
488        for (left, right, value) in expected.iter().copied() {
489            assert_eq!(
490                format2.kerning(left.into(), right.into()),
491                Some(value),
492                "left = {left}, right = {right}"
493            );
494        }
495    }
496
497    // Kern version 1, format 2 kern table taken from
498    // HarfRuzz test font
499    // <https://github.com/harfbuzz/harfruzz/blob/b3704a4b51ec045a1acf531a3c4600db4aa55446/tests/fonts/in-house/e39391c77a6321c2ac7a2d644de0396470cd4bfe.ttf>
500    const KERN_FORMAT_2: &[u8] = &[
501        0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
502        0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x3C, 0xFF, 0x7E, 0x0, 0x0, 0x1, 0x74, 0x0,
503        0x2, 0x0, 0x0, 0x0, 0x12, 0x0, 0x7C, 0x0, 0xB0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
504        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC, 0x0, 0x0,
505        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC,
506        0xFF, 0xEC, 0xFF, 0xBA, 0xFF, 0x9C, 0xFF, 0xD8, 0xFF, 0xE2, 0xFF, 0x7E, 0x0, 0x0, 0xFF,
507        0xEC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF,
508        0xE2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF,
509        0xCE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x44, 0x0,
510        0x18, 0x0, 0x34, 0x0, 0x58, 0x0, 0x10, 0x0, 0x10, 0x0, 0x22, 0x0, 0x10, 0x0, 0x10, 0x0,
511        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x34, 0x0, 0x34, 0x0,
512        0x10, 0x0, 0x6A, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x46, 0x0, 0x10, 0x0, 0x10, 0x0,
513        0x46, 0x0, 0x37, 0x0, 0x60, 0x0, 0x10, 0x0, 0x0, 0x0, 0x8, 0x0, 0xE, 0x0, 0xC, 0x0, 0xA,
514        0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2,
515        0x0, 0x2, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
516        0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
517        0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
518        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
519        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x0,
520        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
521        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
522        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
523        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
524        0x0, 0x0, 0x3, 0x84, 0x0, 0x2, 0x0, 0x0, 0x0, 0x16, 0x1, 0x44, 0x2, 0x64, 0x0, 0x10, 0x0,
525        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
526        0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xC4, 0xFF, 0xCE, 0xFF, 0xB0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
527        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x9C, 0xFF, 0xC4, 0xFF, 0x7E, 0x0,
528        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xD8,
529        0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
530        0x0, 0x0, 0xFF, 0xE2, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
531        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x7E, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x60, 0x0, 0x0,
532        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0,
533        0x0, 0x0, 0xFF, 0xE2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF,
534        0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
535        0x0, 0x0, 0x0, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
536        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
537        0xFF, 0x6A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x7E, 0xFF, 0xE2, 0xFF,
538        0x9C, 0xFF, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
539        0x0, 0x0, 0x0, 0x0, 0xFF, 0xCE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xCE, 0xFF, 0xD8, 0xFF,
540        0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xCE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x9C,
541        0xFF, 0xB0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC, 0x0,
542        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x8E, 0x1,
543        0x18, 0x0, 0x10, 0x0, 0x10, 0x1, 0x2, 0x0, 0x10, 0x0, 0x94, 0x0, 0x10, 0x0, 0x10, 0x0,
544        0x10, 0x1, 0x2E, 0x0, 0x10, 0x0, 0xD6, 0x0, 0x10, 0x0, 0x10, 0x1, 0x2, 0x0, 0xAA, 0x0,
545        0x10, 0x0, 0xC0, 0x0, 0x10, 0x0, 0xEC, 0x1, 0x2E, 0x0, 0x26, 0x0, 0x68, 0x0, 0x52, 0x0,
546        0x3C, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
547        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
548        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
549        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
550        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x7E, 0x0,
551        0x10, 0x1, 0x2, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
552        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
553        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
554        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
555        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x7E, 0x0, 0x10, 0x0,
556        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x1, 0x18, 0x0, 0x10, 0x0,
557        0x10, 0x0, 0x7E, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
558        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
559        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x7E, 0x0, 0x10, 0x0, 0x7E, 0x0, 0x7E, 0x0,
560        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x1, 0x2, 0x0, 0x24, 0x0, 0x8E, 0x0, 0x6,
561        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8,
562        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
563        0x0, 0xC, 0x0, 0x14, 0x0, 0xE, 0x0, 0x10, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
564        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0,
565        0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA, 0x0, 0xA, 0x0,
566        0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
567        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0,
568        0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
569        0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
570        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
571        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
572        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
573        0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0,
574        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
575        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
576        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4,
577    ];
578
579    #[test]
580    fn format_3() {
581        // Build a simple NxM kerning array with 5 glyphs
582        let mut buf = BeBuffer::new();
583        buf = buf.push(5u16); // glyphCount
584        buf = buf.push(4u8); // kernValueCount
585        buf = buf.push(3u8); // leftClassCount
586        buf = buf.push(2u8); // rightClassCount
587        buf = buf.push(0u8); // unused flags
588        buf = buf.extend([0i16, -10, -20, 12]); // kernValues
589        buf = buf.extend([0u8, 2, 1, 1, 2]); // leftClass
590        buf = buf.extend([0u8, 1, 1, 0, 1]); // rightClass
591        buf = buf.extend([0u8, 1, 2, 3, 2, 1]); // kernIndex
592        let format3 = Subtable3::read(FontData::new(buf.as_slice())).unwrap();
593        const EXPECTED: [(u32, u32, i32); 25] = [
594            (0, 0, 0),
595            (0, 1, -10),
596            (0, 2, -10),
597            (0, 3, 0),
598            (0, 4, -10),
599            (1, 0, -20),
600            (1, 1, -10),
601            (1, 2, -10),
602            (1, 3, -20),
603            (1, 4, -10),
604            (2, 0, -20),
605            (2, 1, 12),
606            (2, 2, 12),
607            (2, 3, -20),
608            (2, 4, 12),
609            (3, 0, -20),
610            (3, 1, 12),
611            (3, 2, 12),
612            (3, 3, -20),
613            (3, 4, 12),
614            (4, 0, -20),
615            (4, 1, -10),
616            (4, 2, -10),
617            (4, 3, -20),
618            (4, 4, -10),
619        ];
620        for (left, right, value) in EXPECTED {
621            assert_eq!(
622                format3.kerning(left.into(), right.into()),
623                Some(value),
624                "left = {left}, right = {right}"
625            );
626        }
627    }
628}