1use super::*;
2use std::collections::HashSet;
3
4pub fn compile(
5 program: &lang::Program,
6 design_size: FixWord,
7 kerns: &[FixWord],
8 entry_points: &HashMap<Char, u16>,
9) -> (CompiledProgram, Vec<InfiniteLoopError>) {
10 let pair_to_instruction = build_node_to_program_start_map(program, entry_points);
11 let (replacements, infinite_loop_errors) =
12 calculate_replacements(program, design_size, kerns, pair_to_instruction);
13 let program = CompiledProgram {
14 right_boundary_char: program.right_boundary_char,
15 replacements: replacements
16 .into_iter()
17 .map(|(node, replacement)| ((node.0.char_or(), node.1), replacement))
18 .collect(),
19 };
20 (program, infinite_loop_errors)
21}
22
23#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
24struct Node(LeftChar, Char);
25
26#[derive(Debug)]
27struct OngoingCalculation {
28 node: Node,
31
32 finalized: Vec<IntermediateOp>,
33 pending: Pending,
37}
38
39#[derive(Debug)]
40struct Pending(Option<C>, C, Option<C>);
41
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct C {
44 pub c: Char,
45 pub is_lig: bool,
46}
47
48impl C {
49 fn left_char(c: LeftChar) -> Option<Self> {
50 match c {
51 LeftChar::Char(c) => Some(Self { c, is_lig: false }),
52 LeftChar::BoundaryChar => None,
53 }
54 }
55 fn char(c: Char) -> Self {
56 Self { c, is_lig: false }
57 }
58}
59
60impl OngoingCalculation {
61 fn child(&self) -> Node {
62 Node(
63 match &self.pending.0 {
64 Some(c) => LeftChar::Char(c.c),
65 None => LeftChar::BoundaryChar,
66 },
67 self.pending.1.c,
68 )
69 }
70}
71
72#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
73enum LeftChar {
74 Char(Char),
75 BoundaryChar,
76}
77
78impl LeftChar {
79 fn char_or(&self) -> Option<Char> {
80 match self {
81 LeftChar::Char(c) => Some(*c),
82 LeftChar::BoundaryChar => None,
83 }
84 }
85}
86
87impl From<Char> for LeftChar {
88 fn from(value: Char) -> Self {
89 LeftChar::Char(value)
90 }
91}
92
93impl TryFrom<LeftChar> for Char {
94 type Error = ();
95
96 fn try_from(value: LeftChar) -> Result<Self, Self::Error> {
97 match value {
98 LeftChar::Char(c) => Ok(c),
99 LeftChar::BoundaryChar => Err(()),
100 }
101 }
102}
103
104impl std::fmt::Display for LeftChar {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 match self {
107 LeftChar::Char(char) => write!(f, "{char}"),
108 LeftChar::BoundaryChar => Ok(()),
109 }
110 }
111}
112
113fn build_node_to_program_start_map(
114 program: &lang::Program,
115 entry_points: &HashMap<Char, u16>,
116) -> HashMap<Node, usize> {
117 let mut result = HashMap::<Node, usize>::new();
118 let all_entry_points = entry_points
119 .iter()
120 .map(|(c, e)| (LeftChar::Char(*c), *e))
121 .chain(
122 program
123 .left_boundary_char_entrypoint
124 .iter()
125 .map(|e| (LeftChar::BoundaryChar, *e)),
126 );
127 for (left, entry_point) in all_entry_points {
128 let mut next_instruction = Some(entry_point as usize);
129 while let Some(next) = next_instruction {
130 let instruction = match program.instructions.get(next) {
131 None => {
132 break;
134 }
135 Some(instruction) => instruction,
136 };
137 next_instruction = instruction
138 .next_instruction
139 .map(|increment| next + 1 + (increment as usize));
140
141 result
143 .entry(Node(left, instruction.right_char))
144 .or_insert(next);
145 }
146 }
147 result
148}
149
150#[derive(Clone, Debug, PartialEq, Eq)]
151pub(crate) struct Replacement(pub(super) Vec<IntermediateOp>, pub C);
152
153macro_rules! filter_left_boundary {
154 ($head:expr, $($tail:expr,)*) => (
155 match $head {
156 None => vec![$($tail),*],
157 Some(head) => vec![head, $($tail),*],
158 }
159 );
160}
161
162fn calculate_replacements(
163 program: &lang::Program,
164 design_size: FixWord,
165 kerns: &[FixWord],
166 pair_to_instruction: HashMap<Node, usize>,
167) -> (HashMap<Node, Replacement>, Vec<InfiniteLoopError>) {
168 let mut result: HashMap<Node, Replacement> = Default::default();
169 let mut actionable: Vec<OngoingCalculation> = vec![];
170 let mut node_to_parents: HashMap<Node, Vec<OngoingCalculation>> = Default::default();
171 for (&pair, &index) in &pair_to_instruction {
172 let Node(left, right) = pair;
173 let operation = program.instructions[index].operation;
174 let operation = match operation {
175 lang::Operation::EntrypointRedirect(u, _) => {
176 let [op_byte, remainder] = u.to_be_bytes();
179 lang::Operation::lig_kern_operation_from_bytes(op_byte, remainder)
180 }
181 operation => operation,
182 };
183 let (finalized, pending): (Vec<C>, Option<Pending>) = match operation {
184 lang::Operation::Kern(kern) => {
185 result.insert(
186 pair,
187 Replacement(
188 match C::left_char(left) {
189 None => vec![IntermediateOp::Kern(kern.to_scaled(design_size))],
190 Some(left) => vec![
191 IntermediateOp::C(left),
192 IntermediateOp::Kern(kern.to_scaled(design_size)),
193 ],
194 },
195 C::char(right),
196 ),
197 );
198 continue;
199 }
200 lang::Operation::KernAtIndex(index) => {
201 let kern = kerns.get(index as usize).copied().unwrap_or_default();
202 result.insert(
203 pair,
204 Replacement(
205 match C::left_char(left) {
206 None => vec![IntermediateOp::Kern(kern.to_scaled(design_size))],
207 Some(left) => vec![
208 IntermediateOp::C(left),
209 IntermediateOp::Kern(kern.to_scaled(design_size)),
210 ],
211 },
212 C::char(right),
213 ),
214 );
215 continue;
216 }
217 lang::Operation::EntrypointRedirect(_, _) => {
218 continue;
219 }
220 lang::Operation::Ligature {
221 char_to_insert,
222 post_lig_operation,
223 post_lig_tag_invalid: _,
224 } => match post_lig_operation {
225 lang::PostLigOperation::RetainBothMoveNowhere => (
226 vec![],
228 Some(Pending(
230 C::left_char(left),
231 C {
232 c: char_to_insert,
233 is_lig: true,
234 },
235 Some(C::char(right)),
236 )),
237 ),
238 lang::PostLigOperation::RetainBothMoveToInserted => (
239 filter_left_boundary![C::left_char(left),],
241 Some(Pending(
243 Some(C {
244 c: char_to_insert,
245 is_lig: true,
246 }),
247 C::char(right),
248 None,
249 )),
250 ),
251 lang::PostLigOperation::RetainBothMoveToRight => (
252 filter_left_boundary![
254 C::left_char(left),
255 C {
256 c: char_to_insert,
257 is_lig: true,
258 },
259 C::char(right),
260 ],
261 None,
263 ),
264 lang::PostLigOperation::RetainRightMoveToInserted => (
265 vec![],
267 Some(Pending(
269 Some(C {
270 c: char_to_insert,
271 is_lig: true,
272 }),
273 C::char(right),
274 None,
275 )),
276 ),
277 lang::PostLigOperation::RetainRightMoveToRight => (
278 vec![
280 C {
281 c: char_to_insert,
282 is_lig: true,
283 },
284 C::char(right),
285 ],
286 None,
288 ),
289 lang::PostLigOperation::RetainLeftMoveNowhere => (
290 vec![],
292 Some(Pending(
294 C::left_char(left),
295 C {
296 c: char_to_insert,
297 is_lig: true,
298 },
299 None,
300 )),
301 ),
302 lang::PostLigOperation::RetainLeftMoveToInserted => (
303 filter_left_boundary![
305 C::left_char(left),
306 C {
307 c: char_to_insert,
308 is_lig: true,
309 },
310 ],
311 None,
313 ),
314 lang::PostLigOperation::RetainNeitherMoveToInserted => (
315 vec![C {
317 c: char_to_insert,
318 is_lig: true,
319 }],
320 None,
322 ),
323 },
324 };
325 if let Some(pending) = pending {
326 let finalized = finalized.into_iter().map(IntermediateOp::C).collect();
327 actionable.push(OngoingCalculation {
328 node: pair,
329 finalized,
330 pending,
331 });
332 node_to_parents.insert(pair, vec![]);
333 } else {
334 let mut finalized = finalized;
335 let last = finalized.pop().unwrap();
336 let finalized = finalized.into_iter().map(IntermediateOp::C).collect();
337 result.insert(pair, Replacement(finalized, last));
338 }
339 }
340
341 while let Some(mut calc) = actionable.pop() {
342 let child = calc.child();
343 if let Some(blocking) = node_to_parents.get_mut(&child) {
344 blocking.push(calc);
345 continue;
346 }
347 let last = match result.get(&child) {
348 None => {
349 match calc.pending.0.clone() {
351 None => {
352 }
354 Some(left) => {
355 calc.finalized.push(IntermediateOp::C(left));
356 }
357 }
358 calc.pending.1.clone()
359 }
360 Some(replacement) => {
361 let mut consumes_left_lig = match calc.pending.0 {
364 None => false,
365 Some(left) => left.is_lig,
366 };
367 calc.finalized
368 .extend(replacement.0.iter().map(|elem| match elem {
369 IntermediateOp::Kern(kern) => IntermediateOp::Kern(*kern),
370 IntermediateOp::C(c) => {
371 let op = IntermediateOp::C(C {
372 c: c.c,
373 is_lig: c.is_lig || consumes_left_lig,
374 });
375 consumes_left_lig = false;
376 op
377 }
378 }));
379 let right_is_lig = calc.pending.1.is_lig;
380 C {
381 c: replacement.1.c,
382 is_lig: replacement.1.is_lig || consumes_left_lig || right_is_lig,
383 }
384 }
385 };
386 match calc.pending.2 {
387 None => {
388 if let Some(blocking) = node_to_parents.remove(&calc.node) {
389 actionable.extend(blocking);
390 }
391 result.insert(calc.node, Replacement(calc.finalized, last));
392 }
393 Some(new_right) => {
394 calc.pending = Pending(Some(last), new_right, None);
395 actionable.push(calc);
396 }
397 }
398 }
399
400 let mut node_to_child: HashMap<Node, Node> = node_to_parents
425 .into_values()
426 .flatten()
427 .map(|calc| (calc.node, calc.child()))
428 .collect();
429 let mut knuth_ordered_nodes: Vec<Node> = node_to_child.keys().copied().collect();
430 knuth_ordered_nodes.sort_by(|lhs, rhs| {
431 lhs.0
432 .cmp(&rhs.0)
433 .then(pair_to_instruction[lhs].cmp(&pair_to_instruction[rhs]))
434 });
435
436 let mut infinite_loop_errors: Vec<InfiniteLoopError> = vec![];
437 for mut node in knuth_ordered_nodes {
438 let mut seen = HashSet::<Node>::new();
439 while let Some(child) = node_to_child.remove(&node) {
440 seen.insert(node);
441 node = child;
442 }
443 if seen.contains(&node) {
456 infinite_loop_errors.push(InfiniteLoopError {
457 starting_pair: (node.0.char_or(), node.1),
458 });
459 }
460 }
461 (result, infinite_loop_errors)
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467 use lang::PostLigOperation::*;
468
469 fn new_kern(
470 next_instruction: Option<u8>,
471 right_char: char,
472 kern: FixWord,
473 ) -> lang::Instruction {
474 lang::Instruction {
475 next_instruction,
476 right_char: right_char.try_into().unwrap(),
477 operation: lang::Operation::Kern(kern),
478 }
479 }
480
481 pub fn new_lig(
482 next_instruction: Option<u8>,
483 right_char: char,
484 char_to_insert: char,
485 post_lig_operation: lang::PostLigOperation,
486 ) -> lang::Instruction {
487 lang::Instruction {
488 next_instruction,
489 right_char: right_char.try_into().unwrap(),
490 operation: lang::Operation::Ligature {
491 char_to_insert: char_to_insert.try_into().unwrap(),
492 post_lig_operation,
493 post_lig_tag_invalid: false,
494 },
495 }
496 }
497
498 fn run_success_test(
499 instructions: Vec<lang::Instruction>,
500 entry_points: Vec<(char, u16)>,
501 want_new: Vec<(char, char, Vec<IntermediateOp>, C)>,
502 ) {
503 let entry_points: HashMap<Char, u16> = entry_points
504 .into_iter()
505 .map(|(c, u)| (c.try_into().unwrap(), u))
506 .collect();
507 let want: HashMap<(Option<Char>, Char), Replacement> = want_new
508 .into_iter()
509 .map(|t| {
510 (
511 (Some(t.0.try_into().unwrap()), t.1.try_into().unwrap()),
512 Replacement(t.2, t.3),
513 )
514 })
515 .collect();
516 let program = lang::Program {
517 instructions,
518 ..Default::default()
519 };
520 let (compiled_program, infinite_loop_error_or) =
521 compile(&program, FixWord::ONE, &vec![], &entry_points);
522 assert!(infinite_loop_error_or.is_empty(), "no infinite loop errors");
523
524 let mut got: HashMap<(Option<Char>, Char), Replacement> = Default::default();
525 for pair in compiled_program.all_pairs_with_replacements() {
526 let replacement = compiled_program
527 .get_replacement(pair.0, Some(pair.1))
528 .unwrap();
529 got.insert(pair, replacement.clone());
530 }
531
532 assert_eq!(got, want);
533 }
534
535 macro_rules! success_tests {
536 ( $( ($name: ident, $instructions: expr, $entry_points: expr, $want_new: expr, ), )+ ) => {
537 $(
538 #[test]
539 fn $name() {
540 let instructions = $instructions;
541 let entry_points = $entry_points;
542 let want_new = $want_new;
543 run_success_test(instructions, entry_points, want_new);
544 }
545 )+
546 };
547 }
548
549 success_tests!(
550 (empty_program, vec![], vec![], vec![],),
551 (
552 kern,
553 vec![new_kern(None, 'V', FixWord::ONE)],
554 vec![('A', 0)],
555 vec![(
556 'A',
557 'V',
558 vec![
559 IntermediateOp::C(C::char(Char::A)),
560 IntermediateOp::Kern(common::Scaled::ONE)
561 ],
562 C::char(Char::V),
563 )],
564 ),
565 (
566 same_kern_for_multiple_left_characters,
567 vec![new_kern(None, 'V', FixWord::ONE)],
568 vec![('A', 0), ('B', 0)],
569 vec![
570 (
571 'A',
572 'V',
573 vec![
574 IntermediateOp::C(C::char(Char::A)),
575 IntermediateOp::Kern(common::Scaled::ONE)
576 ],
577 C::char(Char::V),
578 ),
579 (
580 'B',
581 'V',
582 vec![
583 IntermediateOp::C(C::char(Char::B)),
584 IntermediateOp::Kern(common::Scaled::ONE)
585 ],
586 C::char(Char::V),
587 ),
588 ],
589 ),
590 (
591 duplicate_kern,
592 vec![
593 new_kern(Some(0), 'V', FixWord::ONE * 2),
594 new_kern(None, 'V', FixWord::ONE * 3),
595 ],
596 vec![('A', 0)],
597 vec![(
598 'A',
599 'V',
600 vec![
601 IntermediateOp::C(C::char(Char::A)),
602 IntermediateOp::Kern(common::Scaled::ONE * 2)
603 ],
604 C::char(Char::V),
605 ),],
606 ),
607 (
608 kern_instructions_with_relationship,
609 vec![
610 new_kern(Some(0), 'V', FixWord::ONE * 2),
611 new_kern(None, 'W', FixWord::ONE * 3),
612 new_kern(None, 'X', FixWord::ONE * 4),
613 ],
614 vec![('A', 0), ('B', 1), ('C', 2)],
615 vec![
616 (
617 'A',
618 'V',
619 vec![
620 IntermediateOp::C(C::char(Char::A)),
621 IntermediateOp::Kern(common::Scaled::ONE * 2)
622 ],
623 C::char(Char::V),
624 ),
625 (
626 'A',
627 'W',
628 vec![
629 IntermediateOp::C(C::char(Char::A)),
630 IntermediateOp::Kern(common::Scaled::ONE * 3)
631 ],
632 C::char(Char::W),
633 ),
634 (
635 'B',
636 'W',
637 vec![
638 IntermediateOp::C(C::char(Char::B)),
639 IntermediateOp::Kern(common::Scaled::ONE * 3)
640 ],
641 C::char(Char::W),
642 ),
643 (
644 'C',
645 'X',
646 vec![
647 IntermediateOp::C(C::char(Char::C)),
648 IntermediateOp::Kern(common::Scaled::ONE * 4)
649 ],
650 C::char(Char::X),
651 ),
652 ],
653 ),
654 (
655 single_lig_1,
656 vec![new_lig(None, 'B', 'Z', RetainNeitherMoveToInserted)],
657 vec![('A', 0)],
658 vec![(
659 'A',
660 'B',
661 vec![],
662 C {
663 c: Char::Z,
664 is_lig: true,
665 }
666 ),],
667 ),
668 (
669 single_lig_2,
670 vec![
671 new_lig(Some(0), 'B', 'Z', RetainLeftMoveToInserted),
672 new_kern(None, 'Z', FixWord::ONE),
673 ],
674 vec![('A', 0)],
675 vec![
676 (
677 'A',
678 'B',
679 vec![IntermediateOp::C(C::char(Char::A)),],
680 C {
681 c: Char::Z,
682 is_lig: true,
683 }
684 ),
685 (
686 'A',
687 'Z',
688 vec![
689 IntermediateOp::C(C::char(Char::A)),
690 IntermediateOp::Kern(common::Scaled::ONE)
691 ],
692 C::char(Char::Z),
693 ),
694 ],
695 ),
696 (
697 retain_left_move_nowhere_1,
698 vec![
699 new_lig(Some(0), 'B', 'Z', RetainLeftMoveNowhere),
700 new_kern(None, 'Z', FixWord::ONE),
701 ],
702 vec![('A', 0)],
703 vec![
704 (
705 'A',
706 'B',
707 vec![
708 IntermediateOp::C(C::char(Char::A)),
709 IntermediateOp::Kern(common::Scaled::ONE),
710 ],
711 C {
712 c: Char::Z,
713 is_lig: true,
714 }
715 ),
716 (
717 'A',
718 'Z',
719 vec![
720 IntermediateOp::C(C::char(Char::A)),
721 IntermediateOp::Kern(common::Scaled::ONE),
722 ],
723 C::char(Char::Z),
724 ),
725 ],
726 ),
727 (
728 retain_left_move_nowhere_2,
729 vec![new_lig(None, 'B', 'Z', RetainLeftMoveNowhere),],
730 vec![('A', 0)],
731 vec![(
732 'A',
733 'B',
734 vec![IntermediateOp::C(C::char(Char::A)),],
735 C {
736 c: Char::Z,
737 is_lig: true,
738 }
739 ),],
740 ),
741 (
742 single_lig_4,
743 vec![
744 new_lig(None, 'B', 'Z', RetainRightMoveToInserted),
745 new_kern(None, 'B', FixWord::ONE),
746 ],
747 vec![('A', 0), ('Z', 1)],
748 vec![
749 (
750 'A',
751 'B',
752 vec![
753 IntermediateOp::C(C {
754 c: Char::Z,
755 is_lig: true,
756 }),
757 IntermediateOp::Kern(common::Scaled::ONE),
758 ],
759 C::char(Char::B),
760 ),
761 (
762 'Z',
763 'B',
764 vec![
765 IntermediateOp::C(C::char(Char::Z)),
766 IntermediateOp::Kern(common::Scaled::ONE),
767 ],
768 C::char(Char::B),
769 ),
770 ],
771 ),
772 (
773 single_lig_5,
774 vec![
775 new_lig(None, 'B', 'Z', RetainRightMoveToRight),
776 new_kern(None, 'B', FixWord::ONE),
777 ],
778 vec![('A', 0), ('Z', 1)],
779 vec![
780 (
781 'A',
782 'B',
783 vec![IntermediateOp::C(C {
784 c: Char::Z,
785 is_lig: true,
786 })],
787 C::char(Char::B),
788 ),
789 (
790 'Z',
791 'B',
792 vec![
793 IntermediateOp::C(C::char(Char::Z)),
794 IntermediateOp::Kern(common::Scaled::ONE),
795 ],
796 C::char(Char::B),
797 ),
798 ],
799 ),
800 (
801 retain_both_move_nowhere_1,
802 vec![
803 new_lig(Some(0), 'B', 'Z', RetainBothMoveNowhere,),
804 new_kern(None, 'Z', FixWord::ONE * 2),
805 new_kern(None, 'B', FixWord::ONE * 3),
806 ],
807 vec![('A', 0), ('Z', 2)],
808 vec![
809 (
810 'A',
811 'B',
812 vec![
813 IntermediateOp::C(C::char(Char::A)),
814 IntermediateOp::Kern(common::Scaled::ONE * 2),
815 IntermediateOp::C(C {
816 c: Char::Z,
817 is_lig: true,
818 }),
819 IntermediateOp::Kern(common::Scaled::ONE * 3),
820 ],
821 C::char(Char::B),
822 ),
823 (
824 'A',
825 'Z',
826 vec![
827 IntermediateOp::C(C::char(Char::A)),
828 IntermediateOp::Kern(common::Scaled::ONE * 2),
829 ],
830 C::char(Char::Z),
831 ),
832 (
833 'Z',
834 'B',
835 vec![
836 IntermediateOp::C(C::char(Char::Z)),
837 IntermediateOp::Kern(common::Scaled::ONE * 3),
838 ],
839 C::char(Char::B),
840 ),
841 ],
842 ),
843 (
844 retain_both_move_nowhere_2,
845 vec![new_lig(None, 'B', 'Z', RetainBothMoveNowhere),],
846 vec![('A', 0)],
847 vec![(
848 'A',
849 'B',
850 vec![
851 IntermediateOp::C(C::char(Char::A)),
852 IntermediateOp::C(C {
853 c: Char::Z,
854 is_lig: true,
855 }),
856 ],
857 C::char(Char::B),
858 ),],
859 ),
860 (
861 retain_both_move_nowhere_3,
862 vec![
863 new_lig(None, 'B', 'Z', RetainBothMoveNowhere),
864 new_lig(None, 'B', 'Y', RetainRightMoveToRight),
865 ],
866 vec![('A', 0), ('Z', 1)],
867 vec![
868 (
869 'A',
870 'B',
871 vec![
872 IntermediateOp::C(C::char(Char::A)),
873 IntermediateOp::C(C {
874 c: Char::Y,
875 is_lig: true,
876 }),
877 ],
878 C::char(Char::B),
879 ),
880 (
881 'Z',
882 'B',
883 vec![IntermediateOp::C(C {
884 c: Char::Y,
885 is_lig: true,
886 }),],
887 C::char(Char::B),
888 ),
889 ],
890 ),
891 (
892 retain_both_move_nowhere_4,
893 vec![
894 new_lig(Some(0), 'B', 'Z', RetainBothMoveNowhere),
895 new_lig(None, 'Z', 'Y', RetainBothMoveToRight),
896 ],
897 vec![('A', 0)],
898 vec![
899 (
900 'A',
901 'B',
902 vec![
903 IntermediateOp::C(C::char(Char::A)),
904 IntermediateOp::C(C {
905 c: Char::Y,
906 is_lig: true,
907 }),
908 IntermediateOp::C(C {
909 c: Char::Z,
910 is_lig: true,
911 }),
912 ],
913 C::char(Char::B),
914 ),
915 (
916 'A',
917 'Z',
918 vec![
919 IntermediateOp::C(C::char(Char::A)),
920 IntermediateOp::C(C {
921 c: Char::Y,
922 is_lig: true,
923 }),
924 ],
925 C {
926 c: Char::Z,
927 is_lig: false,
928 }
929 ),
930 ],
931 ),
932 (
933 retain_both_move_to_inserted_1,
934 vec![
935 new_lig(Some(0), 'B', 'Z', RetainBothMoveToInserted,),
936 new_kern(None, 'Z', FixWord::ONE * 2),
937 new_kern(None, 'B', FixWord::ONE * 3),
938 ],
939 vec![('A', 0), ('Z', 2)],
940 vec![
941 (
942 'A',
943 'B',
944 vec![
945 IntermediateOp::C(C::char(Char::A)),
946 IntermediateOp::C(C {
947 c: Char::Z,
948 is_lig: true,
949 }),
950 IntermediateOp::Kern(common::Scaled::ONE * 3),
951 ],
952 C::char(Char::B),
953 ),
954 (
955 'A',
956 'Z',
957 vec![
958 IntermediateOp::C(C::char(Char::A)),
959 IntermediateOp::Kern(common::Scaled::ONE * 2),
960 ],
961 C::char(Char::Z),
962 ),
963 (
964 'Z',
965 'B',
966 vec![
967 IntermediateOp::C(C::char(Char::Z)),
968 IntermediateOp::Kern(common::Scaled::ONE * 3),
969 ],
970 C::char(Char::B),
971 ),
972 ],
973 ),
974 (
975 retain_both_move_to_inserted_2,
976 vec![
977 new_lig(None, 'B', 'Z', RetainBothMoveToInserted),
978 new_lig(None, 'B', 'Y', RetainRightMoveToInserted),
979 ],
980 vec![('A', 0), ('Z', 1)],
981 vec![
982 (
983 'A',
984 'B',
985 vec![
986 IntermediateOp::C(C::char(Char::A)),
987 IntermediateOp::C(C {
988 c: Char::Y,
989 is_lig: true,
990 }),
991 ],
992 C::char(Char::B),
993 ),
994 (
995 'Z',
996 'B',
997 vec![IntermediateOp::C(C {
998 c: Char::Y,
999 is_lig: true,
1000 }),],
1001 C::char(Char::B),
1002 ),
1003 ],
1004 ),
1005 (
1006 retain_both_move_to_inserted_3,
1007 vec![new_lig(None, 'B', 'Z', RetainBothMoveToInserted),],
1008 vec![('A', 0)],
1009 vec![(
1010 'A',
1011 'B',
1012 vec![
1013 IntermediateOp::C(C::char(Char::A)),
1014 IntermediateOp::C(C {
1015 c: Char::Z,
1016 is_lig: true,
1017 }),
1018 ],
1019 C::char(Char::B),
1020 ),],
1021 ),
1022 (
1023 retain_both_move_to_inserted_4,
1024 vec![
1025 new_lig(None, 'B', 'Z', RetainBothMoveToInserted,),
1026 new_lig(None, 'B', 'Y', RetainRightMoveToRight),
1027 ],
1028 vec![('A', 0), ('Z', 1)],
1029 vec![
1030 (
1031 'A',
1032 'B',
1033 vec![
1034 IntermediateOp::C(C::char(Char::A)),
1035 IntermediateOp::C(C {
1036 c: Char::Y,
1037 is_lig: true,
1038 }),
1039 ],
1040 C::char(Char::B),
1041 ),
1042 (
1043 'Z',
1044 'B',
1045 vec![IntermediateOp::C(C {
1046 c: Char::Y,
1047 is_lig: true,
1048 }),],
1049 C::char(Char::B),
1050 ),
1051 ],
1052 ),
1053 (
1054 retain_both_move_to_right_1,
1055 vec![
1056 new_lig(Some(0), 'B', 'Z', RetainBothMoveToRight,),
1057 new_kern(None, 'Z', FixWord::ONE * 2),
1058 new_kern(None, 'B', FixWord::ONE * 3),
1059 ],
1060 vec![('A', 0), ('Z', 2)],
1061 vec![
1062 (
1063 'A',
1064 'B',
1065 vec![
1066 IntermediateOp::C(C::char(Char::A)),
1067 IntermediateOp::C(C {
1068 c: Char::Z,
1069 is_lig: true,
1070 }),
1071 ],
1072 C::char(Char::B),
1073 ),
1074 (
1075 'A',
1076 'Z',
1077 vec![
1078 IntermediateOp::C(C::char(Char::A)),
1079 IntermediateOp::Kern(common::Scaled::ONE * 2),
1080 ],
1081 C::char(Char::Z),
1082 ),
1083 (
1084 'Z',
1085 'B',
1086 vec![
1087 IntermediateOp::C(C::char(Char::Z)),
1088 IntermediateOp::Kern(common::Scaled::ONE * 3),
1089 ],
1090 C::char(Char::B),
1091 ),
1092 ],
1093 ),
1094 (
1095 is_lig_propagated_1,
1096 vec![
1097 new_lig(None, 'B', 'Y', RetainBothMoveToInserted,),
1098 new_lig(None, 'B', 'Z', RetainLeftMoveToInserted,),
1099 ],
1100 vec![('A', 0), ('Y', 1)],
1101 vec![
1102 (
1103 'A',
1104 'B',
1105 vec![
1106 IntermediateOp::C(C::char(Char::A)),
1107 IntermediateOp::C(C {
1108 c: Char::Y,
1109 is_lig: true,
1110 }),
1111 ],
1112 C {
1113 c: Char::Z,
1114 is_lig: true,
1115 },
1116 ),
1117 (
1118 'Y',
1119 'B',
1120 vec![IntermediateOp::C(C::char(Char::Y)),],
1121 C {
1122 c: Char::Z,
1123 is_lig: true,
1124 }
1125 ),
1126 ],
1127 ),
1128 (
1129 is_lig_propagated_2,
1130 vec![
1131 new_lig(Some(0), 'B', 'Y', RetainLeftMoveNowhere,),
1132 new_lig(None, 'Y', 'Z', RetainRightMoveToRight,),
1133 ],
1134 vec![('A', 0)],
1135 vec![
1136 (
1137 'A',
1138 'B',
1139 vec![IntermediateOp::C(C {
1140 c: Char::Z,
1141 is_lig: true,
1142 }),],
1143 C {
1144 c: Char::Y,
1145 is_lig: true,
1146 },
1147 ),
1148 (
1149 'A',
1150 'Y',
1151 vec![IntermediateOp::C(C {
1152 c: Char::Z,
1153 is_lig: true,
1154 }),],
1155 C::char(Char::Y)
1156 ),
1157 ],
1158 ),
1159 );
1160}