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