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