1use std::collections::{BTreeMap, HashMap};
13
14use crate::{
15 ligkern,
16 pl::ast::{DesignSize, ParameterNumber},
17 Char, ExtensibleRecipe, FixWord, Header, NamedParameter, NextLargerProgram,
18 NextLargerProgramWarning,
19};
20
21pub mod ast;
22pub mod cst;
23mod error;
24pub use error::*;
25
26pub const MAX_LIG_KERN_INSTRUCTIONS: u16 = (i16::MAX as u16) - 257;
49
50#[derive(Clone, Default, PartialEq, Eq, Debug)]
52pub struct CharDimensions {
53 pub width: Option<FixWord>,
54 pub height: Option<FixWord>,
55 pub depth: Option<FixWord>,
56 pub italic_correction: Option<FixWord>,
57}
58
59#[derive(Clone, Debug, PartialEq, Eq)]
61pub enum CharTag {
62 Ligature(u16),
63 List(Char),
64 Extension(ExtensibleRecipe),
65}
66
67impl CharTag {
68 pub fn ligature(&self) -> Option<u16> {
69 match self {
70 CharTag::Ligature(l) => Some(*l),
71 _ => None,
72 }
73 }
74 pub fn list(&self) -> Option<Char> {
75 match self {
76 CharTag::List(c) => Some(*c),
77 _ => None,
78 }
79 }
80 pub fn extension(&self) -> Option<ExtensibleRecipe> {
81 match self {
82 CharTag::Extension(u) => Some(u.clone()),
83 CharTag::Ligature(_) | CharTag::List(_) => None,
84 }
85 }
86}
87
88#[derive(PartialEq, Eq, Debug)]
90pub struct File {
91 pub header: Header,
92 pub design_units: FixWord,
93 pub char_dimens: BTreeMap<Char, CharDimensions>,
94 pub char_tags: BTreeMap<Char, CharTag>,
95 pub unset_char_tags: BTreeMap<Char, u8>,
97 pub lig_kern_program: ligkern::lang::Program,
98 pub params: Vec<FixWord>,
99
100 pub additional_widths: Vec<FixWord>,
111 pub additional_heights: Vec<FixWord>,
113 pub additional_depths: Vec<FixWord>,
115 pub additional_italics: Vec<FixWord>,
117}
118
119impl Default for File {
120 fn default() -> Self {
121 Self {
122 header: Header::pl_default(),
123 design_units: FixWord::ONE,
124 char_dimens: Default::default(),
125 char_tags: Default::default(),
126 unset_char_tags: Default::default(),
127 lig_kern_program: Default::default(),
128 params: Default::default(),
129 additional_widths: vec![],
130 additional_heights: vec![],
131 additional_depths: vec![],
132 additional_italics: vec![],
133 }
134 }
135}
136
137impl File {
138 pub fn from_pl_source_code(source: &str) -> (File, Vec<ParseWarning>) {
140 let (ast, mut errors) = ast::Ast::from_pl_source_code(source);
141 let file = File::from_ast(ast, &mut errors);
142 (file, errors)
143 }
144
145 pub fn lig_kern_entrypoints(&self, include_orphans: bool) -> HashMap<Char, u16> {
147 self.char_tags
148 .iter()
149 .filter(|(c, _)| self.char_dimens.contains_key(c) || include_orphans)
150 .filter_map(|d| match d.1 {
151 CharTag::Ligature(l) => Some((*d.0, *l)),
152 _ => None,
153 })
154 .collect()
155 }
156
157 pub fn replace_lig_kern_program(
158 &mut self,
159 program: ligkern::lang::Program,
160 entrypoints: HashMap<Char, u16>,
161 ) {
162 self.lig_kern_program = program;
163 self.char_tags
164 .retain(|_, tag| !matches!(tag, CharTag::Ligature(_)));
165 for (c, ep) in entrypoints {
166 self.char_tags.insert(c, CharTag::Ligature(ep));
167 }
168 }
169
170 pub fn clear_lig_kern_data(&mut self) {
172 self.char_tags = self
174 .char_tags
175 .iter()
176 .filter_map(|(c, tag)| match tag {
177 CharTag::Ligature(_) => None,
178 _ => Some((*c, tag.clone())),
179 })
180 .collect();
181 self.lig_kern_program = Default::default();
182 }
183
184 pub fn from_ast(ast: ast::Ast, errors: &mut Vec<error::ParseWarning>) -> File {
186 let mut file: File = Default::default();
187 let mut lig_kern_precedes = false;
188
189 let mut next_larger_span = HashMap::<Char, std::ops::Range<usize>>::new();
190
191 for node in ast.0 {
192 match node {
193 ast::Root::Checksum(v) => {
194 file.header.checksum = Some(v.data);
195 }
196 ast::Root::DesignSize(v) => {
197 if let DesignSize::Valid(design_size) = v.data {
198 file.header.design_size = design_size;
199 }
200 }
201 ast::Root::DesignUnits(v) => {
202 file.design_units = v.data;
203 }
204 ast::Root::CodingScheme(v) => {
205 file.header.character_coding_scheme = Some(v.data);
206 }
207 ast::Root::Family(v) => {
208 file.header.font_family = Some(v.data);
209 }
210 ast::Root::Face(v) => {
211 file.header.face = Some(v.data);
212 }
213 ast::Root::SevenBitSafeFlag(v) => {
214 file.header.seven_bit_safe = Some(v.data);
215 }
216 ast::Root::Header(v) => match v.left.0.checked_sub(18) {
217 None => errors.push(ParseWarning {
218 span: v.left_span.clone(),
219 knuth_pltotf_offset: Some(v.left_span.end),
220 kind: ParseWarningKind::HeaderIndexIsTooSmall,
221 }),
222 Some(i) => {
223 let i = i as usize;
224 if file.header.additional_data.len() <= i {
225 file.header.additional_data.resize(i + 1, 0);
226 }
227 file.header.additional_data[i] = v.right;
228 }
229 },
230 ast::Root::FontDimension(b) => {
231 for node in b.children {
232 let (number, value) = match node {
233 ast::FontDimension::NamedParam(named_param, v) => {
234 (named_param.number() as usize, v.data)
235 }
236 ast::FontDimension::IndexedParam(v) => (v.left.0 as usize, v.right),
237 ast::FontDimension::Comment(_) => continue,
238 };
239 let index = number - 1;
240 if file.params.len() < number {
241 file.params.resize(number, Default::default());
242 }
243 file.params[index] = value;
244 }
245 }
246 ast::Root::LigTable(b) => {
247 for node in b.children {
248 let mut insert_lig_kern_instruction = |instruction, span| {
249 if file.lig_kern_program.instructions.len()
250 < MAX_LIG_KERN_INSTRUCTIONS as usize
251 {
252 file.lig_kern_program.instructions.push(instruction);
253 } else {
254 errors.push(error::ParseWarning {
256 span,
257 knuth_pltotf_offset: None,
258 kind: ParseWarningKind::LigTableIsTooBig,
259 });
260 }
261 };
262 match node {
263 ast::LigTable::Label(v) => {
264 let u: u16 = file.lig_kern_program.instructions.len().try_into().expect("lig_kern_instructions.len()<= MAX_LIG_KERN_INSTRUCTIONS which is a u16");
265 match v.data {
266 ast::LigTableLabel::Char(c) => {
267 file.char_tags.insert(c, CharTag::Ligature(u));
269 }
270 ast::LigTableLabel::BoundaryChar => {
271 file.lig_kern_program.left_boundary_char_entrypoint =
272 Some(u);
273 }
274 }
275 lig_kern_precedes = false;
276 }
277 ast::LigTable::Lig(post_lig_operation, v) => {
278 insert_lig_kern_instruction(
279 ligkern::lang::Instruction {
280 next_instruction: Some(0),
281 right_char: v.left,
282 operation: ligkern::lang::Operation::Ligature {
283 char_to_insert: v.right,
284 post_lig_operation,
285 post_lig_tag_invalid: false,
286 },
287 },
289 v.left_span,
290 );
291 lig_kern_precedes = true;
292 }
293 ast::LigTable::Kern(v) => {
294 insert_lig_kern_instruction(
295 ligkern::lang::Instruction {
296 next_instruction: Some(0),
297 right_char: v.left,
298 operation: ligkern::lang::Operation::Kern(v.right),
299 },
301 v.left_span,
302 );
303 lig_kern_precedes = true;
304 }
305 ast::LigTable::Stop(_) => {
306 if lig_kern_precedes {
307 file.lig_kern_program
308 .instructions
309 .last_mut()
310 .unwrap()
311 .next_instruction = None;
312 } else {
313 }
315 lig_kern_precedes = false;
316 }
317 ast::LigTable::Skip(v) => {
318 if lig_kern_precedes {
319 file.lig_kern_program
320 .instructions
321 .last_mut()
322 .unwrap()
323 .next_instruction = Some(v.data.0);
324 } else {
325 }
327 lig_kern_precedes = false;
328 }
329 ast::LigTable::Comment(_) => {}
330 }
331 }
332 }
333 ast::Root::BoundaryChar(v) => {
334 file.lig_kern_program.right_boundary_char = Some(v.data);
335 }
336 ast::Root::Character(b) => {
337 let char_dimens = file.char_dimens.entry(b.data).or_default();
338 for node in b.children {
339 match node {
340 ast::Character::Width(v) => {
341 if let Some(additional_width) =
342 char_dimens.width.replace(v.data.unwrap_or(FixWord::ZERO))
343 {
344 file.additional_widths.push(additional_width);
345 }
346 }
347 ast::Character::Height(v) => {
348 if let Some(additional_height) = char_dimens.height.replace(v.data)
349 {
350 file.additional_heights.push(additional_height);
351 }
352 }
353 ast::Character::Depth(v) => {
354 if let Some(additional_depth) = char_dimens.depth.replace(v.data) {
355 file.additional_depths.push(additional_depth);
356 }
357 }
358 ast::Character::ItalicCorrection(v) => {
359 if let Some(additional_italic) =
360 char_dimens.italic_correction.replace(v.data)
361 {
362 file.additional_italics.push(additional_italic);
363 }
364 }
365 ast::Character::NextLarger(c) => {
366 file.char_tags.insert(b.data, CharTag::List(c.data));
368 next_larger_span.insert(b.data, c.data_span);
369 }
370 ast::Character::ExtensibleCharacter(e) => {
371 let mut recipe: ExtensibleRecipe = Default::default();
372 for node in e.children {
373 match node {
374 ast::ExtensibleCharacter::Top(v) => {
375 recipe.top = Some(v.data)
376 }
377 ast::ExtensibleCharacter::Middle(v) => {
378 recipe.middle = Some(v.data)
379 }
380 ast::ExtensibleCharacter::Bottom(v) => {
381 recipe.bottom = Some(v.data)
382 }
383 ast::ExtensibleCharacter::Replicated(v) => {
384 recipe.rep = v.data
385 }
386 ast::ExtensibleCharacter::Comment(_) => {}
387 }
388 }
389 file.char_tags.insert(b.data, CharTag::Extension(recipe));
391 }
392 ast::Character::Comment(_) => {}
393 }
394 }
395 }
396 ast::Root::Comment(_) => {}
397 }
398 }
399
400 errors.sort_by_key(|w| {
405 w.knuth_pltotf_offset
406 .expect("all warnings generated so far have an offset populated")
407 });
408
409 if let Some(final_instruction) = file.lig_kern_program.instructions.last_mut() {
411 if final_instruction.next_instruction == Some(0) {
412 final_instruction.next_instruction = None;
413 }
414 }
415
416 let lig_kern_seven_bit_safe = {
418 for err in crate::ligkern::CompiledProgram::compile(
419 &file.lig_kern_program,
420 file.header.design_size,
421 &[],
422 file.lig_kern_entrypoints(true), )
424 .1
425 {
426 errors.push(ParseWarning {
427 span: 0..0, knuth_pltotf_offset: None,
429 kind: ParseWarningKind::CycleInLigKernProgram(err),
430 });
431 file.clear_lig_kern_data();
432 }
433 file.lig_kern_program
434 .is_seven_bit_safe(file.lig_kern_entrypoints(false))
435 };
436
437 let next_larger_seven_bit_safe = {
439 let (program, next_larger_warnings) = NextLargerProgram::new(
440 file.char_tags
441 .iter()
442 .filter_map(|(c, t)| t.list().map(|next_larger| (*c, next_larger))),
443 |c| file.char_dimens.contains_key(&c),
444 false,
445 );
446 for warning in next_larger_warnings {
447 match &warning {
448 NextLargerProgramWarning::NonExistentCharacter {
449 original: _,
450 next_larger,
451 } => {
452 file.char_dimens.insert(
453 *next_larger,
454 CharDimensions {
455 width: Some(FixWord::ZERO),
456 ..Default::default()
457 },
458 );
459 }
460 NextLargerProgramWarning::InfiniteLoop { original, .. } => {
461 let discriminant = file
462 .char_tags
463 .remove(original)
464 .and_then(|t| t.list())
465 .expect("this char has a NEXTLARGER SPEC");
466 file.unset_char_tags.insert(*original, discriminant.0);
467 }
468 }
469 let span = next_larger_span
470 .get(&warning.bad_char())
471 .cloned()
472 .expect("every char with a next larger tag had a NEXTLARGER AST node");
473 errors.push(ParseWarning {
474 span,
475 knuth_pltotf_offset: None,
476 kind: ParseWarningKind::CycleInNextLargerProgram(warning),
477 });
478 }
479 program.is_seven_bit_safe()
480 };
481
482 let mut extensible_seven_bit_safe = true;
484 file.char_tags
485 .iter()
486 .filter_map(|(c, t)| t.extension().map(|t| (c, t)))
487 .for_each(|(c, e)| {
488 if c.is_seven_bit() && !e.is_seven_bit() {
489 extensible_seven_bit_safe = false;
490 }
491 for c in e.chars() {
492 if let std::collections::btree_map::Entry::Vacant(v) = file.char_dimens.entry(c)
493 {
494 v.insert(Default::default());
495 };
497 }
498 });
499
500 let seven_bit_safe =
501 lig_kern_seven_bit_safe && next_larger_seven_bit_safe && extensible_seven_bit_safe;
502 if file.header.seven_bit_safe == Some(true) && !seven_bit_safe {
503 errors.push(ParseWarning {
504 span: 0..0, knuth_pltotf_offset: None,
506 kind: ParseWarningKind::NotReallySevenBitSafe,
507 });
508 }
509 file.header.seven_bit_safe = Some(seven_bit_safe);
510
511 file
512 }
513}
514
515impl From<crate::File> for File {
516 fn from(tfm_file: crate::File) -> Self {
517 let char_dimens: BTreeMap<Char, CharDimensions> = tfm_file
518 .char_dimens
519 .iter()
520 .map(|(c, info)| {
521 (
522 *c,
523 CharDimensions {
524 width: match info.width_index {
525 super::WidthIndex::Invalid => None,
526 super::WidthIndex::Valid(n) => {
527 tfm_file.widths.get(n.get() as usize).copied()
528 }
529 },
530 height: if info.height_index == 0 {
531 None
532 } else {
533 tfm_file.heights.get(info.height_index as usize).copied()
534 },
535 depth: if info.depth_index == 0 {
536 None
537 } else {
538 tfm_file.depths.get(info.depth_index as usize).copied()
539 },
540 italic_correction: if info.italic_index == 0 {
541 None
542 } else {
543 tfm_file
544 .italic_corrections
545 .get(info.italic_index as usize)
546 .copied()
547 },
548 },
549 )
550 })
551 .collect();
552
553 let mut lig_kern_program = tfm_file.lig_kern_program;
554 lig_kern_program.pack_kerns(&tfm_file.kerns);
555 let lig_kern_entrypoints: HashMap<Char, u16> = tfm_file
556 .char_tags
557 .iter()
558 .filter_map(|(c, t)| {
559 t.ligature()
560 .and_then(|l| lig_kern_program.unpack_entrypoint(l).ok())
561 .map(|e| (*c, e))
562 })
563 .collect();
564
565 let char_tags = tfm_file
566 .char_tags
567 .into_iter()
568 .filter_map(|(c, tag)| match tag {
569 super::CharTag::Ligature(_) => lig_kern_entrypoints
570 .get(&c)
571 .map(|e| (c, CharTag::Ligature(*e))),
572 super::CharTag::List(l) => Some((c, CharTag::List(l))),
573 super::CharTag::Extension(i) => tfm_file
575 .extensible_chars
576 .get(i as usize)
577 .cloned()
578 .map(|t| (c, CharTag::Extension(t))),
579 })
580 .collect();
581
582 File {
583 header: tfm_file.header,
584 design_units: FixWord::ONE,
585 char_dimens,
586 char_tags,
587 unset_char_tags: Default::default(),
588 lig_kern_program,
589 params: tfm_file.params,
590 additional_widths: vec![],
591 additional_heights: vec![],
592 additional_depths: vec![],
593 additional_italics: vec![],
594 }
595 }
596}
597
598impl File {
599 pub fn lower(&self, char_display_format: CharDisplayFormat) -> ast::Ast {
601 let mut roots = vec![];
602
603 if let Some(font_family) = &self.header.font_family {
605 roots.push(ast::Root::Family(font_family.clone().into()))
606 }
607 if let Some(face) = self.header.face {
608 roots.push(ast::Root::Face(face.into()))
609 }
610 for (i, &u) in self.header.additional_data.iter().enumerate() {
611 let i: u8 = i.try_into().unwrap();
612 let i = i.checked_add(18).unwrap(); roots.push(ast::Root::Header((ast::DecimalU8(i), u).into()))
614 }
615 #[derive(Clone, Copy)]
616 enum FontType {
617 Vanilla,
618 TexMathSy,
619 TexMathEx,
620 }
621 let font_type = {
622 let scheme = match &self.header.character_coding_scheme {
623 None => String::new(),
624 Some(scheme) => scheme.to_uppercase(),
625 };
626 if scheme.starts_with("TEX MATH SY") {
627 FontType::TexMathSy
628 } else if scheme.starts_with("TEX MATH EX") {
629 FontType::TexMathEx
630 } else {
631 FontType::Vanilla
632 }
633 };
634 if let Some(scheme) = &self.header.character_coding_scheme {
635 roots.push(ast::Root::CodingScheme(scheme.clone().into()));
636 }
637 roots.extend([
638 ast::Root::DesignSize(
639 if self.header.design_size_valid {
640 DesignSize::Valid(self.header.design_size)
641 } else {
642 DesignSize::Invalid
643 }
644 .into(),
645 ),
646 ast::Root::Comment("DESIGNSIZE IS IN POINTS".into()),
647 ast::Root::Comment("OTHER SIZES ARE MULTIPLES OF DESIGNSIZE".into()),
648 ast::Root::Checksum(self.header.checksum.unwrap_or_default().into()),
649 ]);
650 if self.header.seven_bit_safe == Some(true) {
651 roots.push(ast::Root::SevenBitSafeFlag(true.into()));
652 }
653 let params: Vec<ast::FontDimension> = self
655 .params
656 .iter()
657 .enumerate()
658 .filter_map(|(i, ¶m)| {
659 let i: u16 = match (i + 1).try_into() {
660 Ok(i) => i,
661 Err(_) => return None,
662 };
663 let named_param = match (i, font_type) {
665 (1, _) => NamedParameter::Slant,
666 (2, _) => NamedParameter::Space,
667 (3, _) => NamedParameter::Stretch,
668 (4, _) => NamedParameter::Shrink,
669 (5, _) => NamedParameter::XHeight,
670 (6, _) => NamedParameter::Quad,
671 (7, _) => NamedParameter::ExtraSpace,
672 (8, FontType::TexMathSy) => NamedParameter::Num1,
673 (9, FontType::TexMathSy) => NamedParameter::Num2,
674 (10, FontType::TexMathSy) => NamedParameter::Num3,
675 (11, FontType::TexMathSy) => NamedParameter::Denom1,
676 (12, FontType::TexMathSy) => NamedParameter::Denom2,
677 (13, FontType::TexMathSy) => NamedParameter::Sup1,
678 (14, FontType::TexMathSy) => NamedParameter::Sup2,
679 (15, FontType::TexMathSy) => NamedParameter::Sup3,
680 (16, FontType::TexMathSy) => NamedParameter::Sub1,
681 (17, FontType::TexMathSy) => NamedParameter::Sub2,
682 (18, FontType::TexMathSy) => NamedParameter::SupDrop,
683 (19, FontType::TexMathSy) => NamedParameter::SubDrop,
684 (20, FontType::TexMathSy) => NamedParameter::Delim1,
685 (21, FontType::TexMathSy) => NamedParameter::Delim2,
686 (22, FontType::TexMathSy) => NamedParameter::AxisHeight,
687 (8, FontType::TexMathEx) => NamedParameter::DefaultRuleThickness,
688 (9, FontType::TexMathEx) => NamedParameter::BigOpSpacing1,
689 (10, FontType::TexMathEx) => NamedParameter::BigOpSpacing2,
690 (11, FontType::TexMathEx) => NamedParameter::BigOpSpacing3,
691 (12, FontType::TexMathEx) => NamedParameter::BigOpSpacing4,
692 (13, FontType::TexMathEx) => NamedParameter::BigOpSpacing5,
693 _ => {
694 let parameter_number = ParameterNumber(i);
695 return Some(ast::FontDimension::IndexedParam(
696 (parameter_number, param).into(),
697 ));
698 }
699 };
700 Some(ast::FontDimension::NamedParam(named_param, param.into()))
701 })
702 .collect();
703 if !params.is_empty() {
704 roots.push(ast::Root::FontDimension(((), params).into()));
705 }
706
707 if let Some(boundary_char) = self.lig_kern_program.right_boundary_char {
709 roots.push(ast::Root::BoundaryChar(boundary_char.into()));
710 }
711 let index_to_labels = {
712 let mut m = HashMap::<usize, Vec<Char>>::new();
713 for (c, tag) in &self.char_tags {
714 if let CharTag::Ligature(index) = tag {
715 m.entry(*index as usize).or_default().push(*c);
716 }
717 }
718 m.values_mut().for_each(|v| v.sort());
719 m
720 };
721 let mut l = Vec::<ast::LigTable>::new();
722 let build_lig_kern_op = |instruction: &ligkern::lang::Instruction| match instruction
723 .operation
724 {
725 ligkern::lang::Operation::Kern(kern) => {
726 Some(ast::LigTable::Kern((instruction.right_char, kern).into()))
727 }
728 ligkern::lang::Operation::KernAtIndex(_) => {
729 panic!("tfm::pl::File lig/kern programs cannot contain `KernAtIndex` operations. Use a `Kern` operation instead.");
730 }
731 ligkern::lang::Operation::EntrypointRedirect(_, _) => None,
732 ligkern::lang::Operation::Ligature {
733 char_to_insert,
734 post_lig_operation,
735 post_lig_tag_invalid: _,
736 } => Some(ast::LigTable::Lig(
737 post_lig_operation,
738 (instruction.right_char, char_to_insert).into(),
739 )),
740 };
741
742 let mut unreachable_elems: Option<String> = None;
744 let flush_unreachable_elems = |elems: &mut Option<String>, l: &mut Vec<ast::LigTable>| {
745 if let Some(elems) = elems.take() {
746 l.push(ast::LigTable::Comment(elems))
747 }
748 };
749 for (index, (reachable, instruction)) in self
750 .lig_kern_program
751 .reachable_iter(
752 self.char_tags
753 .iter()
754 .filter_map(|(c, t)| t.ligature().map(|l| (*c, l))),
755 )
756 .zip(&self.lig_kern_program.instructions)
757 .enumerate()
758 {
759 match reachable {
760 ligkern::lang::ReachableIterItem::Reachable { adjusted_skip } => {
761 flush_unreachable_elems(&mut unreachable_elems, &mut l);
762 if let Some(e) = self.lig_kern_program.left_boundary_char_entrypoint {
763 if e as usize == index {
764 l.push(ast::LigTable::Label(
765 ast::LigTableLabel::BoundaryChar.into(),
766 ));
767 }
768 }
769 for label in index_to_labels.get(&index).unwrap_or(&vec![]) {
770 l.push(ast::LigTable::Label(
771 ast::LigTableLabel::Char(*label).into(),
772 ));
773 }
774 if let Some(op) = build_lig_kern_op(instruction) {
775 l.push(op);
776 }
777 match adjusted_skip {
778 Some(i) => l.push(ast::LigTable::Skip(ast::DecimalU8(i).into())),
780 None => {
781 match instruction.next_instruction {
782 None => l.push(ast::LigTable::Stop(().into())),
783 Some(0) => {}
784 Some(i) => l.push(ast::LigTable::Skip(ast::DecimalU8(i).into())),
786 }
787 }
788 }
789 }
790 ligkern::lang::ReachableIterItem::Unreachable => {
791 let unreachable_elems = unreachable_elems.get_or_insert_with(|| {
792 " THIS PART OF THE PROGRAM IS NEVER USED!\n".to_string()
795 });
796 if let Some(op) = build_lig_kern_op(instruction) {
797 unreachable_elems
798 .push_str(&format!["{}", op.lower(char_display_format).display(6, 3)]);
799 }
800 }
801 ligkern::lang::ReachableIterItem::Passthrough => {}
802 }
803 }
804 flush_unreachable_elems(&mut unreachable_elems, &mut l);
805 if !self.lig_kern_program.instructions.is_empty() {
806 roots.push(ast::Root::LigTable(((), l).into()))
807 }
808
809 let ordered_chars = {
811 let mut v: Vec<Char> = self.char_dimens.keys().copied().collect();
812 v.sort();
813 v
814 };
815 for c in &ordered_chars {
816 let data = match self.char_dimens.get(c) {
817 None => continue, Some(data) => data,
819 };
820 let mut v = vec![];
821 match data.width {
822 None => {
823 v.push(ast::Character::Width(None.into()));
824 }
825 Some(width) => {
826 v.push(ast::Character::Width(Some(width).into()));
827 }
828 };
829 if let Some(height) = data.height {
830 v.push(ast::Character::Height(height.into()));
831 }
832 if let Some(depth) = data.depth {
833 v.push(ast::Character::Depth(depth.into()));
834 }
835 if let Some(italic_correction) = data.italic_correction {
836 v.push(ast::Character::ItalicCorrection(italic_correction.into()));
837 }
838
839 match self.char_tags.get(c) {
840 None => {}
841 Some(CharTag::Ligature(entrypoint)) => {
842 let l: Vec<cst::Node> = self
843 .lig_kern_program
844 .instructions_for_entrypoint(*entrypoint)
845 .map(|(_, b)| b)
846 .filter_map(build_lig_kern_op)
847 .map(|n| n.lower(char_display_format))
848 .collect();
849 if l.is_empty() {
850 v.push(ast::Character::Comment("\n".into()));
851 } else {
852 v.push(ast::Character::Comment(format![
853 "\n{}",
854 cst::Cst(l).display(6, 3)
855 ]));
856 }
857 }
858 Some(CharTag::List(c)) => v.push(ast::Character::NextLarger((*c).into())),
859 Some(CharTag::Extension(recipe)) => {
860 let mut r = vec![];
862 if let Some(top) = recipe.top {
863 r.push(ast::ExtensibleCharacter::Top(top.into()));
864 }
865 if let Some(middle) = recipe.middle {
866 r.push(ast::ExtensibleCharacter::Middle(middle.into()));
867 }
868 if let Some(bottom) = recipe.bottom {
869 r.push(ast::ExtensibleCharacter::Bottom(bottom.into()));
870 }
871 let rep = if self.char_dimens.contains_key(&recipe.rep) {
872 recipe.rep
873 } else {
874 *c
875 };
876 r.push(ast::ExtensibleCharacter::Replicated(rep.into()));
877 v.push(ast::Character::ExtensibleCharacter(((), r).into()))
878 }
879 }
880 roots.push(ast::Root::Character((*c, v).into()));
881 }
882
883 ast::Ast(roots)
884 }
885
886 pub fn display(&self, indent: usize, char_display_format: CharDisplayFormat) -> Display<'_> {
891 Display {
892 pl_file: self,
893 indent,
894 char_display_format,
895 }
896 }
897}
898
899pub struct Display<'a> {
903 pl_file: &'a File,
904 indent: usize,
905 char_display_format: CharDisplayFormat,
906}
907
908impl<'a> std::fmt::Display for Display<'a> {
909 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
910 let ast = self.pl_file.lower(self.char_display_format);
911 let cst = ast.lower(self.char_display_format);
912 let d = cst.display(0, self.indent);
913 d.fmt(f)
914 }
915}
916
917#[derive(Default, Debug, Clone, Copy)]
919pub enum CharDisplayFormat {
920 #[default]
924 Default,
925 Ascii,
928 Octal,
930}
931
932struct Chars<'a> {
934 s: &'a str,
935}
936
937impl<'a> Chars<'a> {
938 fn new(s: &'a str) -> Self {
939 Self { s }
940 }
941}
942
943impl<'a> Iterator for Chars<'a> {
944 type Item = char;
945 fn next(&mut self) -> Option<Self::Item> {
946 let mut iter = self.s.chars();
947 match iter.next() {
948 Some(mut c) => {
949 if c == '\r' {
950 let mut skipped = 1;
951 loop {
952 match iter.next() {
953 Some('\r') => {
954 skipped += 1;
955 continue;
956 }
957 Some('\n') => {
958 c = '\n';
959 self.s = &self.s[skipped..];
960 break;
961 }
962 _ => break,
963 }
964 }
965 }
966 self.s = &self.s[c.len_utf8()..];
967 Some(c)
968 }
969 None => None,
970 }
971 }
972 fn size_hint(&self) -> (usize, Option<usize>) {
973 (0, Some(self.s.len()))
974 }
975}
976
977#[cfg(test)]
978mod tests {
979 use super::*;
980 use crate::{Face, WidthIndex};
981
982 fn run_from_pl_source_code_test(source: &str, mut want: File) {
983 want.header.seven_bit_safe = Some(true);
984 let (got, errors) = File::from_pl_source_code(source);
985 assert_eq!(errors, vec![]);
986 assert_eq!(got, want);
987 }
988
989 macro_rules! from_pl_source_code_tests {
990 ( $( ($name: ident, $source: expr, $want: expr, ), )+ ) => {
991 $(
992 #[test]
993 fn $name() {
994 let source = $source;
995 let want = $want;
996 run_from_pl_source_code_test(source, want);
997 }
998 )+
999 };
1000 }
1001
1002 from_pl_source_code_tests!(
1003 (
1004 checksum,
1005 "(CHECKSUM H 7)",
1006 File {
1007 header: Header {
1008 checksum: Some(0x7),
1009 ..Header::pl_default()
1010 },
1011 ..Default::default()
1012 },
1013 ),
1014 (
1015 checksum_0,
1016 "(CHECKSUM O 0)",
1017 File {
1018 header: Header {
1019 checksum: Some(0),
1020 ..Header::pl_default()
1021 },
1022 ..Default::default()
1023 },
1024 ),
1025 (
1026 design_size,
1027 "(DESIGNSIZE R 3.0)",
1028 File {
1029 header: Header {
1030 design_size: (FixWord::ONE * 3).into(),
1031 ..Header::pl_default()
1032 },
1033 ..Default::default()
1034 },
1035 ),
1036 (
1037 design_units,
1038 "(DESIGNUNITS R 2.0)",
1039 File {
1040 design_units: FixWord::ONE * 2,
1041 ..Default::default()
1042 },
1043 ),
1044 (
1045 coding_scheme,
1046 "(CODINGSCHEME Hola Mundo)",
1047 File {
1048 header: Header {
1049 character_coding_scheme: Some("Hola Mundo".into()),
1050 ..Header::pl_default()
1051 },
1052 ..Default::default()
1053 },
1054 ),
1055 (
1056 font_family,
1057 "(FAMILY Hello World)",
1058 File {
1059 header: Header {
1060 font_family: Some("Hello World".into()),
1061 ..Header::pl_default()
1062 },
1063 ..Default::default()
1064 },
1065 ),
1066 (
1067 face,
1068 "(FACE H 29)",
1069 File {
1070 header: Header {
1071 face: Some(Face::Other(0x29)),
1072 ..Header::pl_default()
1073 },
1074 ..Default::default()
1075 },
1076 ),
1077 (
1078 seven_bit_safe_flag,
1079 "(SEVENBITSAFEFLAG FALSE)",
1080 File {
1081 header: Header {
1082 seven_bit_safe: Some(false),
1083 ..Header::pl_default()
1084 },
1085 ..Default::default()
1086 },
1087 ),
1088 (
1089 additional_header_data,
1090 "(HEADER D 20 H 1234567)",
1091 File {
1092 header: Header {
1093 additional_data: vec![0, 0, 0x1234567],
1094 ..Header::pl_default()
1095 },
1096 ..Default::default()
1097 },
1098 ),
1099 (
1100 boundary_char,
1101 "(BOUNDARYCHAR C a)",
1102 File {
1103 lig_kern_program: ligkern::lang::Program {
1104 instructions: vec![],
1105 right_boundary_char: Some('a'.try_into().unwrap()),
1106 left_boundary_char_entrypoint: None,
1107 passthrough: Default::default(),
1108 },
1109 ..Default::default()
1110 },
1111 ),
1112 (
1113 named_param,
1114 "(FONTDIMEN (STRETCH D 13.0))",
1115 File {
1116 params: vec![FixWord::ZERO, FixWord::ZERO, FixWord::ONE * 13],
1117 ..Default::default()
1118 },
1119 ),
1120 (
1121 indexed_param,
1122 "(FONTDIMEN (PARAMETER D 2 D 15.0))",
1123 File {
1124 params: vec![FixWord::ZERO, FixWord::ONE * 15],
1125 ..Default::default()
1126 },
1127 ),
1128 (
1129 kern,
1130 "(LIGTABLE (KRN C r D 15.0))",
1131 File {
1132 lig_kern_program: ligkern::lang::Program {
1133 instructions: vec![ligkern::lang::Instruction {
1134 next_instruction: None,
1135 right_char: 'r'.try_into().unwrap(),
1136 operation: ligkern::lang::Operation::Kern(FixWord::ONE * 15),
1137 },],
1138 right_boundary_char: None,
1139 left_boundary_char_entrypoint: None,
1140 passthrough: Default::default(),
1141 },
1142 ..Default::default()
1143 },
1144 ),
1145 (
1146 kern_with_stop,
1147 "(LIGTABLE (KRN C r D 15.0) (STOP) (KRN C t D 15.0))",
1148 File {
1149 lig_kern_program: ligkern::lang::Program {
1150 instructions: vec![
1151 ligkern::lang::Instruction {
1152 next_instruction: None,
1153 right_char: 'r'.try_into().unwrap(),
1154 operation: ligkern::lang::Operation::Kern(FixWord::ONE * 15),
1155 },
1156 ligkern::lang::Instruction {
1157 next_instruction: None,
1158 right_char: 't'.try_into().unwrap(),
1159 operation: ligkern::lang::Operation::Kern(FixWord::ONE * 15),
1160 },
1161 ],
1162 right_boundary_char: None,
1163 left_boundary_char_entrypoint: None,
1164 passthrough: Default::default(),
1165 },
1166 ..Default::default()
1167 },
1168 ),
1169 (
1170 kern_with_skip,
1171 "(LIGTABLE (KRN C r D 15.0) (SKIP D 3))",
1172 File {
1173 lig_kern_program: ligkern::lang::Program {
1174 instructions: vec![ligkern::lang::Instruction {
1175 next_instruction: Some(3),
1176 right_char: 'r'.try_into().unwrap(),
1177 operation: ligkern::lang::Operation::Kern(FixWord::ONE * 15),
1178 },],
1179 right_boundary_char: None,
1180 left_boundary_char_entrypoint: None,
1181 passthrough: Default::default(),
1182 },
1183 ..Default::default()
1184 },
1185 ),
1186 (
1187 lig,
1188 "(LIGTABLE (LIG/> C r C t))",
1189 File {
1190 lig_kern_program: ligkern::lang::Program {
1191 instructions: vec![ligkern::lang::Instruction {
1192 next_instruction: None,
1193 right_char: 'r'.try_into().unwrap(),
1194 operation: ligkern::lang::Operation::Ligature {
1195 char_to_insert: 't'.try_into().unwrap(),
1196 post_lig_operation:
1197 ligkern::lang::PostLigOperation::RetainRightMoveToRight,
1198 post_lig_tag_invalid: false,
1199 },
1200 },],
1201 right_boundary_char: None,
1202 left_boundary_char_entrypoint: None,
1203 passthrough: Default::default(),
1204 },
1205 ..Default::default()
1206 },
1207 ),
1208 (
1209 lig_kern_entrypoints,
1210 "(LIGTABLE (LABEL C e) (KRN C r D 15.0) (LABEL C d))",
1211 File {
1212 lig_kern_program: ligkern::lang::Program {
1213 instructions: vec![ligkern::lang::Instruction {
1214 next_instruction: None,
1215 right_char: 'r'.try_into().unwrap(),
1216 operation: ligkern::lang::Operation::Kern(FixWord::ONE * 15),
1217 },],
1218 right_boundary_char: None,
1219 left_boundary_char_entrypoint: None,
1220 passthrough: Default::default(),
1221 },
1222 char_tags: BTreeMap::from([
1223 ('e'.try_into().unwrap(), CharTag::Ligature(0),),
1224 ('d'.try_into().unwrap(), CharTag::Ligature(1),),
1225 ]),
1226 ..Default::default()
1227 },
1228 ),
1229 (
1230 lig_kern_boundary_char_entrypoint,
1231 "(LIGTABLE (LABEL BOUNDARYCHAR) (KRN C r D 15.0))",
1232 File {
1233 lig_kern_program: ligkern::lang::Program {
1234 instructions: vec![ligkern::lang::Instruction {
1235 next_instruction: None,
1236 right_char: 'r'.try_into().unwrap(),
1237 operation: ligkern::lang::Operation::Kern(FixWord::ONE * 15),
1238 },],
1239 right_boundary_char: None,
1240 left_boundary_char_entrypoint: Some(0),
1241 passthrough: Default::default(),
1242 },
1243 ..Default::default()
1244 },
1245 ),
1246 (
1247 char_width,
1248 "(CHARACTER C r (CHARWD D 15.0))",
1249 File {
1250 char_dimens: BTreeMap::from([(
1251 'r'.try_into().unwrap(),
1252 CharDimensions {
1253 width: Some(FixWord::ONE * 15),
1254 ..Default::default()
1255 }
1256 )]),
1257 ..Default::default()
1258 },
1259 ),
1260 (
1261 char_height,
1262 "(CHARACTER C r (CHARHT D 15.0))",
1263 File {
1264 char_dimens: BTreeMap::from([(
1265 'r'.try_into().unwrap(),
1266 CharDimensions {
1267 height: Some(FixWord::ONE * 15),
1268 ..Default::default()
1269 }
1270 )]),
1271 ..Default::default()
1272 },
1273 ),
1274 (
1275 char_depth,
1276 "(CHARACTER C r (CHARDP D 15.0))",
1277 File {
1278 char_dimens: BTreeMap::from([(
1279 'r'.try_into().unwrap(),
1280 CharDimensions {
1281 depth: Some(FixWord::ONE * 15),
1282 ..Default::default()
1283 }
1284 )]),
1285 ..Default::default()
1286 },
1287 ),
1288 (
1289 char_italic_correction,
1290 "(CHARACTER C r (CHARIC D 15.0))",
1291 File {
1292 char_dimens: BTreeMap::from([(
1293 'r'.try_into().unwrap(),
1294 CharDimensions {
1295 italic_correction: Some(FixWord::ONE * 15),
1296 ..Default::default()
1297 }
1298 )]),
1299 ..Default::default()
1300 },
1301 ),
1302 (
1303 char_next_larger,
1304 "(CHARACTER C A) (CHARACTER C r (NEXTLARGER C A))",
1305 File {
1306 char_dimens: BTreeMap::from([
1307 (
1308 'r'.try_into().unwrap(),
1309 CharDimensions {
1310 ..Default::default()
1311 }
1312 ),
1313 (
1314 'A'.try_into().unwrap(),
1315 CharDimensions {
1316 ..Default::default()
1317 }
1318 ),
1319 ]),
1320 char_tags: BTreeMap::from([(
1321 'r'.try_into().unwrap(),
1322 CharTag::List('A'.try_into().unwrap()),
1323 )]),
1324 ..Default::default()
1325 },
1326 ),
1327 (
1328 char_extensible_recipe_empty,
1329 "(CHARACTER C r (VARCHAR))",
1330 File {
1331 char_dimens: BTreeMap::from([
1332 (Char(0), Default::default(),),
1333 ('r'.try_into().unwrap(), Default::default(),),
1334 ]),
1335 char_tags: BTreeMap::from([(
1336 'r'.try_into().unwrap(),
1337 CharTag::Extension(Default::default()),
1338 )]),
1339 ..Default::default()
1340 },
1341 ),
1342 (
1343 char_extensible_recipe_data,
1344 "(CHARACTER C r (VARCHAR (TOP O 1) (MID O 2) (BOT O 3) (REP O 4)))",
1345 File {
1346 char_dimens: BTreeMap::from([
1347 (Char(1), Default::default(),),
1348 (Char(2), Default::default(),),
1349 (Char(3), Default::default(),),
1350 (Char(4), Default::default(),),
1351 ('r'.try_into().unwrap(), Default::default(),),
1352 ]),
1353 char_tags: BTreeMap::from([(
1354 'r'.try_into().unwrap(),
1355 CharTag::Extension(ExtensibleRecipe {
1356 top: Some(Char(1)),
1357 middle: Some(Char(2)),
1358 bottom: Some(Char(3)),
1359 rep: Char(4),
1360 }),
1361 )]),
1362 ..Default::default()
1363 },
1364 ),
1365 );
1366
1367 fn run_from_tfm_file_test(tfm_file: crate::File, want: File) {
1368 let got: File = tfm_file.into();
1369 assert_eq!(got, want);
1370 }
1371
1372 macro_rules! from_tfm_file_tests {
1373 ( $( ($name: ident, $tfm_file: expr, $pl_file: expr, ), )+ ) => {
1374 $(
1375 #[test]
1376 fn $name() {
1377 let tfm_file = $tfm_file;
1378 let want = $pl_file;
1379 run_from_tfm_file_test(tfm_file, want);
1380 }
1381 )+
1382 };
1383 }
1384
1385 from_tfm_file_tests!((
1386 gap_in_chars,
1387 crate::File {
1388 char_dimens: BTreeMap::from([
1389 (
1390 Char('A'.try_into().unwrap()),
1391 crate::CharDimensions {
1392 width_index: WidthIndex::Valid(1.try_into().unwrap()),
1393 height_index: 0,
1394 depth_index: 0,
1395 italic_index: 0,
1396 }
1397 ),
1398 (
1399 Char('C'.try_into().unwrap()),
1400 crate::CharDimensions {
1401 width_index: WidthIndex::Valid(2.try_into().unwrap()),
1402 height_index: 0,
1403 depth_index: 0,
1404 italic_index: 0,
1405 }
1406 ),
1407 (
1408 Char('E'.try_into().unwrap()),
1409 crate::CharDimensions {
1410 width_index: WidthIndex::Invalid,
1411 height_index: 0,
1412 depth_index: 0,
1413 italic_index: 0,
1414 }
1415 ),
1416 ]),
1417 widths: vec![FixWord::ZERO, FixWord::ONE, FixWord::ONE * 2],
1418 ..Default::default()
1419 },
1420 File {
1421 char_dimens: BTreeMap::from([
1422 (
1423 'A'.try_into().unwrap(),
1424 CharDimensions {
1425 width: Some(FixWord::ONE),
1426 ..Default::default()
1427 }
1428 ),
1429 (
1430 'C'.try_into().unwrap(),
1431 CharDimensions {
1432 width: Some(FixWord::ONE * 2),
1433 ..Default::default()
1434 }
1435 ),
1436 (
1437 'E'.try_into().unwrap(),
1438 CharDimensions {
1439 width: None,
1440 ..Default::default()
1441 }
1442 ),
1443 ]),
1444 ..<File as From<crate::File>>::from(Default::default())
1445 },
1446 ),);
1447}