1use std::ops::Range;
2
3use fea_rs::typed::AstNode as _;
4
5use crate::{AsFea, Comment, GlyphContainer, Statement};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct AttachStatement {
10 pub glyphs: GlyphContainer,
12 pub contour_points: Vec<usize>,
14 pub location: Range<usize>,
16}
17impl AttachStatement {
18 pub fn new(glyphs: GlyphContainer, contour_points: Vec<usize>, location: Range<usize>) -> Self {
20 Self {
21 glyphs,
22 contour_points,
23 location,
24 }
25 }
26}
27impl AsFea for AttachStatement {
28 fn as_fea(&self, _indent: &str) -> String {
29 let points = self
30 .contour_points
31 .iter()
32 .map(|p| p.to_string())
33 .collect::<Vec<_>>()
34 .join(" ");
35 format!("Attach {} {};", self.glyphs.as_fea(""), points)
36 }
37}
38impl From<fea_rs::typed::GdefAttach> for AttachStatement {
39 fn from(val: fea_rs::typed::GdefAttach) -> Self {
40 let glyphs = val
41 .iter()
42 .find_map(fea_rs::typed::GlyphOrClass::cast)
43 .unwrap();
44 let contour_points: Vec<usize> = val
45 .iter()
46 .filter(|t| t.kind() == fea_rs::Kind::Number)
47 .map(|t| t.as_token().unwrap().text.parse().unwrap())
48 .collect();
49 AttachStatement::new(glyphs.into(), contour_points, val.node().range())
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct GlyphClassDefStatement {
62 pub base_glyphs: Option<GlyphContainer>,
64 pub ligature_glyphs: Option<GlyphContainer>,
66 pub mark_glyphs: Option<GlyphContainer>,
68 pub component_glyphs: Option<GlyphContainer>,
70 pub location: Range<usize>,
72}
73
74impl GlyphClassDefStatement {
75 pub fn new(
77 base_glyphs: Option<GlyphContainer>,
78 ligature_glyphs: Option<GlyphContainer>,
79 mark_glyphs: Option<GlyphContainer>,
80 component_glyphs: Option<GlyphContainer>,
81 location: Range<usize>,
82 ) -> Self {
83 Self {
84 base_glyphs,
85 ligature_glyphs,
86 mark_glyphs,
87 component_glyphs,
88 location,
89 }
90 }
91}
92
93impl AsFea for GlyphClassDefStatement {
94 fn as_fea(&self, _indent: &str) -> String {
95 let base = self
96 .base_glyphs
97 .as_ref()
98 .map(|g| g.as_fea(""))
99 .unwrap_or_default();
100 let liga = self
101 .ligature_glyphs
102 .as_ref()
103 .map(|g| g.as_fea(""))
104 .unwrap_or_default();
105 let mark = self
106 .mark_glyphs
107 .as_ref()
108 .map(|g| g.as_fea(""))
109 .unwrap_or_default();
110 let comp = self
111 .component_glyphs
112 .as_ref()
113 .map(|g| g.as_fea(""))
114 .unwrap_or_default();
115 format!("GlyphClassDef {}, {}, {}, {};", base, liga, mark, comp)
116 }
117}
118
119impl From<fea_rs::typed::GdefClassDef> for GlyphClassDefStatement {
120 fn from(val: fea_rs::typed::GdefClassDef) -> Self {
121 let mut entries = val
123 .iter()
124 .filter(|t| t.kind() == fea_rs::Kind::GdefClassDefEntryNode)
125 .filter_map(fea_rs::typed::GdefClassDefEntry::cast);
126
127 let extract_container =
129 |entry: fea_rs::typed::GdefClassDefEntry| -> Option<GlyphContainer> {
130 entry
131 .iter()
132 .find_map(fea_rs::typed::GlyphOrClass::cast)
133 .map(Into::into)
134 };
135
136 let base_glyphs = entries.next().and_then(extract_container);
137 let ligature_glyphs = entries.next().and_then(extract_container);
138 let mark_glyphs = entries.next().and_then(extract_container);
139 let component_glyphs = entries.next().and_then(extract_container);
140
141 GlyphClassDefStatement::new(
142 base_glyphs,
143 ligature_glyphs,
144 mark_glyphs,
145 component_glyphs,
146 val.range(),
147 )
148 }
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct LigatureCaretByIndexStatement {
154 pub glyphs: GlyphContainer,
156 pub carets: Vec<usize>,
158 pub location: Range<usize>,
160}
161
162impl LigatureCaretByIndexStatement {
163 pub fn new(glyphs: GlyphContainer, carets: Vec<usize>, location: Range<usize>) -> Self {
165 Self {
166 glyphs,
167 carets,
168 location,
169 }
170 }
171}
172
173impl AsFea for LigatureCaretByIndexStatement {
174 fn as_fea(&self, _indent: &str) -> String {
175 let carets = self
176 .carets
177 .iter()
178 .map(|c| c.to_string())
179 .collect::<Vec<_>>()
180 .join(" ");
181 format!(
182 "LigatureCaretByIndex {} {};",
183 self.glyphs.as_fea(""),
184 carets
185 )
186 }
187}
188
189impl From<fea_rs::typed::GdefLigatureCaret> for LigatureCaretByIndexStatement {
190 fn from(val: fea_rs::typed::GdefLigatureCaret) -> Self {
191 let glyphs = val
192 .iter()
193 .find_map(fea_rs::typed::GlyphOrClass::cast)
194 .unwrap();
195
196 let carets: Vec<usize> = val
198 .iter()
199 .filter(|t| t.kind() == fea_rs::Kind::Number)
200 .map(|t| t.as_token().unwrap().text.parse().unwrap())
201 .collect();
202
203 LigatureCaretByIndexStatement::new(glyphs.into(), carets, val.node().range())
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
209pub struct LigatureCaretByPosStatement {
210 pub glyphs: GlyphContainer,
212 pub carets: Vec<i16>,
214 pub location: Range<usize>,
216}
217
218impl LigatureCaretByPosStatement {
219 pub fn new(glyphs: GlyphContainer, carets: Vec<i16>, location: Range<usize>) -> Self {
221 Self {
222 glyphs,
223 carets,
224 location,
225 }
226 }
227}
228
229impl AsFea for LigatureCaretByPosStatement {
230 fn as_fea(&self, _indent: &str) -> String {
231 let carets = self
232 .carets
233 .iter()
234 .map(|c| c.to_string())
235 .collect::<Vec<_>>()
236 .join(" ");
237 format!("LigatureCaretByPos {} {};", self.glyphs.as_fea(""), carets)
238 }
239}
240
241impl From<fea_rs::typed::GdefLigatureCaret> for LigatureCaretByPosStatement {
242 fn from(val: fea_rs::typed::GdefLigatureCaret) -> Self {
243 let glyphs = val
244 .iter()
245 .find_map(fea_rs::typed::GlyphOrClass::cast)
246 .unwrap();
247
248 let carets: Vec<i16> = val
250 .iter()
251 .filter(|t| t.kind() == fea_rs::Kind::Number)
252 .map(|t| t.as_token().unwrap().text.parse().unwrap())
253 .collect();
254
255 LigatureCaretByPosStatement::new(glyphs.into(), carets, val.node().range())
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, Eq)]
261pub enum GdefStatement {
262 Attach(AttachStatement),
264 GlyphClassDef(GlyphClassDefStatement),
266 LigatureCaretByIndex(LigatureCaretByIndexStatement),
268 LigatureCaretByPos(LigatureCaretByPosStatement),
270 Comment(Comment),
272 }
274impl AsFea for GdefStatement {
275 fn as_fea(&self, indent: &str) -> String {
276 match self {
277 GdefStatement::Attach(stmt) => stmt.as_fea(indent),
278 GdefStatement::GlyphClassDef(stmt) => stmt.as_fea(indent),
279 GdefStatement::LigatureCaretByIndex(stmt) => stmt.as_fea(indent),
280 GdefStatement::LigatureCaretByPos(stmt) => stmt.as_fea(indent),
281 GdefStatement::Comment(cmt) => cmt.as_fea(indent),
282 }
284 }
285}
286impl From<GdefStatement> for Statement {
287 fn from(val: GdefStatement) -> Self {
288 match val {
289 GdefStatement::Attach(stmt) => Statement::GdefAttach(stmt),
290 GdefStatement::GlyphClassDef(stmt) => Statement::GdefClassDef(stmt),
291 GdefStatement::LigatureCaretByIndex(stmt) => Statement::GdefLigatureCaretByIndex(stmt),
292 GdefStatement::LigatureCaretByPos(stmt) => Statement::GdefLigatureCaretByPos(stmt),
293 GdefStatement::Comment(cmt) => Statement::Comment(cmt),
294 }
296 }
297}
298impl TryFrom<Statement> for GdefStatement {
299 type Error = crate::Error;
300 fn try_from(value: Statement) -> Result<Self, Self::Error> {
301 match value {
302 Statement::GdefAttach(stmt) => Ok(GdefStatement::Attach(stmt)),
303 Statement::GdefClassDef(stmt) => Ok(GdefStatement::GlyphClassDef(stmt)),
304 Statement::GdefLigatureCaretByIndex(stmt) => {
305 Ok(GdefStatement::LigatureCaretByIndex(stmt))
306 }
307 Statement::GdefLigatureCaretByPos(stmt) => Ok(GdefStatement::LigatureCaretByPos(stmt)),
308 Statement::Comment(cmt) => Ok(GdefStatement::Comment(cmt)),
309 _ => Err(crate::Error::CannotConvert),
311 }
312 }
313}
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use crate::{GlyphClass, GlyphName};
318
319 #[test]
320 fn test_roundtrip_ligature_caret_by_index() {
321 const FEA: &str = "table GDEF { LigatureCaretByIndex f_f_i 2 3; } GDEF;";
322 let (parsed, _) = fea_rs::parse::parse_string(FEA);
323 let gdef_table = parsed
324 .root()
325 .iter_children()
326 .find_map(fea_rs::typed::GdefTable::cast)
327 .unwrap();
328 let ligature_caret = gdef_table
329 .node()
330 .iter_children()
331 .find_map(fea_rs::typed::GdefLigatureCaret::cast)
332 .unwrap();
333 let stmt = LigatureCaretByIndexStatement::from(ligature_caret);
334 assert_eq!(stmt.glyphs.as_fea(""), "f_f_i");
335 assert_eq!(stmt.carets, vec![2, 3]);
336 assert_eq!(stmt.as_fea(""), "LigatureCaretByIndex f_f_i 2 3;");
337 }
338
339 #[test]
340 fn test_roundtrip_ligature_caret_by_pos() {
341 const FEA: &str = "table GDEF { LigatureCaretByPos f_f_i 200 400; } GDEF;";
342 let (parsed, _) = fea_rs::parse::parse_string(FEA);
343 let gdef_table = parsed
344 .root()
345 .iter_children()
346 .find_map(fea_rs::typed::GdefTable::cast)
347 .unwrap();
348 let ligature_caret = gdef_table
349 .node()
350 .iter_children()
351 .find_map(fea_rs::typed::GdefLigatureCaret::cast)
352 .unwrap();
353 let stmt = LigatureCaretByPosStatement::from(ligature_caret);
354 assert_eq!(stmt.glyphs.as_fea(""), "f_f_i");
355 assert_eq!(stmt.carets, vec![200, 400]);
356 assert_eq!(stmt.as_fea(""), "LigatureCaretByPos f_f_i 200 400;");
357 }
358
359 #[test]
360 fn test_generate_ligature_caret_by_index() {
361 let stmt = LigatureCaretByIndexStatement::new(
362 GlyphContainer::GlyphName(GlyphName::new("f_f_i")),
363 vec![2, 3],
364 0..0,
365 );
366 assert_eq!(stmt.as_fea(""), "LigatureCaretByIndex f_f_i 2 3;");
367 }
368
369 #[test]
370 fn test_generate_ligature_caret_by_pos() {
371 let stmt = LigatureCaretByPosStatement::new(
372 GlyphContainer::GlyphClass(GlyphClass::new(
373 vec![
374 GlyphContainer::GlyphName(GlyphName::new("f_f_i")),
375 GlyphContainer::GlyphName(GlyphName::new("f_f_l")),
376 ],
377 0..0,
378 )),
379 vec![200, 400],
380 0..0,
381 );
382 assert_eq!(stmt.as_fea(""), "LigatureCaretByPos [f_f_i f_f_l] 200 400;");
383 }
384
385 #[test]
386 fn test_roundtrip_attach() {
387 const FEA: &str = "table GDEF { Attach [a e o] 1 2; } GDEF;";
388 let (parsed, _) = fea_rs::parse::parse_string(FEA);
389 let gdef_table = parsed
390 .root()
391 .iter_children()
392 .find_map(fea_rs::typed::GdefTable::cast)
393 .unwrap();
394 let attach = gdef_table
395 .node()
396 .iter_children()
397 .find_map(fea_rs::typed::GdefAttach::cast)
398 .unwrap();
399 let stmt = AttachStatement::from(attach);
400 assert_eq!(stmt.as_fea(""), "Attach [a e o] 1 2;");
401 }
402
403 #[test]
404 fn test_generation_attach() {
405 let stmt = AttachStatement::new(
406 GlyphContainer::GlyphName(GlyphName::new("acutecomb")),
407 vec![3, 5, 7],
408 0..0,
409 );
410 assert_eq!(stmt.as_fea(""), "Attach acutecomb 3 5 7;");
411 }
412
413 #[test]
414 fn test_roundtrip_glyphclassdef() {
415 const FEA: &str = "table GDEF { GlyphClassDef [a b c], [f_f_i], [acute grave], ; } GDEF;";
416 let (parsed, _) = fea_rs::parse::parse_string(FEA);
417 let gdef_table = parsed
418 .root()
419 .iter_children()
420 .find_map(fea_rs::typed::GdefTable::cast)
421 .unwrap();
422 let class_def = gdef_table
423 .node()
424 .iter_children()
425 .find_map(fea_rs::typed::GdefClassDef::cast)
426 .unwrap();
427
428 let stmt = GlyphClassDefStatement::from(class_def);
429 assert!(stmt.base_glyphs.is_some());
430 assert!(stmt.ligature_glyphs.is_some());
431 assert!(stmt.mark_glyphs.is_some());
432 assert!(stmt.component_glyphs.is_none());
433 assert_eq!(
434 stmt.as_fea(""),
435 "GlyphClassDef [a b c], [f_f_i], [acute grave], ;"
436 );
437 }
438
439 #[test]
440 fn test_generation_glyphclassdef() {
441 let stmt = GlyphClassDefStatement::new(
442 Some(GlyphContainer::GlyphClass(GlyphClass::new(
443 vec![GlyphContainer::GlyphName(GlyphName::new("a"))],
444 0..0,
445 ))),
446 None,
447 Some(GlyphContainer::GlyphClass(GlyphClass::new(
448 vec![GlyphContainer::GlyphName(GlyphName::new("acutecomb"))],
449 0..0,
450 ))),
451 None,
452 0..0,
453 );
454 assert_eq!(stmt.as_fea(""), "GlyphClassDef [a], , [acutecomb], ;");
455 }
456
457 #[test]
458 fn test_roundtrip_glyphclassdef_named_classes() {
459 const FEA: &str =
460 "table GDEF { GlyphClassDef @BASE, @LIGATURES, @MARKS, @COMPONENT; } GDEF;";
461 let (parsed, _) = fea_rs::parse::parse_string(FEA);
462 let gdef_table = parsed
463 .root()
464 .iter_children()
465 .find_map(fea_rs::typed::GdefTable::cast)
466 .unwrap();
467 let class_def = gdef_table
468 .node()
469 .iter_children()
470 .find_map(fea_rs::typed::GdefClassDef::cast)
471 .unwrap();
472
473 let stmt = GlyphClassDefStatement::from(class_def);
474 assert!(stmt.base_glyphs.is_some());
475 assert!(stmt.ligature_glyphs.is_some());
476 assert!(stmt.mark_glyphs.is_some());
477 assert!(stmt.component_glyphs.is_some());
478 assert_eq!(
479 stmt.as_fea(""),
480 "GlyphClassDef @BASE, @LIGATURES, @MARKS, @COMPONENT;"
481 );
482 }
483}