//! The kerning table.

use super::aat::StateTable;
pub use super::kerx::Subtable0Pair;

include!("../../generated/generated_kern.rs");

/// The kerning table.
#[derive(Clone)]
pub enum Kern<'a> {
    Ot(OtKern<'a>),
    Aat(AatKern<'a>),
}

impl TopLevelTable for Kern<'_> {
    const TAG: Tag = Tag::new(b"kern");
}

impl<'a> FontRead<'a> for Kern<'a> {
    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
        // The Apple kern table has a 32-bit fixed version field set to
        // 1.0 while the OpenType kern table has a 16-bit version field
        // set to 0. Check the first 16-bit word to determine which
        // version of the table we should parse.
        if data.read_at::<u16>(0)? == 0 {
            OtKern::read(data).map(Self::Ot)
        } else {
            AatKern::read(data).map(Self::Aat)
        }
    }
}

impl<'a> Kern<'a> {
    /// Returns an iterator over all of the subtables in this `kern` table.
    pub fn subtables(&self) -> impl Iterator<Item = Result<Subtable<'a>, ReadError>> + 'a + Clone {
        let (data, is_aat, n_tables) = match self {
            Self::Ot(table) => (table.subtable_data(), false, table.n_tables() as u32),
            Self::Aat(table) => (table.subtable_data(), true, table.n_tables()),
        };
        let data = FontData::new(data);
        Subtables {
            data,
            is_aat,
            n_tables,
        }
    }
}

/// Iterator over the subtables of a `kern` table.
#[derive(Clone)]
struct Subtables<'a> {
    data: FontData<'a>,
    is_aat: bool,
    n_tables: u32,
}

impl<'a> Iterator for Subtables<'a> {
    type Item = Result<Subtable<'a>, ReadError>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.n_tables == 0 || self.data.is_empty() {
            return None;
        }
        self.n_tables -= 1;
        let len = if self.is_aat {
            self.data.read_at::<u32>(0).ok()? as usize
        } else if self.n_tables == 0 {
            // For OT kern tables ignore the length of the last subtable
            // and allow the subtable to extend to the end of the full
            // table. Some fonts abuse this to bypass the 16-bit limit
            // of the length field.
            //
            // This is why we don't use VarLenArray for this type.
            self.data.len()
        } else {
            self.data.read_at::<u16>(2).ok()? as usize
        };
        if len == 0 {
            return None;
        }
        let data = self.data.take_up_to(len)?;
        Some(Subtable::read_with_args(data, &self.is_aat))
    }
}

impl OtSubtable<'_> {
    // version, length and coverage: all u16
    const HEADER_LEN: usize = u16::RAW_BYTE_LEN * 3;
}

impl AatSubtable<'_> {
    // length: u32, coverage and tuple_index: u16
    const HEADER_LEN: usize = u32::RAW_BYTE_LEN + u16::RAW_BYTE_LEN * 2;
}

/// A subtable in the `kern` table.
#[derive(Clone)]
pub enum Subtable<'a> {
    Ot(OtSubtable<'a>),
    Aat(AatSubtable<'a>),
}

impl ReadArgs for Subtable<'_> {
    // is_aat
    type Args = bool;
}

impl<'a> FontReadWithArgs<'a> for Subtable<'a> {
    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
        let is_aat = *args;
        if is_aat {
            Ok(Self::Aat(AatSubtable::read(data)?))
        } else {
            Ok(Self::Ot(OtSubtable::read(data)?))
        }
    }
}

impl<'a> Subtable<'a> {
    /// True if the table has vertical kerning values.
    #[inline]
    pub fn is_vertical(&self) -> bool {
        match self {
            Self::Ot(subtable) => subtable.coverage() & (1 << 0) == 0,
            Self::Aat(subtable) => subtable.coverage() & 0x8000 != 0,
        }
    }

    /// True if the table has horizontal kerning values.    
    #[inline]
    pub fn is_horizontal(&self) -> bool {
        !self.is_vertical()
    }

    /// True if the table has cross-stream kerning values.
    ///
    /// If text is normally written horizontally, adjustments will be
    /// vertical. If adjustment values are positive, the text will be
    /// moved up. If they are negative, the text will be moved down.
    /// If text is normally written vertically, adjustments will be
    /// horizontal. If adjustment values are positive, the text will be
    /// moved to the right. If they are negative, the text will be moved
    /// to the left.
    #[inline]
    pub fn is_cross_stream(&self) -> bool {
        match self {
            Self::Ot(subtable) => subtable.coverage() & (1 << 2) != 0,
            Self::Aat(subtable) => subtable.coverage() & 0x4000 != 0,
        }
    }

    /// True if the table has variation kerning values.
    #[inline]
    pub fn is_variable(&self) -> bool {
        match self {
            Self::Ot(_) => false,
            Self::Aat(subtable) => subtable.coverage() & 0x2000 != 0,
        }
    }

    /// True if the table is represented by a state machine.
    #[inline]
    pub fn is_state_machine(&self) -> bool {
        // Only format 1 is a state machine
        self.data_and_format().1 == 1
    }

    /// Returns an enum representing the actual subtable data.    
    pub fn kind(&self) -> Result<SubtableKind<'a>, ReadError> {
        let (data, format) = self.data_and_format();
        let is_aat = matches!(self, Self::Aat(_));
        SubtableKind::read_with_args(FontData::new(data), &(format, is_aat))
    }

    fn data_and_format(&self) -> (&'a [u8], u8) {
        match self {
            Self::Ot(subtable) => (subtable.data(), ((subtable.coverage() & 0xFF00) >> 8) as u8),
            Self::Aat(subtable) => (subtable.data(), subtable.coverage() as u8),
        }
    }
}

/// The various `kern` subtable formats.
#[derive(Clone)]
pub enum SubtableKind<'a> {
    Format0(Subtable0<'a>),
    Format1(StateTable<'a>),
    Format2(Subtable2<'a>),
    Format3(Subtable3<'a>),
}

impl ReadArgs for SubtableKind<'_> {
    type Args = (u8, bool);
}

impl<'a> FontReadWithArgs<'a> for SubtableKind<'a> {
    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
        let (format, is_aat) = *args;
        match format {
            0 => Ok(Self::Format0(Subtable0::read(data)?)),
            1 => Ok(Self::Format1(StateTable::read(data)?)),
            2 => {
                let header_len = if is_aat {
                    AatSubtable::HEADER_LEN
                } else {
                    OtSubtable::HEADER_LEN
                };
                Ok(Self::Format2(Subtable2::read_with_args(data, &header_len)?))
            }
            3 => Ok(Self::Format3(Subtable3::read(data)?)),
            _ => Err(ReadError::InvalidFormat(format as _)),
        }
    }
}

impl Subtable0<'_> {
    /// Returns the kerning adjustment for the given pair.
    pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
        super::kerx::pair_kerning(self.pairs(), left, right)
    }
}

/// The type 2 `kern` subtable.
#[derive(Clone)]
pub struct Subtable2<'a> {
    pub data: FontData<'a>,
    /// Size of the header of the containing subtable.
    pub header_len: usize,
    /// Left-hand offset table.
    pub left_offset_table: Subtable2ClassTable<'a>,
    /// Right-hand offset table.
    pub right_offset_table: Subtable2ClassTable<'a>,
    /// Offset to kerning value array.
    pub array_offset: usize,
}

impl ReadArgs for Subtable2<'_> {
    type Args = usize;
}

impl<'a> FontReadWithArgs<'a> for Subtable2<'a> {
    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
        let mut cursor = data.cursor();
        let header_len = *args;
        // Skip rowWidth field
        cursor.advance_by(u16::RAW_BYTE_LEN);
        // The offsets here are from the beginning of the subtable and not
        // from the "data" section, so we need to hand parse and subtract
        // the header size.
        let left_offset = (cursor.read::<u16>()? as usize)
            .checked_sub(header_len)
            .ok_or(ReadError::OutOfBounds)?;
        let right_offset = (cursor.read::<u16>()? as usize)
            .checked_sub(header_len)
            .ok_or(ReadError::OutOfBounds)?;
        let array_offset = (cursor.read::<u16>()? as usize)
            .checked_sub(header_len)
            .ok_or(ReadError::OutOfBounds)?;
        let left_offset_table =
            Subtable2ClassTable::read(data.slice(left_offset..).ok_or(ReadError::OutOfBounds)?)?;
        let right_offset_table =
            Subtable2ClassTable::read(data.slice(right_offset..).ok_or(ReadError::OutOfBounds)?)?;
        Ok(Self {
            data,
            header_len,
            left_offset_table,
            right_offset_table,
            array_offset,
        })
    }
}

impl Subtable2<'_> {
    /// Returns the kerning adjustment for the given pair.
    pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
        let left_offset = self.left_offset_table.value(left).unwrap_or(0) as usize;
        let right_offset = self.right_offset_table.value(right).unwrap_or(0) as usize;
        // "The left-hand class values are stored pre-multiplied by the number
        // of bytes in one row and offset by the offset of the array from the
        // start of the subtable."
        let left_offset = left_offset.checked_sub(self.header_len)?;
        // Make sure that the left offset is greater than the array base
        // See <https://github.com/harfbuzz/harfbuzz/blob/6fb10ded54e4640f75f829acb754b05da5c26362/src/hb-aat-layout-common.hh#L1121>
        if left_offset < self.array_offset {
            return None;
        }
        // "The right-hand class values are stored pre-multiplied by the number
        // of bytes in a single kerning value (i.e., two)"
        let offset = left_offset.checked_add(right_offset)?;
        self.data
            .read_at::<i16>(offset)
            .ok()
            .map(|value| value as i32)
    }
}

impl Subtable2ClassTable<'_> {
    fn value(&self, glyph_id: GlyphId) -> Option<u16> {
        let glyph_id: u16 = glyph_id.to_u32().try_into().ok()?;
        let index = glyph_id.checked_sub(self.first_glyph().to_u16())?;
        self.offsets()
            .get(index as usize)
            .map(|offset| offset.get())
    }
}

impl Subtable3<'_> {
    /// Returns the kerning adjustment for the given pair.
    pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
        let left_class = self.left_class().get(left.to_u32() as usize).copied()? as usize;
        let right_class = self.right_class().get(right.to_u32() as usize).copied()? as usize;
        let index = self
            .kern_index()
            .get(left_class * self.right_class_count() as usize + right_class)
            .copied()? as usize;
        self.kern_value().get(index).map(|value| value.get() as i32)
    }
}

#[cfg(test)]
mod tests {
    use font_test_data::bebuffer::BeBuffer;

    use super::*;

    #[test]
    fn ot_format_0() {
        // from https://github.com/fonttools/fonttools/blob/729b3d2960efd3/Tests/ttLib/tables/_k_e_r_n_test.py#L9
        #[rustfmt::skip]
        const KERN_VER_0_FMT_0_DATA: &[u8] = &[
            0x00, 0x00, // "0000 "  #  0: version=0
            0x00, 0x01, // "0001 "  #  2: nTables=1
            0x00, 0x00, // "0000 "  #  4: version=0 (bogus field, unused)
            0x00, 0x20, // "0020 "  #  6: length=32
            0x00,       // "00 "  #  8: format=0
            0x01,       // "01 "  #  9: coverage=1
            0x00, 0x03, // "0003 "  # 10: nPairs=3
            0x00, 0x0C, // "000C "  # 12: searchRange=12
            0x00, 0x01, // "0001 "  # 14: entrySelector=1
            0x00, 0x06, // "0006 "  # 16: rangeShift=6
            0x00, 0x04, 0x00, 0x0C, 0xFF, 0xD8, // "0004 000C FFD8 "  # 18: l=4, r=12, v=-40
            0x00, 0x04, 0x00, 0x1C, 0x00, 0x28, // "0004 001C 0028 "  # 24: l=4, r=28, v=40
            0x00, 0x05, 0x00, 0x28, 0xFF, 0xCE, // "0005 0028 FFCE "  # 30: l=5, r=40, v=-50
        ];
        let kern = Kern::read(FontData::new(KERN_VER_0_FMT_0_DATA)).unwrap();
        let Kern::Ot(ot_kern) = &kern else {
            panic!("Should be an OpenType kerning table");
        };
        assert_eq!(ot_kern.version(), 0);
        assert_eq!(ot_kern.n_tables(), 1);
        let subtables = kern.subtables().collect::<Vec<_>>();
        assert_eq!(subtables.len(), 1);
        let subtable = subtables.first().unwrap().as_ref().unwrap();
        assert!(subtable.is_horizontal());
        let Subtable::Ot(ot_subtable) = subtable else {
            panic!("Should be an OpenType subtable");
        };
        assert_eq!(ot_subtable.coverage(), 1);
        assert_eq!(ot_subtable.length(), 32);
        check_format_0(subtable);
    }

    #[test]
    fn aat_format_0() {
        // As above, but modified for AAT
        #[rustfmt::skip]
        const KERN_VER_1_FMT_0_DATA: &[u8] = &[
            0x00, 0x01, 0x00, 0x00, // "0001 0000"  #  0: version=1.0
            0x00, 0x00, 0x00, 0x01, // "0000 0001 "  #  4: nTables=1
            0x00, 0x00, 0x00, 0x22, // "0000 0020 "  #  8: length=34
            0x00,       // "00 "  #  12: coverage=0
            0x00,       // "00 "  #  13: format=0
            0x00, 0x00, // "0000" #  14: tupleIndex=0
            0x00, 0x03, // "0003 "  # 16: nPairs=3
            0x00, 0x0C, // "000C "  # 18: searchRange=12
            0x00, 0x01, // "0001 "  # 20: entrySelector=1
            0x00, 0x06, // "0006 "  # 22: rangeShift=6
            0x00, 0x04, 0x00, 0x0C, 0xFF, 0xD8, // "0004 000C FFD8 "  # 24: l=4, r=12, v=-40
            0x00, 0x04, 0x00, 0x1C, 0x00, 0x28, // "0004 001C 0028 "  # 30: l=4, r=28, v=40
            0x00, 0x05, 0x00, 0x28, 0xFF, 0xCE, // "0005 0028 FFCE "  # 36: l=5, r=40, v=-50
        ];
        let kern = Kern::read(FontData::new(KERN_VER_1_FMT_0_DATA)).unwrap();
        let Kern::Aat(aat_kern) = &kern else {
            panic!("Should be an AAT kerning table");
        };
        assert_eq!(aat_kern.version(), MajorMinor::VERSION_1_0);
        assert_eq!(aat_kern.n_tables(), 1);
        let subtables = kern.subtables().collect::<Vec<_>>();
        assert_eq!(subtables.len(), 1);
        let subtable = subtables.first().unwrap().as_ref().unwrap();
        assert!(subtable.is_horizontal());
        let Subtable::Aat(aat_subtable) = subtable else {
            panic!("Should be an AAT subtable");
        };
        assert_eq!(aat_subtable.coverage(), 0);
        assert_eq!(aat_subtable.length(), 34);
        check_format_0(subtable);
    }

    fn check_format_0(subtable: &Subtable) {
        let SubtableKind::Format0(format0) = subtable.kind().unwrap() else {
            panic!("Should be a format 0 subtable");
        };
        const EXPECTED: &[(u32, u32, i32)] = &[(4, 12, -40), (4, 28, 40), (5, 40, -50)];
        let pairs = format0
            .pairs()
            .iter()
            .map(|pair| {
                (
                    pair.left().to_u32(),
                    pair.right().to_u32(),
                    pair.value() as i32,
                )
            })
            .collect::<Vec<_>>();
        assert_eq!(pairs, EXPECTED);
        for (left, right, value) in EXPECTED.iter().copied() {
            assert_eq!(
                format0.kerning(left.into(), right.into()),
                Some(value),
                "left = {left}, right = {right}"
            );
        }
    }

    #[test]
    fn format_2() {
        let kern = Kern::read(FontData::new(KERN_FORMAT_2)).unwrap();
        let subtables = kern.subtables().filter_map(|t| t.ok()).collect::<Vec<_>>();
        assert_eq!(subtables.len(), 3);
        // First subtable is format 0 so ignore it
        check_format_2(
            &subtables[1],
            &[
                (68, 60, -100),
                (68, 61, -20),
                (68, 88, -20),
                (69, 67, -30),
                (69, 69, -30),
                (69, 70, -30),
                (69, 71, -30),
                (69, 73, -30),
                (69, 81, -30),
                (69, 83, -30),
                (72, 67, -20),
                (72, 69, -20),
                (72, 70, -20),
                (72, 71, -20),
                (72, 73, -20),
                (72, 81, -20),
                (72, 83, -20),
                (81, 60, -100),
                (81, 61, -20),
                (81, 88, -20),
                (82, 60, -100),
                (82, 61, -20),
                (82, 88, -20),
                (84, 67, -50),
                (84, 69, -50),
                (84, 70, -50),
                (84, 71, -50),
                (84, 73, -50),
                (84, 81, -50),
                (84, 83, -50),
                (88, 67, -20),
                (88, 69, -20),
                (88, 70, -20),
                (88, 71, -20),
                (88, 73, -20),
                (88, 81, -20),
                (88, 83, -20),
            ],
        );
        check_format_2(
            &subtables[2],
            &[
                (60, 67, -100),
                (60, 69, -100),
                (60, 70, -100),
                (60, 71, -100),
                (60, 73, -100),
                (60, 81, -100),
                (60, 83, -100),
            ],
        );
    }

    fn check_format_2(subtable: &Subtable, expected: &[(u32, u32, i32)]) {
        let SubtableKind::Format2(format2) = subtable.kind().unwrap() else {
            panic!("Should be a format 2 subtable");
        };
        for (left, right, value) in expected.iter().copied() {
            assert_eq!(
                format2.kerning(left.into(), right.into()),
                Some(value),
                "left = {left}, right = {right}"
            );
        }
    }

    // Kern version 1, format 2 kern table taken from
    // HarfRuzz test font
    // <https://github.com/harfbuzz/harfruzz/blob/b3704a4b51ec045a1acf531a3c4600db4aa55446/tests/fonts/in-house/e39391c77a6321c2ac7a2d644de0396470cd4bfe.ttf>
    const KERN_FORMAT_2: &[u8] = &[
        0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
        0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x3C, 0xFF, 0x7E, 0x0, 0x0, 0x1, 0x74, 0x0,
        0x2, 0x0, 0x0, 0x0, 0x12, 0x0, 0x7C, 0x0, 0xB0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC,
        0xFF, 0xEC, 0xFF, 0xBA, 0xFF, 0x9C, 0xFF, 0xD8, 0xFF, 0xE2, 0xFF, 0x7E, 0x0, 0x0, 0xFF,
        0xEC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF,
        0xE2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF,
        0xCE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x44, 0x0,
        0x18, 0x0, 0x34, 0x0, 0x58, 0x0, 0x10, 0x0, 0x10, 0x0, 0x22, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x34, 0x0, 0x34, 0x0,
        0x10, 0x0, 0x6A, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x46, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x46, 0x0, 0x37, 0x0, 0x60, 0x0, 0x10, 0x0, 0x0, 0x0, 0x8, 0x0, 0xE, 0x0, 0xC, 0x0, 0xA,
        0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2,
        0x0, 0x2, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
        0x0, 0x0, 0x3, 0x84, 0x0, 0x2, 0x0, 0x0, 0x0, 0x16, 0x1, 0x44, 0x2, 0x64, 0x0, 0x10, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xC4, 0xFF, 0xCE, 0xFF, 0xB0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x9C, 0xFF, 0xC4, 0xFF, 0x7E, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xD8,
        0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0xFF, 0xE2, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x7E, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x60, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0xFF, 0xE2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF,
        0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0xFF, 0x6A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x7E, 0xFF, 0xE2, 0xFF,
        0x9C, 0xFF, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0xFF, 0xCE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xCE, 0xFF, 0xD8, 0xFF,
        0xD8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xCE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x9C,
        0xFF, 0xB0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xEC, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x8E, 0x1,
        0x18, 0x0, 0x10, 0x0, 0x10, 0x1, 0x2, 0x0, 0x10, 0x0, 0x94, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x1, 0x2E, 0x0, 0x10, 0x0, 0xD6, 0x0, 0x10, 0x0, 0x10, 0x1, 0x2, 0x0, 0xAA, 0x0,
        0x10, 0x0, 0xC0, 0x0, 0x10, 0x0, 0xEC, 0x1, 0x2E, 0x0, 0x26, 0x0, 0x68, 0x0, 0x52, 0x0,
        0x3C, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x7E, 0x0,
        0x10, 0x1, 0x2, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x7E, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x1, 0x18, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x7E, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x7E, 0x0, 0x10, 0x0, 0x7E, 0x0, 0x7E, 0x0,
        0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x0, 0x10, 0x1, 0x2, 0x0, 0x24, 0x0, 0x8E, 0x0, 0x6,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0xC, 0x0, 0x14, 0x0, 0xE, 0x0, 0x10, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0,
        0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA, 0x0, 0xA, 0x0,
        0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4,
    ];

    #[test]
    fn format_3() {
        // Build a simple NxM kerning array with 5 glyphs
        let mut buf = BeBuffer::new();
        buf = buf.push(5u16); // glyphCount
        buf = buf.push(4u8); // kernValueCount
        buf = buf.push(3u8); // leftClassCount
        buf = buf.push(2u8); // rightClassCount
        buf = buf.push(0u8); // unused flags
        buf = buf.extend([0i16, -10, -20, 12]); // kernValues
        buf = buf.extend([0u8, 2, 1, 1, 2]); // leftClass
        buf = buf.extend([0u8, 1, 1, 0, 1]); // rightClass
        buf = buf.extend([0u8, 1, 2, 3, 2, 1]); // kernIndex
        let format3 = Subtable3::read(FontData::new(buf.as_slice())).unwrap();
        const EXPECTED: [(u32, u32, i32); 25] = [
            (0, 0, 0),
            (0, 1, -10),
            (0, 2, -10),
            (0, 3, 0),
            (0, 4, -10),
            (1, 0, -20),
            (1, 1, -10),
            (1, 2, -10),
            (1, 3, -20),
            (1, 4, -10),
            (2, 0, -20),
            (2, 1, 12),
            (2, 2, 12),
            (2, 3, -20),
            (2, 4, 12),
            (3, 0, -20),
            (3, 1, 12),
            (3, 2, 12),
            (3, 3, -20),
            (3, 4, 12),
            (4, 0, -20),
            (4, 1, -10),
            (4, 2, -10),
            (4, 3, -20),
            (4, 4, -10),
        ];
        for (left, right, value) in EXPECTED {
            assert_eq!(
                format3.kerning(left.into(), right.into()),
                Some(value),
                "left = {left}, right = {right}"
            );
        }
    }
}
