Skip to main content

fea_rs_ast/
os2.rs

1use std::ops::Range;
2
3use fea_rs::{
4    NodeOrToken,
5    typed::{AstNode as _, Number, Os2TableItem},
6};
7use smol_str::SmolStr;
8
9use crate::{AsFea, Comment, FeaTable, Table};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Os2Field {
13    Comment(Comment),
14    FSType(u8),
15    Panose(Vec<u8>),
16    UnicodeRange(Vec<u16>),
17    CodePageRange(Vec<u16>),
18    TypoAscender(i16),
19    TypoDescender(i16),
20    TypoLineGap(i16),
21    WinAscent(u16),
22    WinDescent(u16),
23    XHeight(i16),
24    CapHeight(i16),
25    WeightClass(u16),
26    WidthClass(u16),
27
28    Vendor(SmolStr),
29}
30impl AsFea for Os2Field {
31    fn as_fea(&self, indent: &str) -> String {
32        match self {
33            Os2Field::Comment(cmt) => cmt.as_fea(indent),
34            Os2Field::FSType(x) => format!("{}FSType {};", indent, x),
35            Os2Field::WeightClass(x) => format!("{}WeightClass {};", indent, x),
36            Os2Field::WidthClass(x) => format!("{}WidthClass {};", indent, x),
37            Os2Field::Panose(vals) => format!(
38                "{}Panose {};",
39                indent,
40                vals.iter()
41                    .map(|v| v.to_string())
42                    .collect::<Vec<_>>()
43                    .join(" ")
44            ),
45            Os2Field::Vendor(x) => format!("{}Vendor \"{}\";", indent, x),
46            Os2Field::TypoAscender(x) => format!("{}TypoAscender {};", indent, x),
47            Os2Field::TypoDescender(x) => format!("{}TypoDescender {};", indent, x),
48            Os2Field::TypoLineGap(x) => format!("{}TypoLineGap {};", indent, x),
49            Os2Field::UnicodeRange(items) => format!(
50                "{}UnicodeRange {};",
51                indent,
52                items
53                    .iter()
54                    .map(|v| v.to_string())
55                    .collect::<Vec<_>>()
56                    .join(" ")
57            ),
58            Os2Field::CodePageRange(items) => format!(
59                "{}CodePageRange {};",
60                indent,
61                items
62                    .iter()
63                    .map(|v| v.to_string())
64                    .collect::<Vec<_>>()
65                    .join(" ")
66            ),
67            Os2Field::WinAscent(x) => format!("{}winAscent {};", indent, x),
68            Os2Field::WinDescent(x) => format!("{}winDescent {};", indent, x),
69            Os2Field::XHeight(x) => format!("{}XHeight {};", indent, x),
70            Os2Field::CapHeight(x) => format!("{}CapHeight {};", indent, x),
71        }
72    }
73}
74
75impl From<Os2TableItem> for Os2Statement {
76    fn from(val: Os2TableItem) -> Self {
77        match val {
78            Os2TableItem::Number(number_record) => {
79                let keyword = number_record
80                    .iter()
81                    .next()
82                    .and_then(|t| t.as_token())
83                    .unwrap();
84                let number = number_record
85                    .iter()
86                    .find_map(Number::cast)
87                    .unwrap()
88                    .text()
89                    .parse::<i64>()
90                    .unwrap();
91                let field = match keyword.as_str() {
92                    "FSType" => Os2Field::FSType(number as u8),
93                    "WeightClass" => Os2Field::WeightClass(number as u16),
94                    "WidthClass" => Os2Field::WidthClass(number as u16),
95                    _ => panic!("Unknown OS/2 number field: {}", keyword.as_str()),
96                };
97                Os2Statement {
98                    field,
99                    location: number_record.range(),
100                }
101            }
102            Os2TableItem::NumberList(os2_number_list) => {
103                let keyword = os2_number_list
104                    .iter()
105                    .next()
106                    .and_then(|t| t.as_token())
107                    .unwrap();
108                let numbers: Vec<u16> = os2_number_list
109                    .iter()
110                    .filter_map(Number::cast)
111                    .map(|n| n.text().parse::<u16>().unwrap())
112                    .collect();
113                let field = match keyword.as_str() {
114                    "Panose" => Os2Field::Panose(numbers.iter().map(|&n| n as u8).collect()),
115                    "UnicodeRange" => Os2Field::UnicodeRange(numbers),
116                    "CodePageRange" => Os2Field::CodePageRange(numbers),
117                    _ => panic!("Unknown OS/2 number list field: {}", keyword.as_str()),
118                };
119                Os2Statement {
120                    field,
121                    location: os2_number_list.range(),
122                }
123            }
124            Os2TableItem::Metric(metric_record) => {
125                let keyword = metric_record
126                    .iter()
127                    .next()
128                    .and_then(|t| t.as_token())
129                    .unwrap();
130                let metric = metric_record
131                    .iter()
132                    .find_map(fea_rs::typed::Metric::cast)
133                    .unwrap();
134
135                let value = match metric {
136                    fea_rs::typed::Metric::Scalar(number) => number.text().parse::<i16>().unwrap(),
137                    _ => unimplemented!(),
138                };
139                let field = match keyword.as_str() {
140                    "TypoAscender" => Os2Field::TypoAscender(value),
141                    "TypoDescender" => Os2Field::TypoDescender(value),
142                    "TypoLineGap" => Os2Field::TypoLineGap(value),
143                    "XHeight" => Os2Field::XHeight(value),
144                    "CapHeight" => Os2Field::CapHeight(value),
145                    "winAscent" => Os2Field::WinAscent(value as u16),
146                    "winDescent" => Os2Field::WinDescent(value as u16),
147                    _ => panic!("Unknown OS/2 metric field: {}", keyword.as_str()),
148                };
149                Os2Statement {
150                    field,
151                    location: metric_record.range(),
152                }
153            }
154            Os2TableItem::Vendor(vendor_record) => {
155                let vendor = vendor_record
156                    .iter()
157                    .find(|t| t.kind() == fea_rs::Kind::String)
158                    .and_then(|t| t.as_token())
159                    .unwrap();
160                Os2Statement {
161                    field: Os2Field::Vendor(vendor.text.trim_matches('"').to_string().into()),
162                    location: vendor_record.range(),
163                }
164            }
165            Os2TableItem::FamilyClass(_os2_family_class) => todo!(),
166        }
167    }
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct Os2Statement {
172    field: Os2Field,
173    location: Range<usize>,
174}
175impl AsFea for Os2Statement {
176    fn as_fea(&self, indent: &str) -> String {
177        self.field.as_fea(indent)
178    }
179}
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct Os2;
182impl FeaTable for Os2 {
183    type Statement = Os2Statement;
184    const TAG: &'static str = "OS/2";
185    type FeaRsTable = fea_rs::typed::Os2Table;
186    #[allow(clippy::manual_map)]
187    fn to_statement(child: &NodeOrToken) -> Option<Os2Statement> {
188        if child.kind() == fea_rs::Kind::Comment {
189            Some(Os2Statement {
190                field: Os2Field::Comment(Comment::from(child.token_text().unwrap())),
191                location: child.range(),
192            })
193        } else if let Some(fr) = fea_rs::typed::Os2TableItem::cast(child) {
194            Some(fr.into())
195        } else {
196            None
197        }
198    }
199}
200
201impl From<fea_rs::typed::Os2Table> for Table<Os2> {
202    fn from(val: fea_rs::typed::Os2Table) -> Self {
203        Self {
204            statements: Os2::statements_from_node(val.node()),
205        }
206    }
207}