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