tfm/
deserialize.rs

1use super::*;
2
3#[derive(Debug, PartialEq, Eq)]
4pub enum DeserializationError {
5    /// The TFM file is empty (i.e., 0 bytes).
6    ///
7    /// Knuth's TFToPL doesn't handle this case explicitly.
8    /// Providing an empty file to the TFtoPL always returns an error.
9    /// However, the error message is non-deterministic
10    ///     and depends on the initial value of a specific byte of memory (`tfm[0]`).
11    /// If that byte is greater than 127 the message for [`DeserializationError::InternalFileLengthIsNegative`]
12    ///     is printed; otherwise the message for [`DeserializationError::FileHasOneByte`] is printed.
13    FileIsEmpty,
14    /// The TFM file consists of a single byte.
15    FileHasOneByte(u8),
16    /// The file length specified inside the TFM file is invalid because it is zero.
17    InternalFileLengthIsZero,
18    /// The file length specified inside the TFM file is invalid because it is negative.
19    ///
20    /// The variant payload is the invalid length in this file.
21    InternalFileLengthIsNegative(i16),
22    /// The file length specified inside the TFM file is invalid because the file is smaller than it claims.
23    ///
24    /// The variant payload is the invalid length of this file (in words) and the actual file size (in bytes).
25    /// One word is 4 bytes.
26    InternalFileLengthIsTooBig(i16, usize),
27    /// The file length specified inside the TFM file is invalid because it is too small.
28    ///
29    /// TFM files must contain at least 24 bytes of data: the 16-bit file size and the 11
30    /// 16-bit numbers in the sub file sizes section.
31    ///
32    /// Knuth's TFToPL doesn't handle this case explicitly.
33    /// When this buggy input is provided,
34    ///     a [DeserializationError::SubFileSizeIsNegative] error is thrown in some cases,
35    ///     and a [DeserializationError::HeaderLengthIsTooSmall] in other cases.
36    /// Although the result is deterministic, there seems to be undefined behavior here
37    ///     because it seems to depend on the value of uninitialized memory.
38    InternalFileLengthIsTooSmall(i16, usize),
39    /// One of the sub file sizes is negative.
40    SubFileSizeIsNegative(SubFileSizes),
41    /// The header length is too small (either 0 or 1).
42    HeaderLengthIsTooSmall(i16),
43    /// The character range is invalid.
44    ///
45    /// This means either that the lower bound of the range (the smallest character)
46    /// is not smaller than the upper bound of the range (the largest character),
47    /// or the upper bound is bigger than 255.
48    InvalidCharacterRange(i16, i16),
49    /// The character dimension sub-files are incomplete.
50    ///
51    /// This means the sub-file for either widths, heights, depths or italic corrections is empty.
52    IncompleteSubFiles(SubFileSizes),
53    /// There are more than 256 extensible characters.
54    TooManyExtensibleCharacters(i16),
55    /// The sub-file sizes are inconsistent.
56    InconsistentSubFileSizes(SubFileSizes),
57}
58
59impl std::fmt::Display for DeserializationError {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        write!(f, "{}", self.tftopl_message())
62    }
63}
64
65impl std::error::Error for DeserializationError {}
66
67impl DeserializationError {
68    /// Returns the error message the TFtoPL program prints for this kind of error.
69    pub fn tftopl_message(&self) -> String {
70        use DeserializationError::*;
71        let first_line = match self {
72            FileHasOneByte(0..=127) => "The input file is only one byte long!".into(),
73            InternalFileLengthIsZero => {
74                "The file claims to have length zero, but that's impossible!".into()
75            }
76            FileHasOneByte(128..=255) | InternalFileLengthIsNegative(_) | FileIsEmpty => {
77                // See documentation on [DeserializationError::FileIsEmpty] for why we return this string in the case
78                // when the file is empty. While two error messages are possible, this is the one I
79                // observed on my machine today.
80                "The first byte of the input file exceeds 127!".into()
81            }
82            InternalFileLengthIsTooBig(_, _) => "The file has fewer bytes than it claims!".into(),
83            InternalFileLengthIsTooSmall(_, _) | SubFileSizeIsNegative(_) => {
84                "One of the subfile sizes is negative!".into()
85            }
86            HeaderLengthIsTooSmall(lh) => format!["The header length is only {lh}!"],
87            InvalidCharacterRange(l, u) => {
88                format!("The character code range {l}..{u} is illegal!")
89            }
90            IncompleteSubFiles(_) => "Incomplete subfiles for character dimensions!".into(),
91            TooManyExtensibleCharacters(n) => format!["There are {n} extensible recipes!"],
92            InconsistentSubFileSizes(_) => "Subfile sizes don't add up to the stated total!".into(),
93        };
94        format!(
95            "{}\nSorry, but I can't go on; are you sure this is a TFM?",
96            first_line
97        )
98    }
99
100    /// Returns the section in Knuth's TFtoPL (version 2014) in which this error occurs.
101    pub fn tftopl_section(&self) -> usize {
102        use DeserializationError::*;
103        match self {
104            FileIsEmpty
105            | FileHasOneByte(_)
106            | InternalFileLengthIsZero
107            | InternalFileLengthIsNegative(_)
108            | InternalFileLengthIsTooBig(_, _) => 20,
109            InternalFileLengthIsTooSmall(_, _)
110            | SubFileSizeIsNegative(_)
111            | HeaderLengthIsTooSmall(_)
112            | InvalidCharacterRange(_, _)
113            | IncompleteSubFiles(_)
114            | TooManyExtensibleCharacters(_)
115            | InconsistentSubFileSizes(_) => 21,
116        }
117    }
118}
119
120#[derive(Debug, PartialEq, Eq)]
121pub enum DeserializationWarning {
122    /// The file length specified inside the TFM file is smaller than the actual file size.
123    ///
124    /// Additional data after the file length is ignored.
125    ///
126    /// The first element is the number of words specified in the TFM header.
127    /// The second element is the number of bytes in the file.
128    InternalFileLengthIsSmall(i16, usize),
129}
130
131impl DeserializationWarning {
132    /// Returns the warning message the TFtoPL program prints for this kind of error.
133    pub fn tftopl_message(&self) -> String {
134        use DeserializationWarning::*;
135        match self {
136            InternalFileLengthIsSmall(_, _) => {
137                "There's some extra junk at the end of the TFM file,\nbut I'll proceed as if it weren't there.".into()
138            },
139        }
140    }
141
142    /// Returns the section in Knuth's TFtoPL (version 2014) in which this warning occurs.
143    pub fn tftopl_section(&self) -> usize {
144        use DeserializationWarning::*;
145        match self {
146            InternalFileLengthIsSmall(_, _) => 20,
147        }
148    }
149
150    /// Returns true if this warning means the .tfm file was modified.
151    pub fn tfm_file_modified(&self) -> bool {
152        use DeserializationWarning::*;
153        match self {
154            InternalFileLengthIsSmall(_, _) => false,
155        }
156    }
157}
158
159/// Deserialize a TeX font metric (.tfm) file.
160pub(super) fn deserialize(
161    b: &[u8],
162) -> (
163    Result<File, DeserializationError>,
164    Vec<DeserializationWarning>,
165) {
166    match RawFile::deserialize(b) {
167        (Ok(raw_file), warnings) => {
168            let file = from_raw_file(&raw_file);
169            (Ok(file), warnings)
170        }
171        (Err(err), warnings) => (Err(err), warnings),
172    }
173}
174
175pub(super) fn from_raw_file(raw_file: &RawFile) -> File {
176    let char_infos: Vec<(Option<CharDimensions>, Option<CharTag>)> =
177        deserialize_array(raw_file.char_infos);
178    File {
179        header: Header::deserialize(raw_file.header),
180        smallest_char: raw_file.begin_char,
181        char_dimens: (raw_file.begin_char.0..=raw_file.end_char.0)
182            .zip(char_infos.iter())
183            .filter_map(|(c, (d, _))| d.clone().map(|d| (Char(c), d)))
184            .collect(),
185        char_tags: (raw_file.begin_char.0..=raw_file.end_char.0)
186            .zip(char_infos.iter())
187            .filter_map(|(c, (_, d))| d.clone().map(|d| (Char(c), d)))
188            .collect(),
189        unset_char_tags: Default::default(),
190        widths: deserialize_array(raw_file.widths),
191        heights: deserialize_array(raw_file.heights),
192        depths: deserialize_array(raw_file.depths),
193        italic_corrections: deserialize_array(raw_file.italic_corrections),
194        lig_kern_program: deserialize_lig_kern_program(raw_file.lig_kern_instructions),
195        kerns: deserialize_array(raw_file.kerns),
196        extensible_chars: deserialize_array(raw_file.extensible_recipes),
197        params: deserialize_array(raw_file.params),
198    }
199}
200
201/// Raw .tfm file.
202pub struct RawFile<'a> {
203    pub sub_file_sizes: SubFileSizes,
204    pub raw_sub_file_sizes: &'a [u8],
205    pub header: &'a [u8],
206    pub begin_char: Char,
207    pub end_char: Char,
208    pub char_infos: &'a [u8],
209    pub widths: &'a [u8],
210    pub heights: &'a [u8],
211    pub depths: &'a [u8],
212    pub italic_corrections: &'a [u8],
213    pub lig_kern_instructions: &'a [u8],
214    pub kerns: &'a [u8],
215    pub extensible_recipes: &'a [u8],
216    pub params: &'a [u8],
217}
218
219/// Sub-file sizes in a .tfm file.
220#[derive(Default, Clone, Debug, PartialEq, Eq)]
221pub struct SubFileSizes {
222    /// Length of the file, in words.
223    pub lf: i16,
224    /// Length of the header data, in words.
225    pub lh: i16,
226    /// Smallest character code in the font.
227    pub bc: i16,
228    /// Largest character code in the font.
229    pub ec: i16,
230    /// Number of words in the width table.
231    pub nw: i16,
232    /// Number of words in the height table.
233    pub nh: i16,
234    /// Number of words in the depth table.
235    pub nd: i16,
236    /// Number of words in the italic correction table.
237    pub ni: i16,
238    /// Number of words in the lig/kern table.
239    pub nl: i16,
240    /// Number of words in the kern table.
241    pub nk: i16,
242    /// Number of words in the extensible character table.
243    pub ne: i16,
244    /// Number of font parameter words.
245    pub np: i16,
246}
247
248impl SubFileSizes {
249    /// Calculate the valid value of lf, given all of the other fields.
250    pub fn valid_lf(&self) -> i16 {
251        let s = self;
252        6 + s.lh + (s.ec - s.bc + 1) + s.nw + s.nh + s.nd + s.ni + s.nl + s.nk + s.ne + s.np
253    }
254}
255
256impl From<[u8; 24]> for SubFileSizes {
257    fn from(value: [u8; 24]) -> Self {
258        let d = |u: usize| -> i16 { i16::from_be_bytes([value[u], value[u + 1]]) };
259        Self {
260            lf: d(0),
261            lh: d(2),
262            bc: d(4),
263            ec: d(6),
264            nw: d(8),
265            nh: d(10),
266            nd: d(12),
267            ni: d(14),
268            nl: d(16),
269            nk: d(18),
270            ne: d(20),
271            np: d(22),
272        }
273    }
274}
275
276impl From<SubFileSizes> for [u8; 24] {
277    fn from(v: SubFileSizes) -> Self {
278        let a = |u: i16| u.to_be_bytes()[0];
279        let b = |u: i16| u.to_be_bytes()[1];
280        [
281            a(v.lf),
282            b(v.lf),
283            a(v.lh),
284            b(v.lh),
285            a(v.bc),
286            b(v.bc),
287            a(v.ec),
288            b(v.ec),
289            a(v.nw),
290            b(v.nw),
291            a(v.nh),
292            b(v.nh),
293            a(v.nd),
294            b(v.nd),
295            a(v.ni),
296            b(v.ni),
297            a(v.nl),
298            b(v.nl),
299            a(v.nk),
300            b(v.nk),
301            a(v.ne),
302            b(v.ne),
303            a(v.np),
304            b(v.np),
305        ]
306    }
307}
308
309impl<'a> RawFile<'a> {
310    pub fn from_debug_output(
311        s: &str,
312        keep_sub_file_sizes: bool,
313    ) -> Result<Vec<u8>, (String, usize)> {
314        debug::parse(s, keep_sub_file_sizes)
315    }
316
317    pub fn deserialize(
318        b: &'a [u8],
319    ) -> (
320        Result<Self, DeserializationError>,
321        Vec<DeserializationWarning>,
322    ) {
323        let lf = {
324            let b0 = match b.first() {
325                Some(b0) => *b0,
326                None => return (Err(DeserializationError::FileIsEmpty), vec![]),
327            };
328            let b1 = match b.get(1) {
329                Some(b1) => *b1,
330                None => return (Err(DeserializationError::FileHasOneByte(b0)), vec![]),
331            };
332            i16::from_be_bytes([b0, b1])
333        };
334        let mut warnings = vec![];
335        match lf {
336            ..=-1 => {
337                return (
338                    Err(DeserializationError::InternalFileLengthIsNegative(lf)),
339                    warnings,
340                )
341            }
342            0 => {
343                return (
344                    Err(DeserializationError::InternalFileLengthIsZero),
345                    warnings,
346                )
347            }
348            1.. => {
349                let actual_file_length = b.len();
350                let claimed_file_length = (lf as usize) * 4;
351                match actual_file_length.cmp(&claimed_file_length) {
352                    std::cmp::Ordering::Less => {
353                        return (
354                            Err(DeserializationError::InternalFileLengthIsTooBig(
355                                lf,
356                                actual_file_length,
357                            )),
358                            warnings,
359                        )
360                    }
361                    std::cmp::Ordering::Equal => (),
362                    std::cmp::Ordering::Greater => warnings.push(
363                        DeserializationWarning::InternalFileLengthIsSmall(lf, actual_file_length),
364                    ),
365                }
366                if lf <= 3 {
367                    return (
368                        Err(DeserializationError::InternalFileLengthIsTooSmall(
369                            lf,
370                            actual_file_length,
371                        )),
372                        // TFtoPL doesn't output a warning here.
373                        // Which makes sense because the error already encompasses the warning.
374                        vec![],
375                    );
376                }
377            }
378        }
379        let s: SubFileSizes = {
380            let sb: [u8; 24] = b
381                .get(0..24)
382                .expect("3 < lf <= b.len()")
383                .try_into()
384                .expect("slice has 24 elements so fits in 24 length const array");
385            sb.into()
386        };
387
388        if s.lh < 0
389            || s.bc < 0
390            || s.ec < 0
391            || s.nw < 0
392            || s.nh < 0
393            || s.nd < 0
394            || s.ni < 0
395            || s.nl < 0
396            || s.nk < 0
397            || s.ne < 0
398            || s.np < 0
399        {
400            return (
401                Err(DeserializationError::SubFileSizeIsNegative(s.clone())),
402                warnings,
403            );
404        }
405        if s.lh < 2 {
406            return (
407                Err(DeserializationError::HeaderLengthIsTooSmall(s.lh)),
408                warnings,
409            );
410        }
411        let (bc, ec) = match s.bc.cmp(&s.ec.saturating_add(1)) {
412            std::cmp::Ordering::Less => {
413                let ec: u8 = match s.ec.try_into() {
414                    Err(_) => {
415                        return (
416                            Err(DeserializationError::InvalidCharacterRange(s.bc, s.ec)),
417                            warnings,
418                        )
419                    }
420                    Ok(ec) => ec,
421                };
422                (
423                    Char(s.bc.try_into().expect("bc<ec<=u8::MAX, so bc<=u8::MAX")),
424                    Char(ec),
425                )
426            }
427            std::cmp::Ordering::Equal => (Char(1), Char(0)),
428            std::cmp::Ordering::Greater => {
429                return (
430                    Err(DeserializationError::InvalidCharacterRange(s.bc, s.ec)),
431                    warnings,
432                )
433            }
434        };
435        if s.nw == 0 || s.nh == 0 || s.nd == 0 || s.ni == 0 {
436            return (
437                Err(DeserializationError::IncompleteSubFiles(s.clone())),
438                warnings,
439            );
440        }
441        if s.ne > 255 {
442            return (
443                Err(DeserializationError::TooManyExtensibleCharacters(s.ne)),
444                warnings,
445            );
446        }
447        if s.lf != s.valid_lf() {
448            return (
449                Err(DeserializationError::InconsistentSubFileSizes(s.clone())),
450                warnings,
451            );
452        }
453
454        (Ok(Self::finish_deserialization(b, s, bc, ec)), warnings)
455    }
456
457    pub(crate) fn finish_deserialization(
458        b: &[u8],
459        s: SubFileSizes,
460        bc: Char,
461        ec: Char,
462    ) -> RawFile<'_> {
463        let mut b = b;
464        let mut get = |u: i16| {
465            let u = (u as usize) * 4;
466            let a = &b[..u];
467            b = &b[u..];
468            a
469        };
470        RawFile {
471            raw_sub_file_sizes: get(6),
472            header: get(s.lh),
473            begin_char: bc,
474            end_char: ec,
475            char_infos: get(s.ec - s.bc + 1),
476            widths: get(s.nw),
477            heights: get(s.nh),
478            depths: get(s.nd),
479            italic_corrections: get(s.ni),
480            lig_kern_instructions: get(s.nl),
481            kerns: get(s.nk),
482            extensible_recipes: get(s.ne),
483            params: get(s.np),
484            sub_file_sizes: s,
485        }
486    }
487}
488
489fn deserialize_string(b: &[u8]) -> Option<String> {
490    b.first().map(|tfm_len| {
491        match b.get(1..(*tfm_len as usize) + 1) {
492            Some(b) => {
493                b.iter().map(|u| *u as char).collect()
494            },
495            None => {
496                let first_char = *b.get(1)
497                    .expect("from the calling sites, b.len()E{0,20,40}, and b.len()>=1 because b.first().is_some()");
498                // The string is truncated later in the validate function.
499                format!("{}{}",first_char as char, " ".repeat((*tfm_len as usize)-2))
500            }
501        }
502    })
503}
504
505impl Header {
506    fn deserialize(mut b: &[u8]) -> Self {
507        let checksum = Some(u32::deserialize(b));
508        b = &b[4..];
509        let design_size = FixWord::deserialize(b);
510        b = b.get(4..).unwrap_or(&[0; 0]);
511        let character_coding_scheme = deserialize_string(b.get(0..40).unwrap_or(&[0; 0]));
512        b = b.get(40..).unwrap_or(&[0; 0]);
513        let font_family = deserialize_string(b.get(0..20).unwrap_or(&[0; 0]));
514        b = b.get(20..).unwrap_or(&[0; 0]);
515        let seven_bit_safe = b.first().map(|b| *b > 127);
516        let face = b.get(3).map(|b| (*b).into());
517        let b = b.get(4..).unwrap_or(&[0; 0]);
518        Self {
519            checksum,
520            design_size,
521            design_size_valid: true,
522            character_coding_scheme,
523            font_family,
524            seven_bit_safe,
525            face,
526            additional_data: deserialize_array(b),
527        }
528    }
529}
530
531fn deserialize_lig_kern_program(b: &[u8]) -> ligkern::lang::Program {
532    let instructions: Vec<ligkern::lang::Instruction> = deserialize_array(b);
533    let mut passthrough: HashSet<u16> = Default::default();
534    // Boundary chars are deserialized in TFtoTPL.2014.69
535    let boundary_char = match b.get(..2) {
536        None => None,
537        Some(b) => {
538            if b[0] == 255 {
539                passthrough.insert(0);
540                Some(Char(b[1]))
541            } else {
542                None
543            }
544        }
545    };
546    let boundary_char_entrypoint = match b.len().checked_sub(4) {
547        None => None,
548        Some(r) => {
549            let b = &b[r..];
550            if b[0] == 255 {
551                passthrough.insert(
552                    (instructions.len() - 1)
553                        .try_into()
554                        .expect("cannot be more than u16::MAX instructions"),
555                );
556                let r = u16::from_be_bytes([b[2], b[3]]);
557                Some(r)
558            } else {
559                None
560            }
561        }
562    };
563    ligkern::lang::Program {
564        instructions,
565        right_boundary_char: boundary_char,
566        left_boundary_char_entrypoint: boundary_char_entrypoint,
567        passthrough,
568    }
569}
570
571fn deserialize_array<T: Deserializable>(mut b: &[u8]) -> Vec<T> {
572    let mut r = vec![];
573    while !b.is_empty() {
574        r.push(T::deserialize(b));
575        b = &b[4..]
576    }
577    r
578}
579
580/// Implementations of this trait can be deserialized from a 4-byte word.
581trait Deserializable: Sized {
582    fn deserialize(b: &[u8]) -> Self;
583}
584
585impl Deserializable for u32 {
586    #[inline]
587    fn deserialize(b: &[u8]) -> Self {
588        u32::from_be_bytes([b[0], b[1], b[2], b[3]])
589    }
590}
591
592impl Deserializable for FixWord {
593    #[inline]
594    fn deserialize(b: &[u8]) -> Self {
595        FixWord(u32::deserialize(b) as i32)
596    }
597}
598
599impl Deserializable for (Option<CharDimensions>, Option<CharTag>) {
600    fn deserialize(b: &[u8]) -> Self {
601        let info = match b[0].try_into() {
602            Ok(n) => Some(CharDimensions {
603                width_index: WidthIndex::Valid(n),
604                height_index: b[1] / (1 << 4),
605                depth_index: b[1] % (1 << 4),
606                italic_index: b[2] / (1 << 2),
607            }),
608            Err(_) => None,
609        };
610        let tag = match b[2] % (1 << 2) {
611            0 => None,
612            1 => Some(CharTag::Ligature(b[3])),
613            2 => Some(CharTag::List(Char(b[3]))),
614            _ => Some(CharTag::Extension(b[3])),
615        };
616        (info, tag)
617    }
618}
619
620impl Deserializable for ligkern::lang::Instruction {
621    fn deserialize(b: &[u8]) -> Self {
622        let (skip_byte, right_char, op_byte, remainder) = (b[0], Char(b[1]), b[2], b[3]);
623        if skip_byte > 128 {
624            return ligkern::lang::Instruction {
625                next_instruction: None,
626                right_char,
627                operation: ligkern::lang::Operation::EntrypointRedirect(
628                    u16::from_be_bytes([b[2], b[3]]),
629                    true,
630                ),
631            };
632        }
633        ligkern::lang::Instruction {
634            next_instruction: if skip_byte < 128 {
635                Some(skip_byte)
636            } else {
637                None
638            },
639            right_char,
640            operation: ligkern::lang::Operation::lig_kern_operation_from_bytes(op_byte, remainder),
641        }
642    }
643}
644
645impl Deserializable for ExtensibleRecipe {
646    fn deserialize(b: &[u8]) -> Self {
647        let char_or = |b: u8| {
648            if b == 0 {
649                None
650            } else {
651                Some(Char(b))
652            }
653        };
654        ExtensibleRecipe {
655            top: char_or(b[0]),
656            middle: char_or(b[1]),
657            bottom: char_or(b[2]),
658            rep: Char(b[3]),
659        }
660    }
661}
662
663#[cfg(test)]
664mod tests {
665    use super::*;
666
667    macro_rules! deserialize_tests {
668        ( $( ($name: ident, $input: expr, $want: expr $( , $warning: expr )? ), )+ ) => {
669            $(
670                mod $name {
671                    use super::*;
672                    #[test]
673                    fn deserialize_test() {
674                        let input = $input;
675                        let want = $want;
676                        let got = deserialize(&input);
677                        let warnings = vec![ $( $warning )? ];
678                        assert_eq!(got, (want, warnings));
679                    }
680                }
681            )+
682        };
683    }
684
685    macro_rules! serde_tests {
686        ( $( ($name: ident, $bytes: expr, $file: expr $(,)? ), )+ ) => {
687            $(
688                mod $name {
689                    use super::*;
690                    #[test]
691                    fn deserialize_test() {
692                        let input = $bytes;
693                        let want = $file;
694                        let got = deserialize(&input);
695                        assert_eq!(got, (Ok(want), vec![]));
696                    }
697                    #[test]
698                    fn serialize_test() {
699                        let input = $file;
700                        let want = canonical_tfm_bytes($bytes);
701                        let got = input.serialize();
702                        assert_eq!(got, want);
703                    }
704                }
705            )+
706        };
707    }
708
709    deserialize_tests!(
710        (empty_file, [], Err(DeserializationError::FileIsEmpty)),
711        (
712            single_byte_1,
713            [2],
714            Err(DeserializationError::FileHasOneByte(2))
715        ),
716        (
717            single_byte_2,
718            [255],
719            Err(DeserializationError::FileHasOneByte(255))
720        ),
721        (
722            internal_file_length_is_negative,
723            [255, 0],
724            Err(DeserializationError::InternalFileLengthIsNegative(-256))
725        ),
726        (
727            internal_file_length_is_zero,
728            [0, 0, 1, 1],
729            Err(DeserializationError::InternalFileLengthIsZero)
730        ),
731        (
732            internal_file_length_is_too_big,
733            [0, 2, 1, 1],
734            Err(DeserializationError::InternalFileLengthIsTooBig(2, 4))
735        ),
736        (
737            internal_file_length_is_too_small,
738            extend(&[0, 2, 255, 0], 24),
739            Err(DeserializationError::InternalFileLengthIsTooSmall(2, 24))
740        ),
741        (
742            tfm_file_contains_only_sub_file_sizes,
743            extend(&[0, 3, 0, 0], 12),
744            Err(DeserializationError::InternalFileLengthIsTooSmall(3, 12))
745        ),
746        (
747            sub_file_size_too_small,
748            extend(&[0, 6, 255, 0], 24),
749            Err(DeserializationError::SubFileSizeIsNegative(SubFileSizes {
750                lf: 6,
751                lh: -256,
752                ..Default::default()
753            }))
754        ),
755        (
756            header_length_too_small_0,
757            extend(&[0, 6, 0, 0], 24),
758            Err(DeserializationError::HeaderLengthIsTooSmall(0))
759        ),
760        (
761            header_length_too_small_1,
762            extend(&[0, 6, 0, 1], 24),
763            Err(DeserializationError::HeaderLengthIsTooSmall(1))
764        ),
765        (
766            invalid_character_range_1,
767            extend(&[0, 6, 0, 2, 0, 2, 0, 0], 24),
768            Err(DeserializationError::InvalidCharacterRange(2, 0))
769        ),
770        (
771            invalid_character_range_2,
772            extend(&[0, 6, 0, 2, 0, 2, 1, 0], 24),
773            Err(DeserializationError::InvalidCharacterRange(2, 256))
774        ),
775        (
776            incomplete_sub_files,
777            extend(
778                &[
779                    /* lf */ 0, 6, /* lh */ 0, 2, /* bc */ 0, 1, /* ec */ 0, 2,
780                    /* nw */ 0, 3, /* nh */ 0, 4, /* nd */ 0, 5, /* ni */ 0, 0,
781                ],
782                24
783            ),
784            Err(DeserializationError::IncompleteSubFiles(SubFileSizes {
785                lf: 6,
786                lh: 2,
787                bc: 1,
788                ec: 2,
789                nw: 3,
790                nh: 4,
791                nd: 5,
792                ..Default::default()
793            }))
794        ),
795        (
796            too_many_extensible_characters,
797            extend(
798                &[
799                    /* lf */ 0, 6, /* lh */ 0, 2, /* bc */ 0, 1, /* ec */ 0, 2,
800                    /* nw */ 0, 3, /* nh */ 0, 4, /* nd */ 0, 5, /* ni */ 0, 6,
801                    /* nl */ 0, 0, /* nk */ 0, 0, /* ne */ 1, 1, /* np */ 0, 0,
802                ],
803                24
804            ),
805            Err(DeserializationError::TooManyExtensibleCharacters(257))
806        ),
807        (
808            inconsistent_sub_file_sizes,
809            extend(
810                &[
811                    /* lf */ 0, 6, /* lh */ 0, 2, /* bc */ 0, 3, /* ec */ 0, 4,
812                    /* nw */ 0, 5, /* nh */ 0, 6, /* nd */ 0, 7, /* ni */ 0, 8,
813                    /* nl */ 0, 9, /* nk */ 0, 10, /* ne */ 0, 11, /* np */ 0,
814                    12,
815                ],
816                24
817            ),
818            Err(DeserializationError::InconsistentSubFileSizes(
819                SubFileSizes {
820                    lf: 6,
821                    lh: 2,
822                    bc: 3,
823                    ec: 4,
824                    nw: 5,
825                    nh: 6,
826                    nd: 7,
827                    ni: 8,
828                    nl: 9,
829                    nk: 10,
830                    ne: 11,
831                    np: 12,
832                }
833            ))
834        ),
835        (
836            corrupt_strings_in_header,
837            build_from_header(&[
838                /* checksum */ 0, 0, 0, 7, /* design_size */ 0, 0, 0, 11,
839                /* character_coding_scheme */ 240, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
840                65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
841                65, 65, 65, 65, 65, 65, 65, /* font_family */ 100, 66, 66, 66, 66, 66, 66, 66,
842                66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, /* seven_bit_safe */ 0, 0, 0,
843                /* face */ 61,
844            ]),
845            Ok(File {
846                header: Header {
847                    checksum: Some(7),
848                    design_size: FixWord(11),
849                    design_size_valid: true,
850                    character_coding_scheme: Some(format!["A{}", " ".repeat(238)]),
851                    font_family: Some(format!["B{}", " ".repeat(98)]),
852                    seven_bit_safe: Some(false),
853                    face: Some(Face::Other(61)),
854                    additional_data: vec![],
855                },
856                ..Default::default()
857            },)
858        ),
859        (
860            file_longer_than_expected,
861            extend(
862                &vec![
863                    /* lf */ 0, 12, /* lh */ 0, 2, /* bc */ 0, 1, /* ec */ 0,
864                    0, /* nw */ 0, 1, /* nh */ 0, 1, /* nd */ 0, 1, /* ni */ 0,
865                    1, /* nl */ 0, 0, /* nk */ 0, 0, /* ne */ 0, 0, /* np */ 0,
866                    0, /* header.checksum */ 0, 0, 0, 0, /* header.design_size */ 0, 0,
867                    0, 0,
868                ],
869                40 * 4
870            ),
871            Ok(File {
872                header: Header::tfm_default(),
873                ..Default::default()
874            }),
875            DeserializationWarning::InternalFileLengthIsSmall(12, 40 * 4)
876        ),
877        (
878            lig_kern_invalid_lig_tag,
879            tfm_bytes_with_one_lig_kern_command([128, 8, 70, 23]),
880            Ok(tfm_file_with_one_lig_kern_instruction(
881                ligkern::lang::Instruction {
882                    next_instruction: None,
883                    right_char: Char(8),
884                    operation: ligkern::lang::Operation::Ligature {
885                        char_to_insert: Char(23),
886                        post_lig_operation:
887                            ligkern::lang::PostLigOperation::RetainNeitherMoveToInserted,
888                        post_lig_tag_invalid: true,
889                    },
890                }
891            ))
892        ),
893    );
894
895    serde_tests!(
896        (
897            minimal_header,
898            build_from_header(&[/* checksum */ 0, 0, 0, 7, /* design_size */ 0, 0, 0, 11,]),
899            File {
900                header: Header {
901                    checksum: Some(7),
902                    design_size: FixWord(11),
903                    design_size_valid: true,
904                    character_coding_scheme: None,
905                    font_family: None,
906                    seven_bit_safe: None,
907                    face: None,
908                    additional_data: vec![],
909                },
910                ..Default::default()
911            },
912        ),
913        (
914            full_header,
915            build_from_header(&[
916                /* checksum */ 0, 0, 0, 7, /* design_size */ 0, 0, 0, 11,
917                /* character_coding_scheme */ 3, 65, 66, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
918                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
919                /* font_family */ 3, 68, 69, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
920                0, /* seven_bit_safe */ 128, 0, 0, /* face */ 9,
921                /* additional_data */ 0, 0, 0, 13,
922            ]),
923            File {
924                header: Header {
925                    checksum: Some(7),
926                    design_size: FixWord(11),
927                    design_size_valid: true,
928                    character_coding_scheme: Some("ABC".into()),
929                    font_family: Some("DEF".into()),
930                    seven_bit_safe: Some(true),
931                    face: Some(Face::Valid(
932                        FaceWeight::Bold,
933                        FaceSlope::Italic,
934                        FaceExpansion::Condensed
935                    )),
936                    additional_data: vec![13],
937                },
938                ..Default::default()
939            },
940        ),
941        (
942            char_infos,
943            extend(
944                &vec![
945                    /* lf */ 0, 29, /* lh */ 0, 2, /* bc */ 0, 70, /* ec */ 0,
946                    72, /* nw */ 0, 6, /* nh */ 0, 3, /* nd */ 0, 4,
947                    /* ni */ 0, 5, /* nl */ 0, 0, /* nk */ 0, 0, /* ne */ 0, 0,
948                    /* np */ 0, 0, /* header.checksum */ 0, 0, 0, 0,
949                    /* header.design_size */ 0, 0, 0, 0, /* char_infos */ 5, 35, 16, 0,
950                    0, 0, 0, 0, 1, 0, 1, 23, /* widths */
951                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4,
952                    /* heights */
953                    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, /* depths */
954                    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, /* italic_corrections */
955                    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4,
956                ],
957                15 * 4
958            ),
959            File {
960                header: Header::tfm_default(),
961                smallest_char: Char(70),
962                char_dimens: BTreeMap::from([
963                    (
964                        Char(70),
965                        CharDimensions {
966                            width_index: WidthIndex::Valid(5.try_into().unwrap()),
967                            height_index: 2,
968                            depth_index: 3,
969                            italic_index: 4,
970                        }
971                    ),
972                    (
973                        Char(72),
974                        CharDimensions {
975                            width_index: WidthIndex::Valid(1.try_into().unwrap()),
976                            height_index: 0,
977                            depth_index: 0,
978                            italic_index: 0,
979                        }
980                    ),
981                ]),
982                char_tags: BTreeMap::from([(Char(72), CharTag::Ligature(23)),]),
983                widths: vec![
984                    FixWord(0),
985                    FixWord(0),
986                    FixWord(1),
987                    FixWord(2),
988                    FixWord(3),
989                    FixWord(4)
990                ],
991                heights: vec![FixWord(0), FixWord(1), FixWord(2)],
992                depths: vec![FixWord(0), FixWord(1), FixWord(2), FixWord(3)],
993                italic_corrections: vec![
994                    FixWord(0),
995                    FixWord(1),
996                    FixWord(2),
997                    FixWord(3),
998                    FixWord(4)
999                ],
1000                ..Default::default()
1001            },
1002        ),
1003        (
1004            char_infos_large_char,
1005            extend(
1006                &vec![
1007                    /* lf */ 0, 27, /* lh */ 0, 2, /* bc */ 0, 255, /* ec */ 0,
1008                    255, /* nw */ 0, 6, /* nh */ 0, 3, /* nd */ 0, 4,
1009                    /* ni */ 0, 5, /* nl */ 0, 0, /* nk */ 0, 0, /* ne */ 0, 0,
1010                    /* np */ 0, 0, /* header.checksum */ 0, 0, 0, 0,
1011                    /* header.design_size */ 0, 0, 0, 0, /* char_infos */ 5, 35, 16, 0,
1012                    /* widths */
1013                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4,
1014                    /* heights */
1015                    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, /* depths */
1016                    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, /* italic_corrections */
1017                    0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4,
1018                ],
1019                13 * 4
1020            ),
1021            File {
1022                header: Header::tfm_default(),
1023                smallest_char: Char(255),
1024                char_dimens: BTreeMap::from([(
1025                    Char(255),
1026                    CharDimensions {
1027                        width_index: WidthIndex::Valid(5.try_into().unwrap()),
1028                        height_index: 2,
1029                        depth_index: 3,
1030                        italic_index: 4,
1031                    }
1032                ),]),
1033                widths: vec![
1034                    FixWord(0),
1035                    FixWord(0),
1036                    FixWord(1),
1037                    FixWord(2),
1038                    FixWord(3),
1039                    FixWord(4)
1040                ],
1041                heights: vec![FixWord(0), FixWord(1), FixWord(2)],
1042                depths: vec![FixWord(0), FixWord(1), FixWord(2), FixWord(3)],
1043                italic_corrections: vec![
1044                    FixWord(0),
1045                    FixWord(1),
1046                    FixWord(2),
1047                    FixWord(3),
1048                    FixWord(4)
1049                ],
1050                ..Default::default()
1051            },
1052        ),
1053        (
1054            widths_heights_depths_italic_corrections_kerns,
1055            vec![
1056                /* lf */ 0, 17, /* lh */ 0, 2, /* bc */ 0, 1, /* ec */ 0, 0,
1057                /* nw */ 0, 2, /* nh */ 0, 2, /* nd */ 0, 2, /* ni */ 0, 2,
1058                /* nl */ 0, 0, /* nk */ 0, 1, /* ne */ 0, 0, /* np */ 0, 0,
1059                /* header.checksum */ 0, 0, 0, 0, /* header.design_size */ 0, 0, 0, 0,
1060                /* widths */ 0, 0, 0, 0, 0, 0, 0, 23, /* heights */ 0, 0, 0, 0, 0, 0, 0,
1061                29, /* depths */ 0, 0, 0, 0, 0, 0, 0, 31, /* italic_corrections */ 0, 0,
1062                0, 0, 0, 0, 0, 37, /* lig_kern_commands */
1063                /* kerns */ 0, 0, 0, 37
1064            ],
1065            File {
1066                header: Header::tfm_default(),
1067                widths: vec![FixWord::ZERO, FixWord(23)],
1068                heights: vec![FixWord::ZERO, FixWord(29)],
1069                depths: vec![FixWord::ZERO, FixWord(31)],
1070                italic_corrections: vec![FixWord::ZERO, FixWord(37)],
1071                kerns: vec![FixWord(37)],
1072                ..Default::default()
1073            },
1074        ),
1075        (
1076            lig_kern_command_1,
1077            tfm_bytes_with_one_lig_kern_command([0, 5, 130, 13]),
1078            tfm_file_with_one_lig_kern_instruction(ligkern::lang::Instruction {
1079                next_instruction: Some(0),
1080                right_char: Char(5),
1081                operation: ligkern::lang::Operation::KernAtIndex(256 * 2 + 13)
1082            }),
1083        ),
1084        (
1085            lig_kern_command_2,
1086            tfm_bytes_with_one_lig_kern_command([0, 6, 3, 17]),
1087            tfm_file_with_one_lig_kern_instruction(ligkern::lang::Instruction {
1088                next_instruction: Some(0),
1089                right_char: Char(6),
1090                operation: ligkern::lang::Operation::Ligature {
1091                    char_to_insert: Char(17),
1092                    post_lig_operation: ligkern::lang::PostLigOperation::RetainBothMoveNowhere,
1093                    post_lig_tag_invalid: false,
1094                },
1095            }),
1096        ),
1097        (
1098            lig_kern_command_3,
1099            tfm_bytes_with_one_lig_kern_command([0, 7, 3 + 4, 19]),
1100            tfm_file_with_one_lig_kern_instruction(ligkern::lang::Instruction {
1101                next_instruction: Some(0),
1102                right_char: Char(7),
1103                operation: ligkern::lang::Operation::Ligature {
1104                    char_to_insert: Char(19),
1105                    post_lig_operation: ligkern::lang::PostLigOperation::RetainBothMoveToInserted,
1106                    post_lig_tag_invalid: false,
1107                },
1108            }),
1109        ),
1110        (
1111            lig_kern_command_4,
1112            tfm_bytes_with_one_lig_kern_command([128, 8, 3 + 8, 23]),
1113            tfm_file_with_one_lig_kern_instruction(ligkern::lang::Instruction {
1114                next_instruction: None,
1115                right_char: Char(8),
1116                operation: ligkern::lang::Operation::Ligature {
1117                    char_to_insert: Char(23),
1118                    post_lig_operation: ligkern::lang::PostLigOperation::RetainBothMoveToRight,
1119                    post_lig_tag_invalid: false,
1120                },
1121            }),
1122        ),
1123        (
1124            lig_kern_command_5,
1125            tfm_bytes_with_one_lig_kern_command([128, 8, 1, 23]),
1126            tfm_file_with_one_lig_kern_instruction(ligkern::lang::Instruction {
1127                next_instruction: None,
1128                right_char: Char(8),
1129                operation: ligkern::lang::Operation::Ligature {
1130                    char_to_insert: Char(23),
1131                    post_lig_operation: ligkern::lang::PostLigOperation::RetainRightMoveToInserted,
1132                    post_lig_tag_invalid: false,
1133                },
1134            }),
1135        ),
1136        (
1137            lig_kern_command_6,
1138            tfm_bytes_with_one_lig_kern_command([128, 8, 1 + 4, 23]),
1139            tfm_file_with_one_lig_kern_instruction(ligkern::lang::Instruction {
1140                next_instruction: None,
1141                right_char: Char(8),
1142                operation: ligkern::lang::Operation::Ligature {
1143                    char_to_insert: Char(23),
1144                    post_lig_operation: ligkern::lang::PostLigOperation::RetainRightMoveToRight,
1145                    post_lig_tag_invalid: false,
1146                },
1147            }),
1148        ),
1149        (
1150            lig_kern_command_7,
1151            tfm_bytes_with_one_lig_kern_command([128, 8, 2, 23]),
1152            tfm_file_with_one_lig_kern_instruction(ligkern::lang::Instruction {
1153                next_instruction: None,
1154                right_char: Char(8),
1155                operation: ligkern::lang::Operation::Ligature {
1156                    char_to_insert: Char(23),
1157                    post_lig_operation: ligkern::lang::PostLigOperation::RetainLeftMoveNowhere,
1158                    post_lig_tag_invalid: false,
1159                },
1160            }),
1161        ),
1162        (
1163            lig_kern_command_8,
1164            tfm_bytes_with_one_lig_kern_command([128, 8, 2 + 4, 23]),
1165            tfm_file_with_one_lig_kern_instruction(ligkern::lang::Instruction {
1166                next_instruction: None,
1167                right_char: Char(8),
1168                operation: ligkern::lang::Operation::Ligature {
1169                    char_to_insert: Char(23),
1170                    post_lig_operation: ligkern::lang::PostLigOperation::RetainLeftMoveToInserted,
1171                    post_lig_tag_invalid: false,
1172                },
1173            }),
1174        ),
1175        (
1176            lig_kern_command_9,
1177            tfm_bytes_with_one_lig_kern_command([128, 8, 0, 23]),
1178            tfm_file_with_one_lig_kern_instruction(ligkern::lang::Instruction {
1179                next_instruction: None,
1180                right_char: Char(8),
1181                operation: ligkern::lang::Operation::Ligature {
1182                    char_to_insert: Char(23),
1183                    post_lig_operation:
1184                        ligkern::lang::PostLigOperation::RetainNeitherMoveToInserted,
1185                    post_lig_tag_invalid: false,
1186                },
1187            }),
1188        ),
1189        (
1190            lig_kern_command_special,
1191            tfm_bytes_with_one_lig_kern_command([254, 0, 1, 2]),
1192            tfm_file_with_one_lig_kern_instruction(ligkern::lang::Instruction {
1193                next_instruction: None,
1194                right_char: Char(0),
1195                operation: ligkern::lang::Operation::EntrypointRedirect(
1196                    u16::from_be_bytes([1, 2]),
1197                    true,
1198                ),
1199            }),
1200        ),
1201        (
1202            extensible_chars,
1203            vec![
1204                /* lf */ 0, 13, /* lh */ 0, 2, /* bc */ 0, 1, /* ec */ 0, 0,
1205                /* nw */ 0, 1, /* nh */ 0, 1, /* nd */ 0, 1, /* ni */ 0, 1,
1206                /* nl */ 0, 0, /* nk */ 0, 0, /* ne */ 0, 1, /* np */ 0, 0,
1207                /* header.checksum */ 0, 0, 0, 0, /* header.design_size */ 0, 0, 0, 0,
1208                /* widths */ 0, 0, 0, 0, /* heights */ 0, 0, 0, 0, /* depths */ 0,
1209                0, 0, 0, /* italic_corrections */ 0, 0, 0, 0, /* lig_kern_commands */
1210                /* kerns */ /* extensible_chars */ 17, 19, 23, 27
1211            ],
1212            File {
1213                header: Header::tfm_default(),
1214                extensible_chars: vec![ExtensibleRecipe {
1215                    top: Some(Char(17)),
1216                    middle: Some(Char(19)),
1217                    bottom: Some(Char(23)),
1218                    rep: Char(27),
1219                }],
1220                ..Default::default()
1221            },
1222        ),
1223        (
1224            params,
1225            vec![
1226                /* lf */ 0, 22, /* lh */ 0, 2, /* bc */ 0, 1, /* ec */ 0, 0,
1227                /* nw */ 0, 1, /* nh */ 0, 1, /* nd */ 0, 1, /* ni */ 0, 1,
1228                /* nl */ 0, 0, /* nk */ 0, 0, /* ne */ 0, 0, /* np */ 0, 10,
1229                /* header.checksum */ 0, 0, 0, 0, /* header.design_size */ 0, 0, 0, 0,
1230                /* widths */ 0, 0, 0, 0, /* heights */ 0, 0, 0, 0, /* depths */ 0,
1231                0, 0, 0, /* italic_corrections */ 0, 0, 0, 0, /* lig_kern_commands */
1232                /* kerns */ /* extensible_chars */ /* params */
1233                0, 0, 0, 11, 0, 0, 0, 13, 0, 0, 0, 17, 0, 0, 0, 19, 0, 0, 0, 23, 0, 0, 0, 29, 0, 0,
1234                0, 31, 0, 0, 0, 37, 0, 0, 0, 41, 0, 0, 0, 43,
1235            ],
1236            File {
1237                header: Header::tfm_default(),
1238                params: vec![
1239                    FixWord(11),
1240                    FixWord(13),
1241                    FixWord(17),
1242                    FixWord(19),
1243                    FixWord(23),
1244                    FixWord(29),
1245                    FixWord(31),
1246                    FixWord(37),
1247                    FixWord(41),
1248                    FixWord(43),
1249                ],
1250                ..Default::default()
1251            },
1252        ),
1253    );
1254
1255    fn extend(input: &[u8], size: usize) -> Vec<u8> {
1256        let mut v: Vec<u8> = input.into();
1257        for _ in input.len()..size {
1258            v.push(0)
1259        }
1260        v
1261    }
1262
1263    fn tfm_file_with_one_lig_kern_instruction(instruction: ligkern::lang::Instruction) -> File {
1264        File {
1265            header: Header::tfm_default(),
1266            lig_kern_program: ligkern::lang::Program {
1267                instructions: vec![
1268                    instruction,
1269                    ligkern::lang::Instruction {
1270                        next_instruction: None,
1271                        right_char: Char(0),
1272                        operation: ligkern::lang::Operation::KernAtIndex(0),
1273                    },
1274                ],
1275                right_boundary_char: None,
1276                left_boundary_char_entrypoint: None,
1277                passthrough: Default::default(),
1278            },
1279            ..Default::default()
1280        }
1281    }
1282
1283    fn tfm_bytes_with_one_lig_kern_command(lig_kern_command: [u8; 4]) -> Vec<u8> {
1284        let mut v = vec![
1285            /* lf */ 0, 14, /* lh */ 0, 2, /* bc */ 0, 1, /* ec */ 0, 0,
1286            /* nw */ 0, 1, /* nh */ 0, 1, /* nd */ 0, 1, /* ni */ 0, 1,
1287            /* nl */ 0, 2, /* nk */ 0, 0, /* ne */ 0, 0, /* np */ 0, 0,
1288            /* header.checksum */ 0, 0, 0, 0, /* header.design_size */ 0, 0, 0, 0,
1289            /* widths */ 0, 0, 0, 0, /* heights */ 0, 0, 0, 0, /* depths */ 0, 0, 0,
1290            0, /* italic_corrections */ 0, 0, 0, 0,
1291        ];
1292        /* lig_kern_commands */
1293        v.extend(lig_kern_command);
1294        v.extend([128, 0, 128, 0]);
1295        v
1296    }
1297
1298    fn build_from_header(header: &[u8]) -> Vec<u8> {
1299        assert_eq!(header.len() % 4, 0);
1300        let num_words: i16 = (header.len() / 4).try_into().unwrap();
1301        let lf: u8 = (6 + num_words + 4).try_into().unwrap();
1302        let lh: u8 = num_words.try_into().unwrap();
1303        let mut v: Vec<u8> = vec![
1304            /* lf */ 0, lf, /* lh */ 0, lh, /* bc */ 0, 1, /* ec */ 0, 0,
1305            /* nw */ 0, 1, /* nh */ 0, 1, /* nd */ 0, 1, /* ni */ 0, 1,
1306            /* nl */ 0, 0, /* nk */ 0, 0, /* ne */ 0, 0, /* np */ 0, 0,
1307        ];
1308        v.extend(header);
1309        v.extend(&[0_u8; 16]); // the widths etc.
1310        v
1311    }
1312
1313    // This function pads out the header with 0s so that it always contains at least 18 words.
1314    fn canonical_tfm_bytes(b: Vec<u8>) -> Vec<u8> {
1315        let lh = u16::from_be_bytes([b[2], b[3]]);
1316        if lh >= 18 {
1317            return b;
1318        }
1319        let mut lf = u16::from_be_bytes([b[0], b[1]]);
1320        lf += 18 - lh;
1321        let boundary = 24 + (lh as usize) * 4;
1322        let mut v: Vec<u8> = lf.to_be_bytes().into();
1323        v.extend(&[0, 18]);
1324        v.extend(&b[4..boundary]);
1325        v.resize(24 + 18 * 4, 0);
1326        v.extend(&b[boundary..]);
1327        v
1328    }
1329}