Skip to main content

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