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