Skip to main content

adobe_font_metrics/
records.rs

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}