Skip to main content

read_fonts/tables/
kerx.rs

1//! The [Extended Kerning (kerx)](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kerx.html) table.
2
3use super::aat::{safe_read_array_to_end, ExtendedStateTable, LookupU16, LookupU32};
4
5include!("../../generated/generated_kerx.rs");
6
7impl VarSize for Subtable<'_> {
8    type Size = u32;
9
10    fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
11        // The default implementation assumes that the length field itself
12        // is not included in the total size which is not true of this
13        // table.
14        data.read_at::<u32>(pos).ok().map(|size| size as usize)
15    }
16}
17
18impl<'a> Subtable<'a> {
19    // length, coverage, tuple_count: all u32
20    pub const HEADER_LEN: usize = u32::RAW_BYTE_LEN * 3;
21
22    /// True if the table has vertical kerning values.
23    #[inline]
24    pub fn is_vertical(&self) -> bool {
25        self.coverage() & 0x80000000 != 0
26    }
27
28    /// True if the table has horizontal kerning values.    
29    #[inline]
30    pub fn is_horizontal(&self) -> bool {
31        !self.is_vertical()
32    }
33
34    /// True if the table has cross-stream kerning values.
35    ///
36    /// If text is normally written horizontally, adjustments will be
37    /// vertical. If adjustment values are positive, the text will be
38    /// moved up. If they are negative, the text will be moved down.
39    /// If text is normally written vertically, adjustments will be
40    /// horizontal. If adjustment values are positive, the text will be
41    /// moved to the right. If they are negative, the text will be moved
42    /// to the left.
43    #[inline]
44    pub fn is_cross_stream(&self) -> bool {
45        self.coverage() & 0x40000000 != 0
46    }
47
48    /// True if the table has variation kerning values.
49    #[inline]
50    pub fn is_variable(&self) -> bool {
51        self.coverage() & 0x20000000 != 0
52    }
53
54    /// Process direction flag. If clear, process the glyphs forwards,
55    /// that is, from first to last in the glyph stream. If we, process
56    /// them from last to first. This flag only applies to state-table
57    /// based 'kerx' subtables (types 1 and 4).
58    #[inline]
59    pub fn process_direction(&self) -> bool {
60        self.coverage() & 0x10000000 != 0
61    }
62
63    /// Returns an enum representing the actual subtable data.
64    pub fn kind(&self) -> Result<SubtableKind<'a>, ReadError> {
65        SubtableKind::read_with_args(
66            FontData::new(self.data()),
67            (self.coverage(), self.tuple_count()),
68        )
69    }
70}
71
72/// The various `kerx` subtable formats.
73#[derive(Clone)]
74pub enum SubtableKind<'a> {
75    Format0(Subtable0<'a>),
76    Format1(Subtable1<'a>),
77    Format2(Subtable2<'a>),
78    Format4(Subtable4<'a>),
79    Format6(Subtable6<'a>),
80}
81
82impl ReadArgs for SubtableKind<'_> {
83    type Args = (u32, u32);
84}
85
86impl<'a> FontRead<'a> for SubtableKind<'a> {
87    fn read_with_args(data: FontData<'a>, args: Self::Args) -> Result<Self, ReadError> {
88        // Format is low byte of coverage
89        let format = args.0 & 0xFF;
90        let tuple_count = args.1;
91        match format {
92            0 => Ok(Self::Format0(Subtable0::read(data)?)),
93            1 => Ok(Self::Format1(Subtable1::read(data)?)),
94            2 => Ok(Self::Format2(Subtable2::read(data)?)),
95            // No format 3
96            4 => Ok(Self::Format4(Subtable4::read(data)?)),
97            // No format 5
98            6 => Ok(Self::Format6(Subtable6::read_with_args(data, tuple_count)?)),
99            _ => Err(ReadError::InvalidFormat(format as _)),
100        }
101    }
102}
103
104impl Subtable0<'_> {
105    /// Returns the kerning adjustment for the given pair.
106    pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
107        pair_kerning(self.pairs(), left, right)
108    }
109}
110
111pub(crate) fn pair_kerning(pairs: &[Subtable0Pair], left: GlyphId, right: GlyphId) -> Option<i32> {
112    let left: GlyphId16 = left.try_into().ok()?;
113    let right: GlyphId16 = right.try_into().ok()?;
114    fn make_key(left: GlyphId16, right: GlyphId16) -> u32 {
115        (left.to_u32() << 16) | right.to_u32()
116    }
117    let idx = pairs
118        .binary_search_by_key(&make_key(left, right), |pair| {
119            make_key(pair.left(), pair.right())
120        })
121        .ok()?;
122    pairs.get(idx).map(|pair| pair.value() as i32)
123}
124
125/// The type 1 `kerx` subtable.
126#[derive(Clone)]
127pub struct Subtable1<'a> {
128    pub state_table: ExtendedStateTable<'a, BigEndian<u16>>,
129    /// Contains the set of kerning values, one for each state.
130    pub values: &'a [BigEndian<i16>],
131}
132
133impl ReadArgs for Subtable1<'_> {
134    type Args = ();
135}
136
137impl<'a> FontRead<'a> for Subtable1<'a> {
138    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
139        let state_table = ExtendedStateTable::read(data)?;
140        let mut cursor = data.cursor();
141        cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
142        let values_offset = cursor.read::<u32>()? as usize;
143        let values = super::aat::safe_read_array_to_end(&data, values_offset)?;
144        Ok(Self {
145            state_table,
146            values,
147        })
148    }
149}
150
151/// The type 2 `kerx` subtable.
152#[derive(Clone)]
153pub struct Subtable2<'a> {
154    pub data: FontData<'a>,
155    /// Left-hand offset table.
156    pub left_offset_table: LookupU16<'a>,
157    /// Right-hand offset table.
158    pub right_offset_table: LookupU16<'a>,
159    /// Kerning values.
160    pub array: &'a [BigEndian<i16>],
161}
162
163impl ReadArgs for Subtable2<'_> {
164    type Args = ();
165}
166
167impl<'a> FontRead<'a> for Subtable2<'a> {
168    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
169        let mut cursor = data.cursor();
170        // Skip rowWidth field
171        cursor.advance_by(u32::RAW_BYTE_LEN);
172        // The offsets here are from the beginning of the subtable and not
173        // from the "data" section, so we need to hand parse and subtract
174        // the header size.
175        let left_offset = (cursor.read::<u32>()? as usize)
176            .checked_sub(Subtable::HEADER_LEN)
177            .ok_or(ReadError::OutOfBounds)?;
178        let right_offset = (cursor.read::<u32>()? as usize)
179            .checked_sub(Subtable::HEADER_LEN)
180            .ok_or(ReadError::OutOfBounds)?;
181        let array_offset = (cursor.read::<u32>()? as usize)
182            .checked_sub(Subtable::HEADER_LEN)
183            .ok_or(ReadError::OutOfBounds)?;
184        let left_offset_table =
185            LookupU16::read(data.slice(left_offset..).ok_or(ReadError::OutOfBounds)?)?;
186        let right_offset_table =
187            LookupU16::read(data.slice(right_offset..).ok_or(ReadError::OutOfBounds)?)?;
188        let array = safe_read_array_to_end(&data, array_offset)?;
189        Ok(Self {
190            data,
191            left_offset_table,
192            right_offset_table,
193            array,
194        })
195    }
196}
197
198impl Subtable2<'_> {
199    /// Returns the kerning adjustment for the given pair.
200    pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
201        let left: u16 = left.to_u32().try_into().ok()?;
202        let right: u16 = right.to_u32().try_into().ok()?;
203        let left_idx = self.left_offset_table.value(left).unwrap_or(0) as usize;
204        let right_idx = self.right_offset_table.value(right).unwrap_or(0) as usize;
205        self.array
206            .get(left_idx + right_idx)
207            .map(|value| value.get() as i32)
208    }
209}
210
211/// The type 4 `kerx` subtable.
212#[derive(Clone)]
213pub struct Subtable4<'a> {
214    pub state_table: ExtendedStateTable<'a, BigEndian<u16>>,
215    /// Flags for control point positioning.
216    pub flags: u32,
217    pub actions: Subtable4Actions<'a>,
218}
219
220impl ReadArgs for Subtable4<'_> {
221    type Args = ();
222}
223
224impl<'a> FontRead<'a> for Subtable4<'a> {
225    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
226        let state_table = ExtendedStateTable::read(data)?;
227        let mut cursor = data.cursor();
228        cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
229        let flags = cursor.read::<u32>()?;
230        let action_type = (flags & 0xC0000000) >> 30;
231        let offset = (flags & 0x00FFFFFF) as usize;
232        let actions = match action_type {
233            0 => Subtable4Actions::ControlPoints(safe_read_array_to_end(&data, offset)?),
234            1 => Subtable4Actions::AnchorPoints(safe_read_array_to_end(&data, offset)?),
235            2 => Subtable4Actions::ControlPointCoords(safe_read_array_to_end(&data, offset)?),
236            _ => {
237                return Err(ReadError::MalformedData(
238                    "invalid action type in kerx subtable 4",
239                ))
240            }
241        };
242        Ok(Self {
243            state_table,
244            flags,
245            actions,
246        })
247    }
248}
249
250/// Actions for the type 4 `kerx` subtable.
251#[derive(Clone)]
252pub enum Subtable4Actions<'a> {
253    /// Sequence of glyph outline point indices.
254    ControlPoints(&'a [BigEndian<u16>]),
255    /// Sequence of indices into the `ankr` table.
256    AnchorPoints(&'a [BigEndian<u16>]),
257    /// Sequence of coordinate values.
258    ControlPointCoords(&'a [BigEndian<i16>]),
259}
260
261/// The type 6 `kerx` subtable.
262#[derive(Clone)]
263pub enum Subtable6<'a> {
264    ShortValues(
265        LookupU16<'a>,
266        LookupU16<'a>,
267        &'a [BigEndian<i16>],
268        Option<&'a [BigEndian<i16>]>,
269    ),
270    LongValues(
271        LookupU32<'a>,
272        LookupU32<'a>,
273        &'a [BigEndian<i32>],
274        Option<&'a [BigEndian<i16>]>,
275    ),
276}
277
278impl ReadArgs for Subtable6<'_> {
279    type Args = u32;
280}
281
282impl<'a> FontRead<'a> for Subtable6<'a> {
283    fn read_with_args(data: FontData<'a>, args: Self::Args) -> Result<Self, ReadError> {
284        let tuple_count = args;
285        let mut cursor = data.cursor();
286        let flags = cursor.read::<u32>()?;
287        // Skip rowCount and columnCount
288        cursor.advance_by(u16::RAW_BYTE_LEN * 2);
289        // All offsets are relative to the parent subtable
290        let row_index_table_offset = (cursor.read::<u32>()? as usize)
291            .checked_sub(Subtable::HEADER_LEN)
292            .ok_or(ReadError::OutOfBounds)?;
293        let column_index_table_offset = (cursor.read::<u32>()? as usize)
294            .checked_sub(Subtable::HEADER_LEN)
295            .ok_or(ReadError::OutOfBounds)?;
296        let kerning_array_offset = (cursor.read::<u32>()? as usize)
297            .checked_sub(Subtable::HEADER_LEN)
298            .ok_or(ReadError::OutOfBounds)?;
299        let kerning_vector = if tuple_count != 0 {
300            let kerning_vector_offset = (cursor.read::<u32>()? as usize)
301                .checked_sub(Subtable::HEADER_LEN)
302                .ok_or(ReadError::OutOfBounds)?;
303            Some(safe_read_array_to_end(&data, kerning_vector_offset)?)
304        } else {
305            None
306        };
307        let row_data = data
308            .slice(row_index_table_offset..)
309            .ok_or(ReadError::OutOfBounds)?;
310        let column_data = data
311            .slice(column_index_table_offset..)
312            .ok_or(ReadError::OutOfBounds)?;
313        if flags & 1 == 0 {
314            let rows = LookupU16::read(row_data)?;
315            let columns = LookupU16::read(column_data)?;
316            let kerning_array = safe_read_array_to_end(&data, kerning_array_offset)?;
317            Ok(Self::ShortValues(
318                rows,
319                columns,
320                kerning_array,
321                kerning_vector,
322            ))
323        } else {
324            let rows = LookupU32::read(row_data)?;
325            let columns = LookupU32::read(column_data)?;
326            let kerning_array = safe_read_array_to_end(&data, kerning_array_offset)?;
327            Ok(Self::LongValues(
328                rows,
329                columns,
330                kerning_array,
331                kerning_vector,
332            ))
333        }
334    }
335}
336
337impl Subtable6<'_> {
338    /// Returns the kerning adjustment for the given pair.
339    pub fn kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
340        let left: u16 = left.to_u32().try_into().ok()?;
341        let right: u16 = right.to_u32().try_into().ok()?;
342        fn tuple_kern(value: i32, vector: &Option<&[BigEndian<i16>]>) -> Option<i32> {
343            if let Some(vector) = vector {
344                vector
345                    .get(value as usize >> 1)
346                    .map(|value| value.get() as i32)
347            } else {
348                Some(value)
349            }
350        }
351        match self {
352            Self::ShortValues(rows, columns, array, vector) => {
353                let left_idx = rows.value(left).unwrap_or_default();
354                let right_idx = columns.value(right).unwrap_or_default();
355                let idx = left_idx as usize + right_idx as usize;
356                let value = array.get(idx).map(|value| value.get() as i32)?;
357                tuple_kern(value, vector)
358            }
359            Self::LongValues(rows, columns, array, vector) => {
360                let left_idx = rows.value(left).unwrap_or_default();
361                let right_idx = columns.value(right).unwrap_or_default();
362                let idx = (left_idx as usize).checked_add(right_idx as usize)?;
363                let value = array.get(idx).map(|value| value.get())?;
364                tuple_kern(value, vector)
365            }
366        }
367    }
368}
369
370#[cfg(feature = "experimental_traverse")]
371impl<'a> SomeRecord<'a> for Subtable<'a> {
372    fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
373        RecordResolver {
374            name: "Subtable",
375            get_field: Box::new(move |idx, _data| match idx {
376                0usize => Some(Field::new("coverage", self.coverage())),
377                1usize => Some(Field::new("tuple_count", self.tuple_count())),
378                _ => None,
379            }),
380            data,
381        }
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use font_test_data::bebuffer::BeBuffer;
389
390    #[test]
391    fn parse_subtable0() {
392        let mut buf = BeBuffer::new();
393        // n_pairs, bsearch params
394        buf = buf.extend([6u32, 0, 0, 0]);
395        // just some randomly generated pairs (left, right, kerning adjustment)
396        let mut pairs = [
397            (0u32, 1u32, -10i32),
398            (2, 4, 22),
399            (0, 3, -6),
400            (8, 2, 500),
401            (10, 1, 42),
402            (9, 12, -1000),
403        ];
404        // pairs must be sorted by left and right packed into a u32
405        pairs.sort_by_key(|pair| (pair.0 << 16) | pair.1);
406        for pair in &pairs {
407            buf = buf
408                .push(pair.0 as u16)
409                .push(pair.1 as u16)
410                .push(pair.2 as i16);
411        }
412        let data = buf.to_vec();
413        let subtable0 = Subtable0::read(FontData::new(&data)).unwrap();
414        for pair in pairs {
415            assert_eq!(
416                subtable0.kerning(pair.0.into(), pair.1.into()),
417                Some(pair.2)
418            );
419        }
420    }
421
422    #[test]
423    fn parse_subtable1() {
424        let data = FormatOneFour::One.build_subtable();
425        let subtable1 = Subtable1::read(FontData::new(&data)).unwrap();
426        let values = subtable1
427            .values
428            .iter()
429            // The values array is unsized in the format so we need
430            // to limit it for comparison
431            .take(ONE_EXPECTED.len())
432            .map(|value| value.get())
433            .collect::<Vec<_>>();
434        assert_eq!(values, &ONE_EXPECTED);
435    }
436
437    #[test]
438    fn parse_subtable2() {
439        let data = FormatTwoSix::Two.build_subtable();
440        let subtable = Subtable2::read(FontData::new(&data)).unwrap();
441        let mut values = vec![];
442        for left in 0u32..4 {
443            for right in 0u32..4 {
444                let Some(kerning) = subtable.kerning(left.into(), right.into()) else {
445                    panic!("expected kerning value for {left} and {right}");
446                };
447                values.push(kerning);
448            }
449        }
450        assert_eq!(values, &TWO_SIX_EXPECTED);
451    }
452
453    #[test]
454    fn parse_subtable4_control_points() {
455        let data = FormatOneFour::FourControlPoints.build_subtable();
456        let subtable4 = Subtable4::read(FontData::new(&data)).unwrap();
457        let Subtable4Actions::ControlPoints(action) = &subtable4.actions else {
458            panic!("expected subtable 4 control points action");
459        };
460        let values = action
461            .chunks_exact(2)
462            .take(FOUR_OUTLINE_ANKR_EXPECTED.len())
463            .map(|values| (values[0].get(), values[1].get()))
464            .collect::<Vec<_>>();
465        assert_eq!(values, &FOUR_OUTLINE_ANKR_EXPECTED);
466    }
467
468    #[test]
469    fn parse_subtable4_anchor_points() {
470        let data = FormatOneFour::FourAnchorPoints.build_subtable();
471        let subtable4 = Subtable4::read(FontData::new(&data)).unwrap();
472        let Subtable4Actions::AnchorPoints(action) = &subtable4.actions else {
473            panic!("expected subtable 4 anchor points action");
474        };
475        let values = action
476            .chunks_exact(2)
477            .take(FOUR_OUTLINE_ANKR_EXPECTED.len())
478            .map(|values| (values[0].get(), values[1].get()))
479            .collect::<Vec<_>>();
480        assert_eq!(values, &FOUR_OUTLINE_ANKR_EXPECTED);
481    }
482
483    #[test]
484    fn parse_subtable4_coords() {
485        let data = FormatOneFour::FourCoords.build_subtable();
486        let subtable4 = Subtable4::read(FontData::new(&data)).unwrap();
487        let Subtable4Actions::ControlPointCoords(action) = &subtable4.actions else {
488            panic!("expected subtable 4 coords action");
489        };
490        let values = action
491            .chunks_exact(4)
492            .take(FOUR_COORDS_EXPECTED.len())
493            .map(|values| {
494                [
495                    values[0].get(),
496                    values[1].get(),
497                    values[2].get(),
498                    values[3].get(),
499                ]
500            })
501            .collect::<Vec<_>>();
502        assert_eq!(values, &FOUR_COORDS_EXPECTED);
503    }
504
505    #[test]
506    fn parse_subtable6_short() {
507        let data = FormatTwoSix::SixShort.build_subtable();
508        let subtable = Subtable6::read_with_args(FontData::new(&data), 0).unwrap();
509        let Subtable6::ShortValues(..) = &subtable else {
510            panic!("expected short values in subtable 6");
511        };
512        check_subtable6(subtable);
513    }
514
515    #[test]
516    fn parse_subtable6_long() {
517        let data = FormatTwoSix::SixLong.build_subtable();
518        let subtable = Subtable6::read_with_args(FontData::new(&data), 0).unwrap();
519        let Subtable6::LongValues(..) = &subtable else {
520            panic!("expected long values in subtable 6");
521        };
522        check_subtable6(subtable);
523    }
524
525    #[test]
526    fn parse_subtable6_long_vector() {
527        let data = FormatTwoSix::SixLongVector.build_subtable();
528        let subtable = Subtable6::read_with_args(FontData::new(&data), 1).unwrap();
529        let Subtable6::LongValues(..) = &subtable else {
530            panic!("expected long values in subtable 6");
531        };
532        check_subtable6(subtable);
533    }
534
535    fn check_subtable6(subtable: Subtable6) {
536        let mut values = vec![];
537        for left in 0u32..4 {
538            for right in 0u32..4 {
539                let Some(kerning) = subtable.kerning(left.into(), right.into()) else {
540                    panic!("expected kerning value for {left} and {right}");
541                };
542                values.push(kerning);
543            }
544        }
545        assert_eq!(values, &TWO_SIX_EXPECTED);
546    }
547
548    // Just kerning adjustment values
549    const ONE_EXPECTED: [i16; 8] = [-40, -20, -10, 0, 10, 20, 40, 80];
550
551    // Mark/Current glyph indices. Either outline points or indices into the ankr
552    // table depending on format 4 action type.
553    const FOUR_OUTLINE_ANKR_EXPECTED: [(u16, u16); 4] = [(0, 2), (2, 4), (4, 8), (8, 16)];
554
555    // Mark/Current xy coordinates
556    const FOUR_COORDS_EXPECTED: [[i16; 4]; 4] = [
557        [-10, 10, -20, 20],
558        [1, 2, 3, 4],
559        [-1, -2, -3, -4],
560        [10, -10, 20, -20],
561    ];
562
563    enum FormatOneFour {
564        One,
565        FourControlPoints,
566        FourAnchorPoints,
567        FourCoords,
568    }
569
570    impl FormatOneFour {
571        fn build_subtable(&self) -> Vec<u8> {
572            let mut flags_offset = ExtendedStateTable::<()>::HEADER_LEN + u32::RAW_BYTE_LEN;
573            // Low bits are offset. Set the action type for format 4.
574            match self {
575                Self::FourAnchorPoints => {
576                    flags_offset |= 1 << 30;
577                }
578                Self::FourCoords => {
579                    flags_offset |= 2 << 30;
580                }
581                _ => {}
582            }
583            let mut buf = BeBuffer::new();
584            buf = buf.push(flags_offset as u32);
585            // Now add some data depending on the format
586            match self {
587                Self::One => {
588                    buf = buf.extend(ONE_EXPECTED);
589                }
590                Self::FourControlPoints | Self::FourAnchorPoints => {
591                    for indices in FOUR_OUTLINE_ANKR_EXPECTED {
592                        buf = buf.push(indices.0).push(indices.1);
593                    }
594                }
595                Self::FourCoords => {
596                    for coords in FOUR_COORDS_EXPECTED {
597                        buf = buf.extend(coords);
598                    }
599                }
600            }
601            let payload = buf.to_vec();
602            let payload_len = payload.len() as u32;
603            #[rustfmt::skip]
604            let header = [
605                6_u32, // number of classes
606                payload_len + 16, // byte offset to class table
607                payload_len + 52, // byte offset to state array
608                payload_len + 88, // byte offset to entry array
609            ];
610            #[rustfmt::skip]
611            let class_table = [
612                6_u16, // format
613                4,     // unit size (4 bytes)
614                5,     // number of units
615                16,    // search range
616                2,     // entry selector
617                0,     // range shift
618                50, 4, // Input glyph 50 maps to class 4
619                51, 4, // Input glyph 51 maps to class 4
620                80, 5, // Input glyph 80 maps to class 5
621                201, 4, // Input glyph 201 maps to class 4
622                202, 4, // Input glyph 202 maps to class 4
623                !0, !0
624            ];
625            #[rustfmt::skip]
626            let state_array: [u16; 18] = [
627                0, 0, 0, 0, 0, 1,
628                0, 0, 0, 0, 0, 1,
629                0, 0, 0, 0, 2, 1,
630            ];
631            #[rustfmt::skip]
632            let entry_table: [u16; 9] = [
633                0, 0, 1,
634                2, 0, 2,
635                0, 0, 3,
636            ];
637            BeBuffer::new()
638                .extend(header)
639                .extend(payload)
640                .extend(class_table)
641                .extend(state_array)
642                .extend(entry_table)
643                .to_vec()
644        }
645    }
646
647    const TWO_SIX_EXPECTED: [i32; 16] =
648        [0i32, 10, 20, 0, 8, 4, -2, 8, 30, -10, -20, 30, 8, 4, -2, 8];
649
650    enum FormatTwoSix {
651        Two,
652        SixShort,
653        SixLong,
654        SixLongVector,
655    }
656
657    impl FormatTwoSix {
658        fn is_long(&self) -> bool {
659            matches!(self, Self::SixLong | Self::SixLongVector)
660        }
661
662        fn is_six(&self) -> bool {
663            !matches!(self, Self::Two)
664        }
665
666        fn has_kerning_vector(&self) -> bool {
667            matches!(self, Self::SixLongVector)
668        }
669
670        // Common helper for building format 2/6 subtables
671        fn build_subtable(&self) -> Vec<u8> {
672            let mut buf = BeBuffer::new();
673            let row_count = 3u32;
674            let column_count = 3u32;
675            let is_long = self.is_long();
676            let has_kerning_vector = self.has_kerning_vector();
677            if self.is_six() {
678                // flags, rowCount, columnCount
679                buf = buf
680                    .push(if is_long { 1u32 } else { 0u32 })
681                    .push(row_count as u16)
682                    .push(column_count as u16);
683            } else {
684                // rowWidth
685                buf = buf.push(row_count);
686            }
687            // Map 4 glyphs
688            // 0 => row 0, column 0
689            // 1 => row 2, column 1
690            // 2 => row 1, column 2
691            // 3 => row 2, column 0
692            // values in the row table are pre-multiplied by column count
693            #[allow(clippy::erasing_op, clippy::identity_op)]
694            let row_table = build_lookup(
695                &[
696                    0 * column_count,
697                    2 * column_count,
698                    1 * column_count,
699                    2 * column_count,
700                ],
701                is_long,
702            );
703            let column_table = build_lookup(&[0, 1, 2, 0], is_long);
704            // 3x3 kerning matrix
705            let kerning_array = [0i32, 10, 20, 30, -10, -20, 8, 4, -2];
706            let mut offset =
707                Subtable::HEADER_LEN + u32::RAW_BYTE_LEN * if self.is_six() { 5 } else { 4 };
708            if has_kerning_vector {
709                // optional offset for kerning vector
710                offset += 4;
711            }
712            // row table offset
713            buf = buf.push(offset as u32);
714            offset += row_table.len();
715            // column table offset
716            buf = buf.push(offset as u32);
717            offset += column_table.len();
718            // kerning array offset
719            buf = buf.push(offset as u32);
720            if has_kerning_vector {
721                // 9 32-bit offsets
722                offset += 9 * 4;
723                // kerning vector offset
724                buf = buf.push(offset as u32);
725                buf = buf.extend(row_table);
726                buf = buf.extend(column_table);
727                // With a kerning vector, the kerning array becomes an offset array
728                let offsets: [u32; 9] = core::array::from_fn(|idx| idx as u32 * 2);
729                buf = buf.extend(offsets);
730                // And the value array is always 16-bit
731                for value in &kerning_array {
732                    buf = buf.push(*value as i16);
733                }
734            } else {
735                buf = buf.extend(row_table);
736                buf = buf.extend(column_table);
737                if is_long {
738                    buf = buf.extend(kerning_array);
739                } else {
740                    for value in &kerning_array {
741                        buf = buf.push(*value as i16);
742                    }
743                }
744            }
745            buf.to_vec()
746        }
747    }
748
749    // Builds a simple lookup table mapping the specified slice from
750    // index -> value.
751    // If `is_long` is true, builds a 32-bit lookup table, otherwise
752    // builds a 16-bit table.
753    fn build_lookup(values: &[u32], is_long: bool) -> Vec<u8> {
754        let mut buf = BeBuffer::new();
755        // format
756        buf = buf.push(0u16);
757        for value in values {
758            if is_long {
759                buf = buf.push(*value);
760            } else {
761                buf = buf.push(*value as u16);
762            }
763        }
764        buf.to_vec()
765    }
766}