1mod compiler;
81use crate::ligkern::compiler::Replacement;
82use crate::Char;
83use crate::FixWord;
84use std::collections::HashMap;
85use std::rc::Rc;
86pub mod lang;
87
88#[derive(Clone, Debug, Default)]
92pub struct CompiledProgram {
93 right_boundary_char: Option<Char>,
94 replacements: HashMap<(Option<Char>, Char), compiler::Replacement>,
95}
96
97#[derive(Clone, Debug, PartialEq, Eq)]
98enum IntermediateOp {
99 Kern(common::Scaled),
101 C(compiler::C),
103}
104
105impl CompiledProgram {
106 pub fn compile(
108 program: &lang::Program,
109 design_size: FixWord,
110 kerns: &[FixWord],
111 entrypoints: HashMap<Char, u16>,
112 ) -> (CompiledProgram, Vec<InfiniteLoopError>) {
113 compiler::compile(program, design_size, kerns, &entrypoints)
114 }
115
116 pub fn compile_from_pl_file(
118 pl_file: &super::pl::File,
119 ) -> (CompiledProgram, Vec<InfiniteLoopError>) {
120 let entrypoints = pl_file.lig_kern_entrypoints(false);
121 CompiledProgram::compile(
124 &pl_file.lig_kern_program,
125 pl_file.header.design_size,
126 &[],
127 entrypoints,
128 )
129 }
130
131 pub fn compile_from_tfm_file(
133 tfm_file: &mut super::File,
134 ) -> (CompiledProgram, Vec<InfiniteLoopError>) {
135 let entrypoints: HashMap<Char, u16> = tfm_file
136 .lig_kern_entrypoints()
137 .into_iter()
138 .filter_map(|(c, e)| {
139 tfm_file
140 .lig_kern_program
141 .unpack_entrypoint(e)
142 .ok()
143 .map(|e| (c, e))
144 })
145 .collect();
146 CompiledProgram::compile(
147 &tfm_file.lig_kern_program,
148 tfm_file.header.design_size,
149 &tfm_file.kerns,
150 entrypoints,
151 )
152 }
153
154 pub fn has_replacement(&self, left_char: Option<char>, right_char: Option<char>) -> bool {
155 self.get_replacement_utf8(left_char, right_char).is_some()
156 }
157
158 fn get_replacement(
159 &self,
160 left_char: Option<Char>,
161 right_char: Option<Char>,
162 ) -> Option<&Replacement> {
163 let right_char = match right_char {
164 None => self.right_boundary_char?,
165 Some(c) => c,
166 };
167 self.replacements.get(&(left_char, right_char))
168 }
169
170 fn get_replacement_utf8(
171 &self,
172 left_char: Option<char>,
173 right_char: Option<char>,
174 ) -> Option<&Replacement> {
175 let left_char = match left_char {
176 None => None,
177 Some(c) => {
178 let Ok(c) = c.try_into() else {
179 return None;
180 };
181 Some(c)
182 }
183 };
184 let right_char = match right_char {
185 None => None,
186 Some(c) => {
187 let Ok(c) = c.try_into() else {
188 return None;
189 };
190 Some(c)
191 }
192 };
193 self.get_replacement(left_char, right_char)
194 }
195
196 pub fn all_pairs_with_replacements(&self) -> Vec<(Option<Char>, Char)> {
199 let mut v: Vec<(Option<Char>, Char)> = self.replacements.keys().copied().collect();
200 v.sort();
201 v
202 }
203
204 pub fn is_seven_bit_safe(&self) -> bool {
213 self.all_pairs_with_replacements()
214 .into_iter()
215 .filter(|(l, r)| l.map(|c| c.is_seven_bit()).unwrap_or(true) && r.is_seven_bit())
216 .flat_map(|(l, r)| self.get_replacement(l, Some(r)))
217 .all(|rep| {
218 rep.0.iter().all(|op| match op {
219 IntermediateOp::Kern(_) => true,
220 IntermediateOp::C(c) => c.c.is_seven_bit(),
221 }) && rep.1.c.is_seven_bit()
222 })
223 }
224
225 pub fn run<'a>(&'a self, word: &'a str) -> RunIter<'a, std::str::Chars<'a>> {
227 self.run_with_options(word.chars(), Default::default())
228 }
229
230 pub fn run_with_options<'a, Word>(
235 &'a self,
236 mut word: Word,
237 options: RunOptions,
238 ) -> RunIter<'a, Word>
239 where
240 Word: Iterator<Item = char>,
241 {
242 let next_left = if options.disable_left_boundary {
243 match word.next() {
244 Some(c) => NextLeft::Char(c),
245 None => NextLeft::None,
246 }
247 } else {
248 NextLeft::Boundary
249 };
250 RunIter {
251 program: self,
252 word,
253 consumes_left: true,
254 intermediate_ops: &[],
255 next_left,
256 ligature: None,
257 left: None,
258 right_boundary_override: options.right_boundary_override,
259 }
260 }
261}
262
263#[derive(Default, Debug)]
265pub struct RunOptions {
266 pub disable_left_boundary: bool,
267 pub right_boundary_override: Option<char>,
268}
269
270#[derive(PartialEq, Debug)]
272pub enum RunItem {
273 Char(char),
274 Kern(common::Scaled),
275 Ligature(Ligature),
276}
277
278#[derive(Default)]
279struct PendingLigature {
280 s: String,
281 includes_left_boundary: bool,
282 includes_right_boundary: bool,
283}
284
285impl PendingLigature {
286 fn into_ligature(self, c: char) -> Ligature {
287 Ligature {
288 c,
289 original: self.s.into(),
290 includes_left_boundary: self.includes_left_boundary,
291 includes_right_boundary: self.includes_right_boundary,
292 }
293 }
294}
295
296pub struct RunIter<'a, Word> {
298 program: &'a CompiledProgram,
300 right_boundary_override: Option<char>,
301
302 word: Word,
304
305 ligature: Option<PendingLigature>,
310
311 intermediate_ops: &'a [IntermediateOp],
319 consumes_left: bool,
320 left: Option<char>,
321
322 next_left: NextLeft,
325}
326
327#[derive(Debug)]
328enum NextLeft {
329 Boundary,
330 Char(char),
331 Lig(char, char),
332 FinalLig(char),
333 None,
334}
335
336impl<'a, Word> RunIter<'a, Word> {
337 pub fn is_separation_point(&self) -> bool {
338 self.intermediate_ops.is_empty()
339 && !matches!(self.next_left, NextLeft::Lig(_, _) | NextLeft::FinalLig(_))
340 }
341}
342
343impl<'a, Word> Iterator for RunIter<'a, Word>
344where
345 Word: Iterator<Item = char>,
346{
347 type Item = RunItem;
348
349 fn next(&mut self) -> Option<Self::Item> {
350 if let Some((op, tail)) = self.intermediate_ops.split_first() {
351 self.intermediate_ops = tail;
352 return Some(match op {
353 IntermediateOp::Kern(kern) => RunItem::Kern(*kern),
354 IntermediateOp::C(compiler::C { c, is_lig: false }) => {
355 self.consumes_left = false;
356 match self.ligature.take() {
357 Some(l) => {
358 RunItem::Ligature(l.into_ligature((*c).into()))
360 }
361 None => RunItem::Char((*c).into()),
362 }
363 }
364 IntermediateOp::C(compiler::C { c, is_lig: true }) => {
365 let mut s = self.ligature.take().unwrap_or_default();
366 if self.consumes_left {
367 if let Some(left) = self.left {
368 s.s.push(left);
369 } else {
370 s.includes_left_boundary = true;
371 }
372 }
373 self.consumes_left = false;
374 s.includes_right_boundary = matches!(self.next_left, NextLeft::None)
375 && matches!(
376 (tail.first(), tail.get(1)),
377 (None, None) | (Some(IntermediateOp::Kern(_)), None)
378 );
379 RunItem::Ligature(s.into_ligature((*c).into()))
380 }
381 });
382 };
383 let (left, left_in_original): (Option<char>, bool) = match &self.next_left {
384 NextLeft::Boundary => (None, true),
385 NextLeft::Char(c) => (Some(*c), true),
386 NextLeft::Lig(c, right) => {
387 let s = self.ligature.get_or_insert_default();
388 if self.consumes_left {
389 if let Some(left) = self.left {
390 s.s.push(left);
391 } else {
392 s.includes_left_boundary = true;
393 }
394 }
395 s.s.push(*right);
396 (Some(*c), false)
397 }
398 NextLeft::FinalLig(c) => {
399 let mut s = self.ligature.take().unwrap_or_default();
400 if self.consumes_left {
401 if let Some(left) = self.left {
402 s.s.push(left);
403 } else {
404 s.includes_left_boundary = true;
405 }
406 }
407 s.includes_right_boundary = true;
408 let lig = RunItem::Ligature(s.into_ligature(*c));
409 self.next_left = NextLeft::None;
410 return Some(lig);
411 }
412 NextLeft::None => return None,
413 };
414 let right = self.word.next();
415 if left.is_none() && right.is_none() {
416 return None;
418 }
419 let right_for_lookup = match right {
420 Some(r) => Some(r),
421 None => self.right_boundary_override,
422 };
423 match self.program.get_replacement_utf8(left, right_for_lookup) {
426 Some(replacement) => {
427 self.left = left;
428 self.consumes_left = left_in_original;
429 self.intermediate_ops = &replacement.0;
430 self.next_left = match (replacement.1.is_lig, right) {
431 (false, None) => NextLeft::None,
432 (false, Some(_)) => NextLeft::Char(replacement.1.c.into()),
433 (true, Some(right)) => NextLeft::Lig(replacement.1.c.into(), right),
434 (true, None) => NextLeft::FinalLig(replacement.1.c.into()),
435 };
436 }
437 None => {
438 self.next_left = match right {
439 None => NextLeft::None,
440 Some(right) => NextLeft::Char(right),
441 };
442 if let Some(left) = left {
443 return Some(match self.ligature.take() {
444 Some(l) => RunItem::Ligature(l.into_ligature(left)),
445 None => RunItem::Char(left),
446 });
447 }
448 }
449 }
450 self.next()
451 }
452}
453
454#[derive(PartialEq, Debug)]
455pub struct Ligature {
456 pub c: char,
457 pub original: Rc<str>,
458 pub includes_left_boundary: bool,
459 pub includes_right_boundary: bool,
460}
461
462#[derive(Clone, Debug, PartialEq, Eq)]
466pub struct InfiniteLoopError {
467 pub starting_pair: (Option<Char>, Char),
469}
470
471impl InfiniteLoopError {
472 pub fn pltotf_message(&self) -> String {
473 let left = match self.starting_pair.0 {
474 Some(c) => format!["'{:03o}", c.0],
475 None => "boundary".to_string(),
476 };
477 format!(
478 "Infinite ligature loop starting with {} and '{:03o}!",
479 left, self.starting_pair.1 .0
480 )
481 }
482 pub fn pltotf_section(&self) -> u8 {
483 125
484 }
485}
486
487#[derive(Clone, Debug, PartialEq, Eq)]
491pub struct InfiniteLoopStep {
492 pub instruction_index: usize,
494 pub post_replacement: (bool, Vec<Char>),
499 pub post_cursor_position: usize,
501}
502
503#[cfg(test)]
504mod tests {
505 use common::Scaled;
506
507 use super::Ligature as L;
508 use super::*;
509 use pretty_assertions::assert_eq;
510
511 const LIGAROO: &'static str = include_str!["ligaroo.plst"];
512
513 fn run_test(program: &str, input: &str, want: Vec<RunItem>) {
514 let source = LIGAROO.replace("(LIGTABLE", &format!["(LIGTABLE\n{program}"]);
515 let pl_file = crate::pl::File::from_pl_source_code(&source).0;
516
517 if std::env::var("TEXCRAFT_VERIFY").unwrap_or_default() == "tex" {
518 verify_against_tex(pl_file, input, want);
519 return;
520 }
521
522 let program = CompiledProgram::compile_from_pl_file(&pl_file).0;
523 let got: Vec<RunItem> = program.run(input).collect();
524 assert_eq!(got, want);
525 }
526
527 fn verify_against_tex(pl_file: crate::pl::File, input: &str, want: Vec<RunItem>) {
534 let tfm_file: crate::File = pl_file.into();
535 let stdout = run_tex(&tfm_file.serialize(), input);
536 let got = parse_showbox_output(&stdout);
537 let want: Vec<RunItem> = want.into_iter().map(normalize_for_tex).collect();
538 assert_eq!(got, want);
539 }
540
541 fn run_tex(tfm_bytes: &[u8], input: &str) -> String {
545 let mut dir = std::env::temp_dir();
546 dir.push("texcraft_tfm_ligkern");
547 dir.push(
550 std::thread::current()
551 .name()
552 .unwrap_or("texcraft_unknown_thread_name")
553 .replace("::", "__"),
554 );
555 std::fs::create_dir_all(&dir).unwrap();
556 std::fs::write(dir.join("ligaroo.tfm"), tfm_bytes).unwrap();
557
558 let tex_source = format!(
559 r"\nonstopmode
560\tracingonline=1
561\showboxbreadth=1000000
562\showboxdepth=100
563\font\ligkernfont=ligaroo
564\ligkernfont
565\setbox253=\hbox{{{input}}}
566\showbox253
567\end
568"
569 );
570 let input_path = dir.join("tex-input.tex");
571 std::fs::write(&input_path, tex_source).unwrap();
572
573 let output = std::process::Command::new("tex")
574 .current_dir(&dir)
575 .env("max_print_line", "10000")
578 .arg(&input_path)
579 .output()
580 .expect("failed to run the `tex` binary");
581 String::from_utf8(output.stdout).expect("stdout of TeX is utf-8")
584 }
585
586 fn parse_showbox_output(stdout: &str) -> Vec<RunItem> {
588 let mut lines = stdout.lines();
589 for line in lines.by_ref() {
590 if line.starts_with(r"> \box253=") {
591 break;
592 }
593 }
594 let dimens_line = lines.next().expect(r"box dump contains an \hbox line");
595 assert!(
596 dimens_line.starts_with(r"\hbox("),
597 r"expected an \hbox dimensions line, got {dimens_line:?}"
598 );
599 lines
602 .map_while(|line| line.strip_prefix('.'))
603 .map(parse_run_item)
604 .collect()
605 }
606
607 fn parse_run_item(line: &str) -> RunItem {
609 if let Some(width) = line.strip_prefix(r"\kern") {
611 let width = common::Scaled::parse_no_units(width.trim())
612 .expect("kern width fits in a scaled number");
613 return RunItem::Kern(width);
614 }
615 let tail = line
618 .strip_prefix(r"\ligkernfont ")
619 .expect(r"box content line is a \kern or a character in the test font");
620 let parse_char = |s: &str| {
621 let mut chars = s.chars();
622 let c = chars.next().expect("a character follows the font name");
623 assert_eq!(chars.next(), None, "expected a single character in {s:?}");
624 c
625 };
626 match tail.split_once(" (ligature ") {
627 None => RunItem::Char(parse_char(tail)),
628 Some((c, original)) => {
629 let original = original
630 .strip_suffix(')')
631 .expect("ligature original chars end with `)`");
632 RunItem::Ligature(Ligature {
633 c: parse_char(c),
634 original: original.into(),
635 includes_left_boundary: false,
636 includes_right_boundary: false,
637 })
638 }
639 }
640 }
641
642 fn normalize_for_tex(item: RunItem) -> RunItem {
650 match item {
651 RunItem::Ligature(ligature) => {
652 let mut original = String::new();
653 if ligature.includes_left_boundary {
654 original.push('|');
655 }
656 original.push_str(&ligature.original);
657 if ligature.includes_right_boundary {
658 original.push('|');
659 }
660 RunItem::Ligature(Ligature {
661 c: ligature.c,
662 original: original.into(),
663 includes_left_boundary: false,
664 includes_right_boundary: false,
665 })
666 }
667 item => item,
668 }
669 }
670
671 macro_rules! tests {
672 ( $(
673 ($name: ident, $program: expr, $input: expr, $want: expr, ),
674 )+ ) => { $(
675 #[test]
676 fn $name() {
677 #[allow(unused_imports)]
678 use RunItem::*;
679 let program = $program;
680 let input = $input;
681 let want = $want;
682 run_test(program, input, want);
683 }
684 )+ };
685 }
686
687 tests!(
688 (empty_input, "", "", vec![],),
689 (
691 single_lig_1,
692 "
693 (LABEL C A)
694 (LIG C B C 1)
695 (KRN C 1 R 0.1)
696 (STOP)
697
698 (LABEL C 1)
699 (KRN C B R 0.3)
700 (STOP)
701 ",
702 "AB",
703 vec![Ligature(L {
704 c: '1',
705 original: "AB".into(),
706 includes_left_boundary: false,
707 includes_right_boundary: false,
708 })],
709 ),
710 (
712 single_lig_2,
713 "
714 (LABEL C A)
715 (/LIG C B C 1)
716 (KRN C 1 R 0.1)
717 (STOP)
718
719 (LABEL C 1)
720 (KRN C B R 0.3)
721 (STOP)
722 ",
723 "AB",
724 vec![
725 Char('A'),
726 Kern(Scaled::ONE),
727 Ligature(L {
728 c: '1',
729 original: "B".into(),
730 includes_left_boundary: false,
731 includes_right_boundary: false,
732 })
733 ],
734 ),
735 (
737 single_lig_3,
738 "
739 (LABEL C A)
740 (/LIG> C B C 1)
741 (KRN C 1 R 0.1)
742 (STOP)
743
744 (LABEL C 1)
745 (KRN C B R 0.3)
746 (STOP)
747 ",
748 "AB",
749 vec![
750 Char('A'),
751 Ligature(L {
752 c: '1',
753 original: "B".into(),
754 includes_left_boundary: false,
755 includes_right_boundary: false,
756 }),
757 ],
758 ),
759 (
761 single_lig_4,
762 "
763 (LABEL C A)
764 (LIG/ C B C 1)
765 (KRN C 1 R 0.1)
766 (STOP)
767
768 (LABEL C 1)
769 (KRN C B R 0.3)
770 (STOP)
771 ",
772 "AB",
773 vec![
774 Ligature(L {
775 c: '1',
776 original: "A".into(),
777 includes_left_boundary: false,
778 includes_right_boundary: false,
779 }),
780 Kern(Scaled::ONE * 3),
781 Char('B'),
782 ],
783 ),
784 (
786 single_lig_5,
787 "
788 (LABEL C A)
789 (LIG/> C B C 1)
790 (KRN C 1 R 0.1)
791 (STOP)
792
793 (LABEL C 1)
794 (KRN C B R 0.3)
795 (STOP)
796 ",
797 "AB",
798 vec![
799 Ligature(L {
800 c: '1',
801 original: "A".into(),
802 includes_left_boundary: false,
803 includes_right_boundary: false,
804 }),
805 Char('B'),
806 ],
807 ),
808 (
810 single_lig_6,
811 "
812 (LABEL C A)
813 (/LIG/ C B C 1)
814 (KRN C 1 R 0.1)
815 (STOP)
816
817 (LABEL C 1)
818 (KRN C B R 0.3)
819 (STOP)
820 ",
821 "AB",
822 vec![
823 Char('A'),
824 Kern(Scaled::ONE),
825 Ligature(L {
826 c: '1',
827 original: "".into(),
828 includes_left_boundary: false,
829 includes_right_boundary: false,
830 }),
831 Kern(Scaled::ONE * 3),
832 Char('B'),
833 ],
834 ),
835 (
837 single_lig_7,
838 "
839 (LABEL C A)
840 (/LIG/> C B C 1)
841 (KRN C 1 R 0.1)
842 (STOP)
843
844 (LABEL C 1)
845 (KRN C B R 0.3)
846 (STOP)
847 ",
848 "AB",
849 vec![
850 Char('A'),
851 Ligature(L {
852 c: '1',
853 original: "".into(),
854 includes_left_boundary: false,
855 includes_right_boundary: false,
856 }),
857 Kern(Scaled::ONE * 3),
858 Char('B'),
859 ],
860 ),
861 (
863 single_lig_8,
864 "
865 (LABEL C A)
866 (/LIG/>> C B C 1)
867 (KRN C 1 R 0.1)
868 (STOP)
869
870 (LABEL C 1)
871 (KRN C B R 0.3)
872 (STOP)
873 ",
874 "AB",
875 vec![
876 Char('A'),
877 Ligature(L {
878 c: '1',
879 original: "".into(),
880 includes_left_boundary: false,
881 includes_right_boundary: false,
882 }),
883 Char('B'),
884 ],
885 ),
886 (
891 no_op_lig,
892 "
893 (LABEL C A)
894 (LIG/> C B C A)
895 (STOP)
896 ",
897 "AB",
898 vec![
899 Ligature(L {
900 c: 'A',
901 original: "A".into(),
902 includes_left_boundary: false,
903 includes_right_boundary: false,
904 }),
905 Char('B'),
906 ],
907 ),
908 (
910 multiple_lig_1,
911 "
912 (LABEL C A)
913 (LIG C B C 1)
914
915 (LABEL C 1)
916 (LIG C C C 2)
917 (STOP)
918 ",
919 "ABC",
920 vec![Ligature(L {
921 c: '2',
922 original: "ABC".into(),
923 includes_left_boundary: false,
924 includes_right_boundary: false,
925 }),],
926 ),
927 (
929 multiple_lig_2,
930 "
931 (LABEL C A)
932 (/LIG/ C B C 1)
933 (LABEL C 1)
934 (LIG C B C 2)
935 (STOP)
936 ",
937 "AB",
938 vec![
939 Char('A'),
940 Ligature(L {
941 c: '2',
942 original: "B".into(),
943 includes_left_boundary: false,
944 includes_right_boundary: false,
945 }),
946 ],
947 ),
948 (
950 multiple_lig_3,
951 "
952 (LABEL C A)
953 (LIG/ C A C 1)
954 (STOP)
955 ",
956 "AAAAA",
957 vec![
958 Ligature(L {
959 c: '1',
960 original: "A".into(),
961 includes_left_boundary: false,
962 includes_right_boundary: false,
963 }),
964 Ligature(L {
965 c: '1',
966 original: "A".into(),
967 includes_left_boundary: false,
968 includes_right_boundary: false,
969 }),
970 Ligature(L {
971 c: '1',
972 original: "A".into(),
973 includes_left_boundary: false,
974 includes_right_boundary: false,
975 }),
976 Ligature(L {
977 c: '1',
978 original: "A".into(),
979 includes_left_boundary: false,
980 includes_right_boundary: false,
981 }),
982 Char('A'),
983 ],
984 ),
985 (
987 multiple_lig_4,
988 "
989 (LABEL C A)
990 (LIG C A C A)
991 (STOP)
992 ",
993 "AAAAAA",
994 vec![Ligature(L {
995 c: 'A',
996 original: "AAAAAA".into(),
997 includes_left_boundary: false,
998 includes_right_boundary: false,
999 }),],
1000 ),
1001 (
1003 multiple_lig_5,
1004 "
1005 (LABEL C A)
1006 (/LIG C B C 1)
1007 (LIG/ C 1 C 2)
1008 (STOP)
1009 ",
1010 "AB",
1011 vec![
1012 Ligature(L {
1013 c: '2',
1014 original: "A".into(),
1015 includes_left_boundary: false,
1016 includes_right_boundary: false,
1017 }),
1018 Ligature(L {
1019 c: '1',
1020 original: "B".into(),
1021 includes_left_boundary: false,
1022 includes_right_boundary: false,
1023 }),
1024 ],
1025 ),
1026 (
1028 multiple_lig_6,
1029 "
1030 (LABEL C A)
1031 (/LIG C B C 1)
1032 (LIG/ C 1 C 2)
1033 (LABEL C 2)
1034 (LIG C 1 C 3)
1035 (STOP)
1036 ",
1037 "AB",
1038 vec![Ligature(L {
1039 c: '3',
1040 original: "AB".into(),
1041 includes_left_boundary: false,
1042 includes_right_boundary: false,
1043 })],
1044 ),
1045 (
1047 multiple_lig_7,
1048 "
1049 (LABEL C A)
1050 (LIG C B C 1)
1051 (STOP)
1052
1053 (LABEL C 1)
1054 (/LIG/>> C C C 2)
1055 (STOP)
1056 ",
1057 "ABC",
1058 vec![
1059 Ligature(L {
1060 c: '1',
1061 original: "AB".into(),
1062 includes_left_boundary: false,
1063 includes_right_boundary: false,
1064 }),
1065 Ligature(L {
1066 c: '2',
1067 original: "".into(),
1068 includes_left_boundary: false,
1069 includes_right_boundary: false,
1070 }),
1071 Char('C'),
1072 ],
1073 ),
1074 (
1075 kern_after_lig_1,
1076 "
1077 (LABEL C A)
1078 (LIG C B C 1)
1079 (STOP)
1080
1081 (LABEL C 1)
1082 (KRN C C R 0.1)
1083 ",
1084 "ABC",
1085 vec![
1086 Ligature(L {
1087 c: '1',
1088 original: "AB".into(),
1089 includes_left_boundary: false,
1090 includes_right_boundary: false,
1091 }),
1092 Kern(Scaled::ONE),
1093 Char('C'),
1094 ],
1095 ),
1096 (
1097 kern_after_lig_2,
1098 "
1099 (LABEL C A)
1100 (LIG C B C 1)
1101 (STOP)
1102
1103 (LABEL C 1)
1104 (KRN C A R 0.1)
1105 ",
1106 "ABAB",
1107 vec![
1108 Ligature(L {
1109 c: '1',
1110 original: "AB".into(),
1111 includes_left_boundary: false,
1112 includes_right_boundary: false,
1113 }),
1114 Kern(Scaled::ONE),
1115 Ligature(L {
1116 c: '1',
1117 original: "AB".into(),
1118 includes_left_boundary: false,
1119 includes_right_boundary: false,
1120 }),
1121 ],
1122 ),
1123 (
1124 left_boundary_char_1,
1125 "
1126 (LABEL BOUNDARYCHAR)
1127 (LIG C A C 1)
1128 ",
1129 "A",
1130 vec![Ligature(L {
1131 c: '1',
1132 original: "A".into(),
1133 includes_left_boundary: true,
1134 includes_right_boundary: false,
1135 }),],
1136 ),
1137 (
1138 left_boundary_char_2,
1139 "
1140 (LABEL BOUNDARYCHAR)
1141 (/LIG/ C A C 1)
1142 (/LIG/ C 1 C 2)
1143 ",
1144 "A",
1145 vec![
1146 Ligature(L {
1147 c: '2',
1148 original: "".into(),
1149 includes_left_boundary: true,
1150 includes_right_boundary: false,
1151 }),
1152 Ligature(L {
1153 c: '1',
1154 original: "".into(),
1155 includes_left_boundary: false,
1156 includes_right_boundary: false,
1157 }),
1158 Char('A'),
1159 ],
1160 ),
1161 (
1162 left_boundary_char_3,
1163 "
1164 (LABEL BOUNDARYCHAR)
1165 (/LIG/ C A C 1)
1166 ",
1167 "A",
1168 vec![
1169 Ligature(L {
1170 c: '1',
1171 original: "".into(),
1172 includes_left_boundary: true,
1173 includes_right_boundary: false,
1174 }),
1175 Char('A'),
1176 ],
1177 ),
1178 (
1180 left_boundary_char_4,
1181 "
1182 (LABEL BOUNDARYCHAR)
1183 (/LIG C A C 1)
1184 ",
1185 "A",
1186 vec![Ligature(L {
1187 c: '1',
1188 original: "A".into(),
1189 includes_left_boundary: true,
1190 includes_right_boundary: false,
1191 }),],
1192 ),
1193 (
1195 left_boundary_char_5,
1196 "
1197 (LABEL BOUNDARYCHAR)
1198 (/LIG> C A C 1)
1199 ",
1200 "A",
1201 vec![Ligature(L {
1202 c: '1',
1203 original: "A".into(),
1204 includes_left_boundary: true,
1205 includes_right_boundary: false,
1206 }),],
1207 ),
1208 (
1210 left_boundary_char_6,
1211 "
1212 (LABEL BOUNDARYCHAR)
1213 (LIG/ C A C 1)
1214 ",
1215 "A",
1216 vec![
1217 Ligature(L {
1218 c: '1',
1219 original: "".into(),
1220 includes_left_boundary: true,
1221 includes_right_boundary: false,
1222 }),
1223 Char('A'),
1224 ],
1225 ),
1226 (
1228 left_boundary_char_7,
1229 "
1230 (LABEL BOUNDARYCHAR)
1231 (LIG/> C A C 1)
1232 ",
1233 "A",
1234 vec![
1235 Ligature(L {
1236 c: '1',
1237 original: "".into(),
1238 includes_left_boundary: true,
1239 includes_right_boundary: false,
1240 }),
1241 Char('A'),
1242 ],
1243 ),
1244 (
1246 left_boundary_char_8,
1247 "
1248 (LABEL BOUNDARYCHAR)
1249 (/LIG/> C A C 1)
1250 ",
1251 "A",
1252 vec![
1253 Ligature(L {
1254 c: '1',
1255 original: "".into(),
1256 includes_left_boundary: true,
1257 includes_right_boundary: false,
1258 }),
1259 Char('A'),
1260 ],
1261 ),
1262 (
1264 left_boundary_char_9,
1265 "
1266 (LABEL BOUNDARYCHAR)
1267 (/LIG/>> C A C 1)
1268 ",
1269 "A",
1270 vec![
1271 Ligature(L {
1272 c: '1',
1273 original: "".into(),
1274 includes_left_boundary: true,
1275 includes_right_boundary: false,
1276 }),
1277 Char('A'),
1278 ],
1279 ),
1280 (
1281 right_boundary_char_lig_1,
1282 "
1283 (LABEL C A)
1284 (LIG C R C 1)
1285 (STOP)
1286 ",
1287 "A",
1288 vec![Ligature(L {
1289 c: '1',
1290 original: "A".into(),
1291 includes_left_boundary: false,
1292 includes_right_boundary: true,
1293 }),],
1294 ),
1295 (
1297 right_boundary_char_lig_2,
1298 "
1299 (LABEL C A)
1300 (/LIG C R C B)
1301 (STOP)
1302 ",
1303 "A",
1304 vec![
1305 Char('A'),
1306 Ligature(L {
1307 c: 'B',
1308 original: "".into(),
1309 includes_left_boundary: false,
1310 includes_right_boundary: true,
1311 }),
1312 ],
1313 ),
1314 (
1316 right_boundary_char_lig_3,
1317 "
1318 (LABEL C A)
1319 (/LIG> C R C B)
1320 (STOP)
1321 ",
1322 "A",
1323 vec![
1324 Char('A'),
1325 Ligature(L {
1326 c: 'B',
1327 original: "".into(),
1328 includes_left_boundary: false,
1329 includes_right_boundary: true,
1330 }),
1331 ],
1332 ),
1333 (
1334 right_boundary_char_lig_4,
1335 "
1336 (LABEL C A)
1337 (/LIG/ C R C B)
1338 (STOP)
1339 ",
1340 "A",
1341 vec![
1342 Char('A'),
1343 Ligature(L {
1344 c: 'B',
1345 original: "".into(),
1346 includes_left_boundary: false,
1347 includes_right_boundary: true,
1348 }),
1349 ],
1350 ),
1351 (
1353 right_boundary_char_lig_5,
1354 "
1355 (LABEL C A)
1356 (LIG/ C R C B)
1357 (STOP)
1358 ",
1359 "A",
1360 vec![Ligature(L {
1361 c: 'B',
1362 original: "A".into(),
1363 includes_left_boundary: false,
1364 includes_right_boundary: true,
1365 }),],
1366 ),
1367 (
1369 right_boundary_char_lig_6,
1370 "
1371 (LABEL C A)
1372 (LIG/> C R C B)
1373 (STOP)
1374 ",
1375 "A",
1376 vec![Ligature(L {
1377 c: 'B',
1378 original: "A".into(),
1379 includes_left_boundary: false,
1380 includes_right_boundary: true,
1381 }),],
1382 ),
1383 (
1385 right_boundary_char_lig_7,
1386 "
1387 (LABEL C A)
1388 (/LIG/> C R C B)
1389 (STOP)
1390 ",
1391 "A",
1392 vec![
1393 Char('A'),
1394 Ligature(L {
1395 c: 'B',
1396 original: "".into(),
1397 includes_left_boundary: false,
1398 includes_right_boundary: true,
1399 }),
1400 ],
1401 ),
1402 (
1404 right_boundary_char_lig_8,
1405 "
1406 (LABEL C A)
1407 (/LIG/>> C R C B)
1408 (STOP)
1409 ",
1410 "A",
1411 vec![
1412 Char('A'),
1413 Ligature(L {
1414 c: 'B',
1415 original: "".into(),
1416 includes_left_boundary: false,
1417 includes_right_boundary: true,
1418 }),
1419 ],
1420 ),
1421 (
1422 right_boundary_char_lig_9,
1423 "
1424 (LABEL C A)
1425 (LIG C R C B)
1426 (LABEL C B)
1427 (LIG C R C C)
1428 (STOP)
1429 ",
1430 "A",
1431 vec![Ligature(L {
1432 c: 'B',
1433 original: "A".into(),
1434 includes_left_boundary: false,
1435 includes_right_boundary: true,
1436 }),],
1437 ),
1438 (
1439 right_boundary_char_lig_10,
1440 "
1441 (LABEL C A)
1442 (LIG/ C R C B)
1443 (LABEL C B)
1444 (LIG/ C R C C)
1445 (STOP)
1446 ",
1447 "A",
1448 vec![Ligature(L {
1449 c: 'C',
1450 original: "A".into(),
1451 includes_left_boundary: false,
1452 includes_right_boundary: true,
1453 }),],
1454 ),
1455 (
1456 right_boundary_char_lig_11,
1457 "
1458 (LABEL C A)
1459 (LIG C R C B)
1460 (LABEL C B)
1461 (LIG/ C R C C)
1462 (STOP)
1463 ",
1464 "A",
1465 vec![Ligature(L {
1466 c: 'B',
1467 original: "A".into(),
1468 includes_left_boundary: false,
1469 includes_right_boundary: true,
1470 }),],
1471 ),
1472 (
1473 right_boundary_char_lig_12,
1474 "
1475 (LABEL C A)
1476 (LIG/ C R C B)
1477 (LABEL C B)
1478 (LIG C R C C)
1479 (STOP)
1480 ",
1481 "A",
1482 vec![Ligature(L {
1483 c: 'C',
1484 original: "A".into(),
1485 includes_left_boundary: false,
1486 includes_right_boundary: true,
1487 }),],
1488 ),
1489 (
1490 right_boundary_char_kern_1,
1491 "
1492 (LABEL C A)
1493 (KRN C R R 1)
1494 (STOP)
1495 ",
1496 "A",
1497 vec![Char('A'), Kern(Scaled::ONE * 10),],
1498 ),
1499 (
1500 right_boundary_char_kern_2,
1501 "
1502 (LABEL C A)
1503 (LIG/ C R C B)
1504 (LABEL C B)
1505 (LIG/ C R C C)
1506 (LABEL C C)
1507 (KRN C R R 1)
1508 (STOP)
1509 ",
1510 "A",
1511 vec![
1512 Ligature(L {
1513 c: 'C',
1514 original: "A".into(),
1515 includes_left_boundary: false,
1516 includes_right_boundary: true,
1517 }),
1518 Kern(Scaled::ONE * 10),
1519 ],
1520 ),
1521 (
1522 right_boundary_char_kern_3,
1523 "
1524 (LABEL C A)
1525 (LIG C B C C)
1526 (LABEL C C)
1527 (KRN C R R 1)
1528 (STOP)
1529 ",
1530 "AB",
1531 vec![
1532 Ligature(L {
1533 c: 'C',
1534 original: "AB".into(),
1535 includes_left_boundary: false,
1536 includes_right_boundary: false,
1537 }),
1538 Kern(Scaled::ONE * 10),
1539 ],
1540 ),
1541 );
1542}