tfm/ligkern/
compiler.rs

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 this calculation is for.
29    // TODO: rename root?
30    node: Node,
31
32    finalized: Vec<IntermediateOp>,
33    // Characters that are still pending replacement. The next step is to apply the ligature
34    // rule for the node. After that, if the second element is not empty, the next step
35    // is to apply the ligature rule for (pending.1, pending.2).
36    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                    // Invalid next instruction
133                    break;
134                }
135                Some(instruction) => instruction,
136            };
137            next_instruction = instruction
138                .next_instruction
139                .map(|increment| next + 1 + (increment as usize));
140
141            // We only insert the element if it doesn't already exist.
142            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                // This reimplements the phantom ligature bug in tftopl.
177                // TODO: in tfmtools don't reimplement these bugs.
178                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                    // finalized
227                    vec![],
228                    // pending
229                    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                    // finalized
240                    filter_left_boundary![C::left_char(left),],
241                    // pending
242                    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                    // finalized
253                    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                    // pending
262                    None,
263                ),
264                lang::PostLigOperation::RetainRightMoveToInserted => (
265                    // finalized
266                    vec![],
267                    // pending
268                    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                    // finalized
279                    vec![
280                        C {
281                            c: char_to_insert,
282                            is_lig: true,
283                        },
284                        C::char(right),
285                    ],
286                    // pending
287                    None,
288                ),
289                lang::PostLigOperation::RetainLeftMoveNowhere => (
290                    // finalized
291                    vec![],
292                    // pending
293                    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                    // finalized
304                    filter_left_boundary![
305                        C::left_char(left),
306                        C {
307                            c: char_to_insert,
308                            is_lig: true,
309                        },
310                    ],
311                    // pending
312                    None,
313                ),
314                lang::PostLigOperation::RetainNeitherMoveToInserted => (
315                    // finalized
316                    vec![C {
317                        c: char_to_insert,
318                        is_lig: true,
319                    }],
320                    // pending
321                    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                // There is no lig/kern rule for this pair.
350                match calc.pending.0.clone() {
351                    None => {
352                        // We are dropping the left boundary on the floor.
353                    }
354                    Some(left) => {
355                        calc.finalized.push(IntermediateOp::C(left));
356                    }
357                }
358                calc.pending.1.clone()
359            }
360            Some(replacement) => {
361                // In the replacement we need to update the is_lig fields in case
362                // either the left or right character is a lig.
363                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    // Next we check for infinite loops. If there is one, the node_to_parents
401    // map will be non-empty and contain all the nodes that couldn't be calculated.
402    //
403    // The main complication here is that there are multiple nodes we can report
404    // as being the cause of the infinite loop. E.g., the loop could be
405    // (A,R) -> (B,R) -> (C,R) -> (A,R), and we could report any of these 3 nodes.
406    // What we want to do, though, is report the same node that Knuth does in tftopl
407    // and pltotf. Thus the algorithm here replicates what Knuth does.
408    //
409    // Knuth iterates over all nodes in the following order: in lexicographical order
410    // for the left pair (with the boundary char last), and in the instruction order
411    // for the right pair. I.e, if we have pairs (A,R) and (B,R) the pair whose
412    // instruction comes first in the lig/kern program will come first.
413    //
414    // Then, given such a node, Knuth performs a path traversal following each
415    // node's dependencies. The first node that is seen twice in the traversal is the
416    // node that is considered to break the infinite loop. Knuth then essentially breaks
417    // the loop and moves on. Note that Knuth's algorithm correctly handles cases
418    // like (A,R) -> (B,R) -> (C,R) -> (B,R) - in such a case, the first node we see (A,R)
419    // isn't actually what causes the loop.
420    //
421    // The process described in the last paragraph can happen multiple times if there
422    // are multiple infinite loops. Knuth reports the node from the last infinite loop.
423    // Note that we have E2E tests that cover these kinds of cases.
424    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        // As mentioned above, when Knuth finds an infinite loop he breaks it.
444        // It's possible that the node that started this iteration is part of an infinite
445        // loop that has already been broken. For example the lig/kern program could be:
446        // - (A,R) -> (C,R) -> (C,R)
447        // - (B,R) -> (C,R) -> (C,R)
448        // In this case when considering (B,R) there is nothing to do because the (C,R)
449        // loop has already been broken.
450        //
451        // We detect this case by keeping track of which nodes we've seen for the first
452        // time in this traversal.
453        // If we haven't seen it, the loop has already been broken. There is an E2E test
454        // for this case.
455        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}