1use crate::{AsFea, FeaTable, Table};
6use fea_rs::{
7 Kind, NodeOrToken,
8 typed::{AstNode as _, ScriptRecord},
9};
10
11#[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 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}