1use fea_rs::{
2 Kind,
3 typed::{AstNode as _, GlyphOrClass},
4};
5
6use crate::{
7 AsFea, GlyphContainer, PotentiallyContextualStatement,
8 contextual::{backtrack, context_glyphs, lookahead},
9};
10use std::ops::Range;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct SingleSubstStatement {
15 pub location: Range<usize>,
17 pub prefix: Vec<GlyphContainer>,
19 pub suffix: Vec<GlyphContainer>,
21 pub glyphs: Vec<GlyphContainer>,
27 pub replacement: Vec<GlyphContainer>,
29 pub force_chain: bool,
31}
32
33impl SingleSubstStatement {
34 pub fn new(
41 glyphs: Vec<GlyphContainer>,
42 replacement: Vec<GlyphContainer>,
43 prefix: Vec<GlyphContainer>,
44 suffix: Vec<GlyphContainer>,
45 location: Range<usize>,
46 force_chain: bool,
47 ) -> Self {
48 Self {
49 prefix,
50 suffix,
51 glyphs,
52 replacement,
53 location,
54 force_chain,
55 }
56 }
57}
58
59impl PotentiallyContextualStatement for SingleSubstStatement {
60 fn prefix(&self) -> &[GlyphContainer] {
61 &self.prefix
62 }
63 fn suffix(&self) -> &[GlyphContainer] {
64 &self.suffix
65 }
66 fn force_chain(&self) -> bool {
67 self.force_chain
68 }
69
70 fn format_begin(&self, _indent: &str) -> String {
71 "sub ".to_string()
72 }
73
74 fn format_contextual_parts(&self, _indent: &str) -> Vec<String> {
75 self.glyphs
76 .iter()
77 .map(|g| format!("{}'", g.as_fea("")))
78 .collect()
79 }
80
81 fn format_noncontextual_parts(&self, _indent: &str) -> Vec<String> {
82 self.glyphs.iter().map(|g| g.as_fea("")).collect()
83 }
84 fn format_end(&self, _indent: &str) -> String {
85 let replacement_str: Vec<String> = self.replacement.iter().map(|g| g.as_fea("")).collect();
86 format!(" by {}", replacement_str.join(" "))
87 }
88}
89
90impl From<fea_rs::typed::Gsub1> for SingleSubstStatement {
91 fn from(val: fea_rs::typed::Gsub1) -> Self {
92 let target = val
93 .node()
94 .iter_children()
95 .find_map(GlyphOrClass::cast)
96 .unwrap();
97 let replacement = val
98 .node()
99 .iter_children()
100 .skip_while(|t| t.kind() != Kind::ByKw)
101 .find_map(GlyphOrClass::cast)
102 .unwrap();
103 SingleSubstStatement {
104 prefix: vec![],
105 suffix: vec![],
106 glyphs: vec![target.into()],
107 replacement: vec![replacement.into()],
108 location: val.node().range(),
109 force_chain: false,
110 }
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct MultipleSubstStatement {
117 pub location: Range<usize>,
119 pub prefix: Vec<GlyphContainer>,
121 pub suffix: Vec<GlyphContainer>,
123 pub glyph: GlyphContainer,
125 pub replacement: Vec<GlyphContainer>,
127 pub force_chain: bool,
129}
130
131impl MultipleSubstStatement {
132 pub fn new(
134 glyph: GlyphContainer,
135 replacement: Vec<GlyphContainer>,
136 prefix: Vec<GlyphContainer>,
137 suffix: Vec<GlyphContainer>,
138 location: Range<usize>,
139 force_chain: bool,
140 ) -> Self {
141 Self {
142 prefix,
143 suffix,
144 glyph,
145 replacement,
146 location,
147 force_chain,
148 }
149 }
150}
151
152impl PotentiallyContextualStatement for MultipleSubstStatement {
153 fn prefix(&self) -> &[GlyphContainer] {
154 &self.prefix
155 }
156 fn suffix(&self) -> &[GlyphContainer] {
157 &self.suffix
158 }
159 fn force_chain(&self) -> bool {
160 self.force_chain
161 }
162
163 fn format_begin(&self, _indent: &str) -> String {
164 "sub ".to_string()
165 }
166
167 fn format_contextual_parts(&self, _indent: &str) -> Vec<String> {
168 vec![format!("{}'", self.glyph.as_fea(""))]
169 }
170
171 fn format_noncontextual_parts(&self, _indent: &str) -> Vec<String> {
172 vec![self.glyph.as_fea("")]
173 }
174
175 fn format_end(&self, _indent: &str) -> String {
176 let replacement_str: Vec<String> = self.replacement.iter().map(|g| g.as_fea("")).collect();
177 format!(" by {}", replacement_str.join(" "))
178 }
179}
180
181impl From<fea_rs::typed::Gsub2> for MultipleSubstStatement {
182 fn from(val: fea_rs::typed::Gsub2) -> Self {
183 let target = val
184 .node()
185 .iter_children()
186 .find_map(GlyphOrClass::cast)
187 .unwrap();
188 let replacement = val
189 .node()
190 .iter_children()
191 .skip_while(|t| t.kind() != Kind::ByKw)
192 .skip(1)
193 .filter_map(GlyphOrClass::cast);
194 MultipleSubstStatement {
195 prefix: vec![],
196 suffix: vec![],
197 glyph: target.into(),
198 replacement: replacement.map(|x| x.into()).collect(),
199 location: val.node().range(),
200 force_chain: false,
201 }
202 }
203}
204
205impl TryFrom<fea_rs::typed::Gsub6> for MultipleSubstStatement {
206 type Error = ();
207
208 fn try_from(val: fea_rs::typed::Gsub6) -> Result<Self, ()> {
209 if !val.node().iter_children().any(|c| {
212 if let Some(inline) = fea_rs::typed::InlineSubRule::cast(c) {
213 inline
214 .node()
215 .iter_children()
216 .filter(|k| GlyphOrClass::cast(k).is_some())
217 .count()
218 > 1
219 } else {
220 false
221 }
222 }) {
223 return Err(());
224 }
225 let prefix = backtrack(val.node());
226 let context = context_glyphs(val.node());
227 let suffix = lookahead(val.node());
228 let targets = inline_sub_targets(val.node());
229
230 Ok(MultipleSubstStatement {
231 prefix,
232 suffix,
233 glyph: context.into_iter().next().unwrap(),
234 replacement: targets,
235 location: val.node().range(),
236 force_chain: true,
237 })
238 }
239}
240
241fn inline_sub_targets(val: &fea_rs::Node) -> Vec<GlyphContainer> {
242 let inline_sub = val
243 .iter_children()
244 .find_map(fea_rs::typed::InlineSubRule::cast)
245 .unwrap();
246 inline_sub
247 .node()
248 .iter_children()
249 .filter_map(GlyphOrClass::cast)
250 .map(|goc| goc.into())
251 .collect()
252}
253
254#[derive(Debug, Clone, PartialEq, Eq)]
256pub struct AlternateSubstStatement {
257 pub location: Range<usize>,
259 pub prefix: Vec<GlyphContainer>,
261 pub suffix: Vec<GlyphContainer>,
263 pub glyph: GlyphContainer,
265 pub replacement: GlyphContainer,
267 pub force_chain: bool,
269}
270
271impl AlternateSubstStatement {
272 pub fn new(
274 glyph: GlyphContainer,
275 replacement: GlyphContainer,
276 prefix: Vec<GlyphContainer>,
277 suffix: Vec<GlyphContainer>,
278 location: Range<usize>,
279 force_chain: bool,
280 ) -> Self {
281 Self {
282 prefix,
283 suffix,
284 glyph,
285 replacement,
286 location,
287 force_chain,
288 }
289 }
290}
291
292impl PotentiallyContextualStatement for AlternateSubstStatement {
293 fn prefix(&self) -> &[GlyphContainer] {
294 &self.prefix
295 }
296 fn suffix(&self) -> &[GlyphContainer] {
297 &self.suffix
298 }
299 fn force_chain(&self) -> bool {
300 self.force_chain
301 }
302
303 fn format_begin(&self, _indent: &str) -> String {
304 "sub ".to_string()
305 }
306
307 fn format_contextual_parts(&self, _indent: &str) -> Vec<String> {
308 vec![format!("{}'", self.glyph.as_fea(""))]
309 }
310
311 fn format_noncontextual_parts(&self, _indent: &str) -> Vec<String> {
312 vec![self.glyph.as_fea("")]
313 }
314 fn format_end(&self, _indent: &str) -> String {
315 let replacement_str: String = self.replacement.as_fea("");
316 format!(" from {}", replacement_str)
317 }
318}
319
320impl From<fea_rs::typed::Gsub3> for AlternateSubstStatement {
321 fn from(val: fea_rs::typed::Gsub3) -> Self {
322 let target = val
323 .node()
324 .iter_children()
325 .find_map(GlyphOrClass::cast)
326 .unwrap();
327 let replacement = val
328 .node()
329 .iter_children()
330 .skip_while(|t| t.kind() != Kind::FromKw)
331 .find_map(fea_rs::typed::GlyphClass::cast)
332 .unwrap();
333 AlternateSubstStatement {
334 prefix: vec![],
335 suffix: vec![],
336 glyph: target.into(),
337 replacement: replacement.into(),
338 location: val.node().range(),
339 force_chain: false,
340 }
341 }
342}
343
344#[derive(Debug, Clone, PartialEq, Eq)]
346pub struct LigatureSubstStatement {
347 pub location: Range<usize>,
349 pub prefix: Vec<GlyphContainer>,
351 pub suffix: Vec<GlyphContainer>,
353 pub glyphs: Vec<GlyphContainer>,
355 pub replacement: GlyphContainer,
357 pub force_chain: bool,
359}
360
361impl LigatureSubstStatement {
362 pub fn new(
364 glyphs: Vec<GlyphContainer>,
365 replacement: GlyphContainer,
366 prefix: Vec<GlyphContainer>,
367 suffix: Vec<GlyphContainer>,
368 location: Range<usize>,
369 force_chain: bool,
370 ) -> Self {
371 Self {
372 prefix,
373 suffix,
374 glyphs,
375 replacement,
376 location,
377 force_chain,
378 }
379 }
380}
381
382impl PotentiallyContextualStatement for LigatureSubstStatement {
383 fn prefix(&self) -> &[GlyphContainer] {
384 &self.prefix
385 }
386 fn suffix(&self) -> &[GlyphContainer] {
387 &self.suffix
388 }
389 fn force_chain(&self) -> bool {
390 self.force_chain
391 }
392
393 fn format_begin(&self, _indent: &str) -> String {
394 "sub ".to_string()
395 }
396
397 fn format_contextual_parts(&self, _indent: &str) -> Vec<String> {
398 self.glyphs
399 .iter()
400 .map(|g| format!("{}'", g.as_fea("")))
401 .collect()
402 }
403
404 fn format_noncontextual_parts(&self, _indent: &str) -> Vec<String> {
405 self.glyphs.iter().map(|g| g.as_fea("")).collect()
406 }
407
408 fn format_end(&self, _indent: &str) -> String {
409 let replacement_str: String = self.replacement.as_fea("");
410 format!(" by {}", replacement_str)
411 }
412}
413
414impl From<fea_rs::typed::Gsub4> for LigatureSubstStatement {
415 fn from(val: fea_rs::typed::Gsub4) -> Self {
416 let target = val
417 .node()
418 .iter_children()
419 .take_while(|t| t.kind() != Kind::ByKw)
420 .filter_map(GlyphOrClass::cast)
421 .collect::<Vec<_>>();
422 let replacement = val
423 .node()
424 .iter_children()
425 .skip_while(|t| t.kind() != Kind::ByKw)
426 .find_map(GlyphOrClass::cast)
427 .unwrap();
428 LigatureSubstStatement {
429 prefix: vec![],
430 suffix: vec![],
431 glyphs: target.into_iter().map(|g| g.into()).collect(),
432 replacement: replacement.into(),
433 location: val.node().range(),
434 force_chain: false,
435 }
436 }
437}
438
439#[derive(Debug, Clone, PartialEq, Eq)]
441pub struct ReverseChainSingleSubstStatement {
442 pub location: Range<usize>,
444 pub prefix: Vec<GlyphContainer>,
446 pub suffix: Vec<GlyphContainer>,
448 pub glyphs: Vec<GlyphContainer>,
450 pub replacements: Vec<GlyphContainer>,
452}
453
454impl ReverseChainSingleSubstStatement {
455 pub fn new(
457 glyphs: Vec<GlyphContainer>,
458 replacements: Vec<GlyphContainer>,
459 prefix: Vec<GlyphContainer>,
460 suffix: Vec<GlyphContainer>,
461 location: Range<usize>,
462 ) -> Self {
463 Self {
464 prefix,
465 suffix,
466 glyphs,
467 replacements,
468 location,
469 }
470 }
471}
472
473impl AsFea for ReverseChainSingleSubstStatement {
474 fn as_fea(&self, _indent: &str) -> String {
475 let mut res = String::new();
476 res.push_str("rsub ");
477 if !self.prefix.is_empty() || !self.suffix.is_empty() {
478 if !self.prefix.is_empty() {
479 let prefix_str: Vec<String> = self.prefix.iter().map(|g| g.as_fea("")).collect();
480 res.push_str(prefix_str.join(" ").as_str());
481 res.push(' ');
482 }
483 let glyphs_str: Vec<String> = self
484 .glyphs
485 .iter()
486 .map(|g| format!("{}'", g.as_fea("")))
487 .collect();
488 res.push_str(&glyphs_str.join(" "));
489 if !self.suffix.is_empty() {
490 res.push(' ');
491 let suffix_str: Vec<String> = self.suffix.iter().map(|g| g.as_fea("")).collect();
492 res.push_str(suffix_str.join(" ").as_str());
493 }
494 } else {
495 let glyphs_str: Vec<String> = self.glyphs.iter().map(|g| g.as_fea("")).collect();
496 res.push_str(&glyphs_str.join(" "));
497 }
498 let replacement_str: Vec<String> = self.replacements.iter().map(|g| g.as_fea("")).collect();
499 res.push_str(&format!(" by {};", replacement_str.join(" ")));
500 res
501 }
502}
503
504impl From<fea_rs::typed::Gsub8> for ReverseChainSingleSubstStatement {
505 fn from(val: fea_rs::typed::Gsub8) -> Self {
506 let prefix = backtrack(val.node());
507 let context = context_glyphs(val.node());
508 let suffix = lookahead(val.node());
509 let targets = inline_sub_targets(val.node());
510 ReverseChainSingleSubstStatement {
516 prefix,
517 suffix,
518 glyphs: context, replacements: targets,
520 location: val.node().range(),
521 }
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use smol_str::SmolStr;
528
529 use crate::{GlyphClass, GlyphName};
530
531 use super::*;
532
533 #[test]
534 fn test_generate_gsub1() {
535 let gsub1 = SingleSubstStatement::new(
536 vec![GlyphContainer::GlyphName(GlyphName::new("x"))],
537 vec![GlyphContainer::GlyphName(GlyphName::new("a"))],
538 vec![GlyphContainer::GlyphClass(GlyphClass::new(
539 vec![
540 GlyphContainer::GlyphName(GlyphName::new("a.smcp")),
541 GlyphContainer::GlyphName(GlyphName::new("b.smcp")),
542 ],
543 0..0,
544 ))],
545 vec![],
546 0..0,
547 false,
548 );
549 assert_eq!(gsub1.as_fea(""), "sub [a.smcp b.smcp] x' by a;");
550 }
551
552 #[test]
553 fn test_roundtrip_gsub1() {
554 let fea = "feature smcp { sub a by a.smcp; } smcp;";
555 let (parsed, _) = fea_rs::parse::parse_string(fea);
556 let gsub1 = parsed
557 .root()
558 .iter_children()
559 .find_map(fea_rs::typed::Feature::cast)
560 .and_then(|feature| {
561 feature
562 .node()
563 .iter_children()
564 .find_map(fea_rs::typed::Gsub1::cast)
565 })
566 .unwrap();
567 let single_subst = SingleSubstStatement::from(gsub1);
568 assert_eq!(single_subst.as_fea(""), "sub a by a.smcp;");
569 }
570
571 #[test]
572 fn test_generate_gsub2() {
573 let gsub2 = MultipleSubstStatement::new(
574 GlyphContainer::GlyphName(GlyphName::new("x")),
575 vec![
576 GlyphContainer::GlyphName(GlyphName::new("a")),
577 GlyphContainer::GlyphName(GlyphName::new("b")),
578 ],
579 vec![],
580 vec![GlyphContainer::GlyphClass(GlyphClass::new(
581 vec![
582 GlyphContainer::GlyphName(GlyphName::new("c.smcp")),
583 GlyphContainer::GlyphName(GlyphName::new("d.smcp")),
584 ],
585 0..0,
586 ))],
587 0..0,
588 false,
589 );
590 assert_eq!(gsub2.as_fea(""), "sub x' [c.smcp d.smcp] by a b;");
591 }
592
593 #[test]
594 fn test_roundtrip_gsub2() {
595 let fea = "feature liga { sub x by a b; } liga;";
596 let (parsed, _) = fea_rs::parse::parse_string(fea);
597 let gsub2 = parsed
598 .root()
599 .iter_children()
600 .find_map(fea_rs::typed::Feature::cast)
601 .and_then(|feature| {
602 feature
603 .node()
604 .iter_children()
605 .find_map(fea_rs::typed::Gsub2::cast)
606 })
607 .unwrap();
608 let multiple_subst = MultipleSubstStatement::from(gsub2);
609 assert_eq!(multiple_subst.as_fea(""), "sub x by a b;");
610 }
611
612 #[test]
613 fn test_generate_gsub3() {
614 let gsub3 = AlternateSubstStatement::new(
615 GlyphContainer::GlyphName(GlyphName::new("x")),
616 GlyphContainer::GlyphClassName(SmolStr::new("@a_alternates")),
617 vec![],
618 vec![],
619 0..0,
620 false,
621 );
622 assert_eq!(gsub3.as_fea(""), "sub x from @a_alternates;");
623 }
624
625 #[test]
626 fn test_roundtrip_gsub3() {
627 let fea = "feature calt { sub x from @x_alternates; } calt;";
628 let (parsed, _) = fea_rs::parse::parse_string(fea);
629 let gsub3 = parsed
630 .root()
631 .iter_children()
632 .find_map(fea_rs::typed::Feature::cast)
633 .and_then(|feature| {
634 feature
635 .node()
636 .iter_children()
637 .find_map(fea_rs::typed::Gsub3::cast)
638 })
639 .unwrap();
640 let alternate_subst = AlternateSubstStatement::from(gsub3);
641 assert_eq!(alternate_subst.as_fea(""), "sub x from @x_alternates;");
642 }
643
644 #[test]
645 fn test_generate_gsub4() {
646 let gsub4 = LigatureSubstStatement::new(
647 vec![
648 GlyphContainer::GlyphName(GlyphName::new("f")),
649 GlyphContainer::GlyphName(GlyphName::new("i")),
650 ],
651 GlyphContainer::GlyphName(GlyphName::new("fi")),
652 vec![],
653 vec![],
654 0..0,
655 false,
656 );
657 assert_eq!(gsub4.as_fea(""), "sub f i by fi;");
658 }
659
660 #[test]
661 fn test_roundtrip_gsub4() {
662 let fea = "feature lig { sub f i by fi; } lig;";
663 let (parsed, _) = fea_rs::parse::parse_string(fea);
664 let gsub4 = parsed
665 .root()
666 .iter_children()
667 .find_map(fea_rs::typed::Feature::cast)
668 .and_then(|feature| {
669 feature
670 .node()
671 .iter_children()
672 .find_map(fea_rs::typed::Gsub4::cast)
673 })
674 .unwrap();
675 let ligature_subst = LigatureSubstStatement::from(gsub4);
676 assert_eq!(ligature_subst.as_fea(""), "sub f i by fi;");
677 }
678
679 #[test]
680 fn test_multiple_subst_from_gsub6() {
681 let fea = r#"feature foo { sub [a b c] d' e f g h i j by k l m n o p; } foo;"#;
682 let (parsed, _) = fea_rs::parse::parse_string(fea);
683 let gsub6 = parsed
684 .root()
685 .iter_children()
686 .find_map(fea_rs::typed::Feature::cast)
687 .and_then(|feature| {
688 feature
689 .node()
690 .iter_children()
691 .find_map(fea_rs::typed::Gsub6::cast)
692 })
693 .unwrap();
694 let multiple_subst = MultipleSubstStatement::try_from(gsub6).unwrap();
695 assert_eq!(multiple_subst.prefix.len(), 1);
696 assert_eq!(multiple_subst.suffix.len(), 6);
697 assert_eq!(multiple_subst.glyph.as_fea(""), "d");
698 assert_eq!(
699 multiple_subst
700 .replacement
701 .iter()
702 .map(|g| g.as_fea(""))
703 .collect::<Vec<_>>(),
704 vec!["k", "l", "m", "n", "o", "p"]
705 );
706 }
707
708 #[test]
709 fn test_roundtrip_gsub8_contextual() {
710 let fea = r#"feature foo { rsub x y a' z w by b; } foo;"#;
711 let (parsed, _) = fea_rs::parse::parse_string(fea);
712 let gsub8 = parsed
713 .root()
714 .iter_children()
715 .find_map(fea_rs::typed::Feature::cast)
716 .and_then(|feature| {
717 feature
718 .node()
719 .iter_children()
720 .find_map(fea_rs::typed::Gsub8::cast)
721 })
722 .unwrap();
723 let stmt = ReverseChainSingleSubstStatement::from(gsub8);
724 assert_eq!(stmt.glyphs.len(), 1);
725 assert_eq!(stmt.glyphs[0].as_fea(""), "a");
726 assert_eq!(stmt.replacements.len(), 1);
727 assert_eq!(stmt.replacements[0].as_fea(""), "b");
728 assert_eq!(stmt.prefix.len(), 2);
729 assert_eq!(stmt.suffix.len(), 2);
730 assert_eq!(stmt.as_fea(""), "rsub x y a' z w by b;");
731 }
732
733 #[test]
734 fn test_generation_gsub8() {
735 let stmt = ReverseChainSingleSubstStatement::new(
736 vec![GlyphContainer::GlyphName(GlyphName::new("x"))],
737 vec![GlyphContainer::GlyphName(GlyphName::new("y"))],
738 vec![],
739 vec![],
740 0..0,
741 );
742 assert_eq!(stmt.as_fea(""), "rsub x by y;");
743 }
744}