Skip to main content

fea_rs_ast/
base.rs

1// Unfortunately Python parses this completely differently to fea_rs
2// We're going to do a halfway thing with more structure and typing
3// on the axis, but still keeping each axis as the main "statement".
4
5use crate::{AsFea, FeaTable, Table};
6use fea_rs::{
7    Kind, NodeOrToken,
8    typed::{AstNode as _, ScriptRecord},
9};
10
11/// A min/max height record for a particular script and language
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct MinMax {
14    pub script: String,
15    pub language: String,
16    pub min: i16,
17    pub max: i16,
18}
19
20impl From<fea_rs::typed::BaseMinMax> for MinMax {
21    fn from(value: fea_rs::typed::BaseMinMax) -> Self {
22        let mut iter = value.iter();
23        let script = iter
24            .find(|t| t.kind() == Kind::Tag)
25            .unwrap()
26            .token_text()
27            .unwrap_or_default()
28            .to_string();
29        let language = iter
30            .find(|t| t.kind() == Kind::Tag)
31            .unwrap()
32            .token_text()
33            .unwrap_or_default()
34            .to_string();
35        let min = iter
36            .find(|t| t.kind() == Kind::Number)
37            .and_then(|t| t.token_text())
38            .and_then(|s| s.parse::<i16>().ok())
39            .unwrap_or_default();
40        let max = iter
41            .find(|t| t.kind() == Kind::Number)
42            .and_then(|t| t.token_text())
43            .and_then(|s| s.parse::<i16>().ok())
44            .unwrap_or_default();
45        Self {
46            script,
47            language,
48            min,
49            max,
50        }
51    }
52}
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct BaseScript {
55    pub script_tag: String,
56    pub default_baseline_tag: String,
57    pub coordinates: Vec<i16>,
58}
59
60impl From<fea_rs::typed::ScriptRecord> for BaseScript {
61    fn from(value: fea_rs::typed::ScriptRecord) -> Self {
62        // Script and tag are the first two tokens
63        let script_tag = value
64            .iter()
65            .find(|t| t.kind() == Kind::Tag)
66            .unwrap()
67            .token_text()
68            .unwrap()
69            .to_string();
70        let default_baseline_tag = value
71            .iter()
72            .filter(|t| t.kind() == Kind::Tag)
73            .nth(1)
74            .unwrap()
75            .token_text()
76            .unwrap()
77            .to_string();
78        let coordinates = value
79            .iter()
80            .filter(|t| t.kind() == Kind::Number)
81            .map(|t| {
82                t.token_text()
83                    .unwrap_or_default()
84                    .parse::<i16>()
85                    .unwrap_or_default()
86            })
87            .collect();
88        Self {
89            script_tag,
90            default_baseline_tag,
91            coordinates,
92        }
93    }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct BaseAxis {
98    pub bases: Vec<String>,
99    pub scripts: Vec<BaseScript>,
100    pub vertical: bool,
101    pub minmax: Vec<MinMax>,
102}
103
104impl BaseAxis {
105    fn new() -> Self {
106        Self {
107            bases: Vec::new(),
108            scripts: Vec::new(),
109            vertical: false,
110            minmax: Vec::new(),
111        }
112    }
113}
114
115impl AsFea for BaseAxis {
116    fn as_fea(&self, indent: &str) -> String {
117        let direction = if self.vertical { "Vert" } else { "Horiz" };
118        let scripts: Vec<String> = self
119            .scripts
120            .iter()
121            .map(|a| {
122                format!(
123                    "{:4} {} {}",
124                    a.script_tag,
125                    a.default_baseline_tag,
126                    a.coordinates
127                        .iter()
128                        .map(|c| c.to_string())
129                        .collect::<Vec<_>>()
130                        .join(" ")
131                )
132            })
133            .collect();
134        let minmaxes: Vec<String> = self
135            .minmax
136            .iter()
137            .map(|a| {
138                format!(
139                    "\n{}{}Axis.MinMax {:4} {:4} {}, {};",
140                    indent, direction, a.script, a.language, a.min, a.max
141                )
142            })
143            .collect();
144        format!(
145            "{}Axis.BaseTagList {};\n{}{}Axis.BaseScriptList {};{}\n",
146            direction,
147            self.bases.join(" "),
148            indent,
149            direction,
150            scripts.join(", "),
151            minmaxes.join("")
152        )
153    }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
157pub struct Base;
158impl FeaTable for Base {
159    type Statement = BaseAxis;
160    const TAG: &'static str = "BASE";
161    type FeaRsTable = fea_rs::typed::BaseTable;
162    fn statements_from_node(node: &fea_rs::Node) -> Vec<Self::Statement> {
163        let mut axes = Vec::new();
164        for child in node.iter_children() {
165            if let Some(taglist) = fea_rs::typed::BaseTagList::cast(child) {
166                let mut axis = BaseAxis::new();
167                axis.vertical = match taglist.iter().next().map(|t| t.kind()) {
168                    Some(Kind::HorizAxisBaseTagListKw) => false,
169                    Some(Kind::VertAxisBaseTagListKw) => true,
170                    other => panic!("unexpected token in BaseTagList {other:?}"),
171                };
172                axis.bases = taglist
173                    .iter()
174                    .skip(1)
175                    .take_while(|t| t.kind() != Kind::Semi)
176                    .filter(|t| t.kind() == Kind::Tag)
177                    .map(|t| t.token_text().unwrap_or_default().to_string())
178                    .collect();
179                axes.push(axis);
180            } else if let Some(scriptlist) = fea_rs::typed::BaseScriptList::cast(child) {
181                let script_records = scriptlist
182                    .iter()
183                    .skip(1)
184                    .take_while(|t| t.kind() != Kind::Semi)
185                    .filter_map(ScriptRecord::cast)
186                    .map(BaseScript::from)
187                    .collect::<Vec<_>>();
188                if let Some(last_axis) = axes.last_mut() {
189                    last_axis.scripts = script_records;
190                } else {
191                    panic!("BaseScriptList found before BaseTagList");
192                }
193            } else if let Some(minmax_stmt) = fea_rs::typed::BaseMinMax::cast(child) {
194                let minmax = MinMax::from(minmax_stmt);
195
196                if let Some(last_axis) = axes.last_mut() {
197                    last_axis.minmax.push(minmax);
198                } else {
199                    panic!("BaseAxisMinMax found before BaseTagList");
200                }
201            }
202        }
203        axes
204    }
205    fn to_statement(_child: &NodeOrToken) -> Option<Self::Statement> {
206        None
207    }
208}
209
210impl From<fea_rs::typed::BaseTable> for Table<Base> {
211    fn from(val: fea_rs::typed::BaseTable) -> Self {
212        Self {
213            statements: Base::statements_from_node(val.node()),
214        }
215    }
216}