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