1use super::*;
2
3#[derive(Debug, PartialEq, Eq)]
4pub enum DeserializationError {
5 FileIsEmpty,
14 FileHasOneByte(u8),
16 InternalFileLengthIsZero,
18 InternalFileLengthIsNegative(i16),
22 InternalFileLengthIsTooBig(i16, usize),
27 InternalFileLengthIsTooSmall(i16, usize),
39 SubFileSizeIsNegative(SubFileSizes),
41 HeaderLengthIsTooSmall(i16),
43 InvalidCharacterRange(i16, i16),
49 IncompleteSubFiles(SubFileSizes),
53 TooManyExtensibleCharacters(i16),
55 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 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 "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 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 InternalFileLengthIsSmall(i16, usize),
129}
130
131impl DeserializationWarning {
132 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 pub fn tftopl_section(&self) -> usize {
144 use DeserializationWarning::*;
145 match self {
146 InternalFileLengthIsSmall(_, _) => 20,
147 }
148 }
149
150 pub fn tfm_file_modified(&self) -> bool {
152 use DeserializationWarning::*;
153 match self {
154 InternalFileLengthIsSmall(_, _) => false,
155 }
156 }
157}
158
159pub(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
201pub 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#[derive(Default, Clone, Debug, PartialEq, Eq)]
221pub struct SubFileSizes {
222 pub lf: i16,
224 pub lh: i16,
226 pub bc: i16,
228 pub ec: i16,
230 pub nw: i16,
232 pub nh: i16,
234 pub nd: i16,
236 pub ni: i16,
238 pub nl: i16,
240 pub nk: i16,
242 pub ne: i16,
244 pub np: i16,
246}
247
248impl SubFileSizes {
249 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 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 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 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
580trait 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 0, 6, 0, 2, 0, 1, 0, 2,
780 0, 3, 0, 4, 0, 5, 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 0, 6, 0, 2, 0, 1, 0, 2,
800 0, 3, 0, 4, 0, 5, 0, 6,
801 0, 0, 0, 0, 1, 1, 0, 0,
802 ],
803 24
804 ),
805 Err(DeserializationError::TooManyExtensibleCharacters(257))
806 ),
807 (
808 inconsistent_sub_file_sizes,
809 extend(
810 &[
811 0, 6, 0, 2, 0, 3, 0, 4,
812 0, 5, 0, 6, 0, 7, 0, 8,
813 0, 9, 0, 10, 0, 11, 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 0, 0, 0, 7, 0, 0, 0, 11,
839 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, 100, 66, 66, 66, 66, 66, 66, 66,
842 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0,
843 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 0, 12, 0, 2, 0, 1, 0,
864 0, 0, 1, 0, 1, 0, 1, 0,
865 1, 0, 0, 0, 0, 0, 0, 0,
866 0, 0, 0, 0, 0, 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(&[0, 0, 0, 7, 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 0, 0, 0, 7, 0, 0, 0, 11,
917 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 3, 68, 69, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
920 0, 128, 0, 0, 9,
921 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 0, 29, 0, 2, 0, 70, 0,
946 72, 0, 6, 0, 3, 0, 4,
947 0, 5, 0, 0, 0, 0, 0, 0,
948 0, 0, 0, 0, 0, 0,
949 0, 0, 0, 0, 5, 35, 16, 0,
950 0, 0, 0, 0, 1, 0, 1, 23, 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 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 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 0, 27, 0, 2, 0, 255, 0,
1008 255, 0, 6, 0, 3, 0, 4,
1009 0, 5, 0, 0, 0, 0, 0, 0,
1010 0, 0, 0, 0, 0, 0,
1011 0, 0, 0, 0, 5, 35, 16, 0,
1012 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 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 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 0, 17, 0, 2, 0, 1, 0, 0,
1057 0, 2, 0, 2, 0, 2, 0, 2,
1058 0, 0, 0, 1, 0, 0, 0, 0,
1059 0, 0, 0, 0, 0, 0, 0, 0,
1060 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0,
1061 29, 0, 0, 0, 0, 0, 0, 0, 31, 0, 0,
1062 0, 0, 0, 0, 0, 37, 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 0, 13, 0, 2, 0, 1, 0, 0,
1205 0, 1, 0, 1, 0, 1, 0, 1,
1206 0, 0, 0, 0, 0, 1, 0, 0,
1207 0, 0, 0, 0, 0, 0, 0, 0,
1208 0, 0, 0, 0, 0, 0, 0, 0, 0,
1209 0, 0, 0, 0, 0, 0, 0, 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 0, 22, 0, 2, 0, 1, 0, 0,
1227 0, 1, 0, 1, 0, 1, 0, 1,
1228 0, 0, 0, 0, 0, 0, 0, 10,
1229 0, 0, 0, 0, 0, 0, 0, 0,
1230 0, 0, 0, 0, 0, 0, 0, 0, 0,
1231 0, 0, 0, 0, 0, 0, 0, 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 0, 14, 0, 2, 0, 1, 0, 0,
1286 0, 1, 0, 1, 0, 1, 0, 1,
1287 0, 2, 0, 0, 0, 0, 0, 0,
1288 0, 0, 0, 0, 0, 0, 0, 0,
1289 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1290 0, 0, 0, 0, 0,
1291 ];
1292 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 0, lf, 0, lh, 0, 1, 0, 0,
1305 0, 1, 0, 1, 0, 1, 0, 1,
1306 0, 0, 0, 0, 0, 0, 0, 0,
1307 ];
1308 v.extend(header);
1309 v.extend(&[0_u8; 16]); v
1311 }
1312
1313 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}