1use std::borrow::Cow;
2
3use crate::{
4 BBox, CharacterCode, CharacterMetric, Composite, CompositeComponent, Direction,
5 KerningOperands, KerningPair, Ligature, ParseError, RecordContext, SourceRecord, TrackKern,
6 Vector,
7};
8
9pub(crate) fn malformed(line: usize, keyword: &'static str, reason: &'static str) -> ParseError {
10 ParseError::MalformedRecord {
11 line,
12 keyword,
13 reason,
14 }
15}
16
17pub(crate) fn keyword(line: &str) -> (&str, &str) {
18 let line = line.trim();
19 line.find(|c: char| c.is_ascii_whitespace())
20 .map_or((line, ""), |i| (&line[..i], line[i..].trim()))
21}
22
23pub(crate) fn number(s: &str, field: &'static str, line: usize) -> Result<f32, ParseError> {
24 let value = s.trim();
25 let result = value.parse::<f32>().ok().filter(|value| value.is_finite());
26 result.ok_or_else(|| ParseError::InvalidNumber {
27 line,
28 field,
29 value: value.to_owned(),
30 })
31}
32
33pub(crate) fn integer<T: std::str::FromStr>(
34 s: &str,
35 field: &'static str,
36 line: usize,
37) -> Result<T, ParseError> {
38 s.trim()
39 .parse()
40 .map_err(|_error| ParseError::InvalidNumber {
41 line,
42 field,
43 value: s.trim().to_owned(),
44 })
45}
46
47pub(crate) fn boolean(s: &str, field: &'static str, line: usize) -> Result<bool, ParseError> {
48 match s.trim() {
49 "true" => Ok(true),
50 "false" => Ok(false),
51 _ => Err(malformed(line, field, "expected `true` or `false`")),
52 }
53}
54
55pub(crate) fn operands<'a, const N: usize>(
56 s: &'a str,
57 field: &'static str,
58 line: usize,
59) -> Result<[&'a str; N], ParseError> {
60 let mut tokens = s.split_ascii_whitespace();
61 let mut values = [""; N];
62 for value in &mut values {
63 *value = tokens
64 .next()
65 .ok_or_else(|| malformed(line, field, "missing operand"))?;
66 }
67 if tokens.next().is_some() {
68 return Err(malformed(line, field, "too many operands"));
69 }
70 Ok(values)
71}
72
73pub(crate) fn vector(s: &str, field: &'static str, line: usize) -> Result<Vector, ParseError> {
74 let [x, y] = operands(s, field, line)?;
75 Ok(Vector {
76 x: number(x, field, line)?,
77 y: number(y, field, line)?,
78 })
79}
80
81pub(crate) fn bbox(s: &str, field: &'static str, line: usize) -> Result<BBox, ParseError> {
82 let [llx, lly, urx, ury] = operands(s, field, line)?;
83 Ok(BBox {
84 llx: number(llx, field, line)?,
85 lly: number(lly, field, line)?,
86 urx: number(urx, field, line)?,
87 ury: number(ury, field, line)?,
88 })
89}
90
91fn hex<'a>(s: &'a str, field: &'static str, line: usize) -> Result<Cow<'a, str>, ParseError> {
92 let digits = s.strip_prefix('<').and_then(|s| s.strip_suffix('>'));
93 match digits {
94 Some(digits) if !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_hexdigit()) => {
95 Ok(Cow::Borrowed(digits))
96 }
97 _ => Err(ParseError::InvalidNumber {
98 line,
99 field,
100 value: s.to_owned(),
101 }),
102 }
103}
104
105pub(crate) fn source_record<'a>(
106 records: &mut Vec<SourceRecord<'a>>,
107 line: usize,
108 context: RecordContext,
109 key: &'a str,
110 value: &'a str,
111) {
112 records.push(SourceRecord {
113 line,
114 context,
115 keyword: Cow::Borrowed(key),
116 value: Cow::Borrowed(value),
117 });
118}
119
120pub(crate) fn character<'a>(
121 s: &'a str,
122 line: usize,
123 index: usize,
124 records: &mut Vec<SourceRecord<'a>>,
125) -> Result<CharacterMetric<'a>, ParseError> {
126 let mut code = None;
127 let mut name = None;
128 let mut advances = [None; 2];
129 let mut bounds = None;
130 let mut v_vector = None;
131 let mut ligatures = Vec::new();
132 for segment in s.split(';').map(str::trim).filter(|s| !s.is_empty()) {
133 let (key, value) = keyword(segment);
134 match key {
135 "C" => {
136 let value: i32 = integer(value, "C", line)?;
137 if value < -1 {
138 return Err(malformed(
139 line,
140 "C",
141 "character code must be -1 or nonnegative",
142 ));
143 }
144 if code.replace(CharacterCode::Decimal(value)).is_some() {
145 return Err(malformed(line, "C", "duplicate character code"));
146 }
147 }
148 "CH" => {
149 let value = CharacterCode::Hex(hex(value, "CH", line)?);
150 if code.replace(value).is_some() {
151 return Err(malformed(line, "CH", "duplicate character code"));
152 }
153 }
154 "N" => {
155 let [value] = operands(value, "N", line)?;
156 name = Some(Cow::Borrowed(value));
157 }
158 "WX" | "W0X" => {
159 advances[0] = Some(Vector {
160 x: number(value, "WX", line)?,
161 y: 0.0,
162 });
163 }
164 "WY" | "W0Y" => {
165 advances[0] = Some(Vector {
166 x: 0.0,
167 y: number(value, "WY", line)?,
168 });
169 }
170 "W1X" => {
171 advances[1] = Some(Vector {
172 x: number(value, "W1X", line)?,
173 y: 0.0,
174 });
175 }
176 "W1Y" => {
177 advances[1] = Some(Vector {
178 x: 0.0,
179 y: number(value, "W1Y", line)?,
180 });
181 }
182 "W" | "W0" => advances[0] = Some(vector(value, "W", line)?),
183 "W1" => advances[1] = Some(vector(value, "W1", line)?),
184 "B" => bounds = Some(bbox(value, "B", line)?),
185 "VV" => v_vector = Some(vector(value, "VV", line)?),
186 "L" => {
187 let [successor, ligature] = operands(value, "L", line)?;
188 ligatures.push(Ligature {
189 successor: Cow::Borrowed(successor),
190 ligature: Cow::Borrowed(ligature),
191 });
192 }
193 _ if known_record(key) => {
194 return Err(malformed(
195 line,
196 "section",
197 "modeled record in a character record",
198 ));
199 }
200 _ => source_record(records, line, RecordContext::Character(index), key, value),
201 }
202 }
203 Ok(CharacterMetric {
204 code: code.ok_or_else(|| malformed(line, "C", "character record requires C or CH"))?,
205 name,
206 advances,
207 bbox: bounds,
208 v_vector,
209 ligatures: Cow::Owned(ligatures),
210 })
211}
212
213pub(crate) fn pair<'a>(
214 key: &str,
215 value: &'a str,
216 line: usize,
217 direction: Direction,
218) -> Result<KerningPair<'a>, ParseError> {
219 let (operands, adjustment) = match key {
220 "KP" => {
221 let [left, right, x, y] = operands(value, "KP", line)?;
222 (
223 KerningOperands::Names {
224 left: Cow::Borrowed(left),
225 right: Cow::Borrowed(right),
226 },
227 Vector {
228 x: number(x, "KP", line)?,
229 y: number(y, "KP", line)?,
230 },
231 )
232 }
233 "KPH" => {
234 let [left, right, x, y] = operands(value, "KPH", line)?;
235 (
236 KerningOperands::Hex {
237 left: hex(left, "KPH", line)?,
238 right: hex(right, "KPH", line)?,
239 },
240 Vector {
241 x: number(x, "KPH", line)?,
242 y: number(y, "KPH", line)?,
243 },
244 )
245 }
246 "KPX" => {
247 let [left, right, x] = operands(value, "KPX", line)?;
248 (
249 KerningOperands::Names {
250 left: Cow::Borrowed(left),
251 right: Cow::Borrowed(right),
252 },
253 Vector {
254 x: number(x, "KPX", line)?,
255 y: 0.0,
256 },
257 )
258 }
259 "KPY" => {
260 let [left, right, y] = operands(value, "KPY", line)?;
261 (
262 KerningOperands::Names {
263 left: Cow::Borrowed(left),
264 right: Cow::Borrowed(right),
265 },
266 Vector {
267 x: 0.0,
268 y: number(y, "KPY", line)?,
269 },
270 )
271 }
272 _ => return Err(malformed(line, "StartKernPairs", "expected a pair record")),
273 };
274 Ok(KerningPair {
275 operands,
276 adjustment,
277 direction,
278 })
279}
280
281pub(crate) fn track(value: &str, line: usize) -> Result<TrackKern, ParseError> {
282 let [degree, min_point_size, min_kern, max_point_size, max_kern] =
283 operands(value, "TrackKern", line)?;
284 let track = TrackKern {
285 degree: integer(degree, "TrackKern", line)?,
286 min_point_size: number(min_point_size, "TrackKern", line)?,
287 min_kern: number(min_kern, "TrackKern", line)?,
288 max_point_size: number(max_point_size, "TrackKern", line)?,
289 max_kern: number(max_kern, "TrackKern", line)?,
290 };
291 if track.min_point_size > track.max_point_size {
292 return Err(malformed(
293 line,
294 "TrackKern",
295 "minimum point size exceeds maximum",
296 ));
297 }
298 Ok(track)
299}
300
301pub(crate) fn composite<'a>(
302 s: &'a str,
303 line: usize,
304 index: usize,
305 records: &mut Vec<SourceRecord<'a>>,
306) -> Result<Composite<'a>, ParseError> {
307 let mut segments = s.split(';').map(str::trim).filter(|s| !s.is_empty());
308 let (key, value) = keyword(
309 segments
310 .next()
311 .ok_or_else(|| malformed(line, "CC", "missing composite record"))?,
312 );
313 if key != "CC" {
314 return Err(malformed(line, "CC", "expected composite header"));
315 }
316 let [name, count] = operands(value, "CC", line)?;
317 let count: usize = integer(count, "CC", line)?;
318 let mut components = Vec::new();
319 for segment in segments {
320 let (key, value) = keyword(segment);
321 if key == "PCC" {
322 let [name, x, y] = operands(value, "PCC", line)?;
323 components.push(CompositeComponent {
324 name: Cow::Borrowed(name),
325 offset: Vector {
326 x: number(x, "PCC", line)?,
327 y: number(y, "PCC", line)?,
328 },
329 });
330 } else if known_record(key) {
331 return Err(malformed(
332 line,
333 "section",
334 "modeled record in a composite record",
335 ));
336 } else {
337 source_record(records, line, RecordContext::Composite(index), key, value);
338 }
339 }
340 if components.len() != count {
341 return Err(malformed(
342 line,
343 "CC",
344 "component count does not match declaration",
345 ));
346 }
347 Ok(Composite {
348 name: Cow::Borrowed(name),
349 components: Cow::Owned(components),
350 })
351}
352
353pub(crate) fn known_record(key: &str) -> bool {
354 matches!(
355 key,
356 "StartFontMetrics"
357 | "EndFontMetrics"
358 | "StartDirection"
359 | "EndDirection"
360 | "StartCharMetrics"
361 | "EndCharMetrics"
362 | "StartKernData"
363 | "EndKernData"
364 | "StartKernPairs"
365 | "StartKernPairs0"
366 | "StartKernPairs1"
367 | "EndKernPairs"
368 | "StartTrackKern"
369 | "EndTrackKern"
370 | "StartComposites"
371 | "EndComposites"
372 | "FontName"
373 | "FontBBox"
374 | "MetricsSets"
375 | "FullName"
376 | "FamilyName"
377 | "Weight"
378 | "Version"
379 | "Notice"
380 | "EncodingScheme"
381 | "CharacterSet"
382 | "MappingScheme"
383 | "EscChar"
384 | "Characters"
385 | "IsBaseFont"
386 | "IsCIDFont"
387 | "VVector"
388 | "IsFixedV"
389 | "CapHeight"
390 | "XHeight"
391 | "Ascender"
392 | "Descender"
393 | "StdHW"
394 | "StdVW"
395 | "UnderlinePosition"
396 | "UnderlineThickness"
397 | "ItalicAngle"
398 | "CharWidth"
399 | "IsFixedPitch"
400 | "C"
401 | "CH"
402 | "N"
403 | "B"
404 | "WX"
405 | "WY"
406 | "W0X"
407 | "W0Y"
408 | "W1X"
409 | "W1Y"
410 | "W"
411 | "W0"
412 | "W1"
413 | "VV"
414 | "L"
415 | "KP"
416 | "KPX"
417 | "KPY"
418 | "KPH"
419 | "TrackKern"
420 | "CC"
421 | "PCC"
422 )
423}