1use std::borrow::Cow;
2
3use crate::records::{
4 self, bbox, boolean, integer, keyword, malformed, number, source_record, vector,
5};
6use crate::{Direction, DirectionMetrics, FontMetrics, MetricsSets, ParseError, RecordContext};
7
8#[derive(Debug, Clone, Copy)]
9struct Count {
10 expected: usize,
11 seen: usize,
12 keyword: &'static str,
13}
14
15impl Count {
16 fn new(
17 value: &str,
18 keyword: &'static str,
19 line: usize,
20 input_len: usize,
21 ) -> Result<Self, ParseError> {
22 let expected = integer(value, keyword, line)?;
23 if expected > input_len {
25 return Err(malformed(
26 line,
27 keyword,
28 "declared count exceeds input size",
29 ));
30 }
31 Ok(Self {
32 expected,
33 seen: 0,
34 keyword,
35 })
36 }
37 fn add(&mut self, line: usize) -> Result<(), ParseError> {
38 self.seen += 1;
39 if self.seen > self.expected {
40 return Err(malformed(line, self.keyword, "more records than declared"));
41 }
42 Ok(())
43 }
44 fn close(self, line: usize) -> Result<(), ParseError> {
45 if self.seen != self.expected {
46 return Err(malformed(
47 line,
48 self.keyword,
49 "record count does not match declaration",
50 ));
51 }
52 Ok(())
53 }
54}
55
56#[derive(Debug, Clone, Copy)]
57enum Section {
58 Font,
59 Direction(MetricsSets),
60 Characters(Count),
61 Pairs(Direction, Count),
62 Tracks(Count),
63 Composites(Count),
64}
65
66struct Reader<'a> {
67 font: FontMetrics<'a>,
68 section: Section,
69 header_seen: bool,
70 bbox_seen: bool,
71 kern_data: bool,
72 finished: bool,
73 input_len: usize,
74 direction_lines: [usize; 2],
75 vector_line: usize,
76}
77
78impl<'a> Reader<'a> {
79 fn new(input_len: usize) -> Self {
80 Self {
81 font: FontMetrics::default(),
82 section: Section::Font,
83 header_seen: false,
84 bbox_seen: false,
85 kern_data: false,
86 finished: false,
87 input_len,
88 direction_lines: [1; 2],
89 vector_line: 1,
90 }
91 }
92
93 fn context(&self) -> RecordContext {
94 match self.section {
95 Section::Font if self.kern_data => RecordContext::KernData,
96 Section::Font => RecordContext::Font,
97 Section::Direction(direction) => RecordContext::Direction(direction),
98 Section::Characters(_) => RecordContext::CharacterMetrics,
99 Section::Pairs(direction, _) => RecordContext::KernPairs(direction),
100 Section::Tracks(_) => RecordContext::TrackKern,
101 Section::Composites(_) => RecordContext::Composites,
102 }
103 }
104
105 fn retain(&mut self, line: usize, key: &'a str, value: &'a str) {
106 let context = self.context();
107 source_record(self.font.source_records.to_mut(), line, context, key, value);
108 }
109
110 fn line(&mut self, raw: &'a str, line: usize) -> Result<(), ParseError> {
111 let text = raw.trim();
112 if text.is_empty() {
113 return Ok(());
114 }
115 if self.finished {
116 return Err(malformed(
117 line,
118 "EndFontMetrics",
119 "unexpected trailing data",
120 ));
121 }
122 let (key, value) = keyword(text);
123 if key == "Comment" {
124 self.retain(line, key, value);
125 return Ok(());
126 }
127 if !self.header_seen {
128 if key != "StartFontMetrics" {
129 return Err(ParseError::MissingHeader { line });
130 }
131 if !value.split_once('.').is_some_and(|(major, minor)| {
132 major == "4" && !minor.is_empty() && minor.bytes().all(|b| b.is_ascii_digit())
133 }) {
134 return Err(ParseError::UnsupportedVersion {
135 line,
136 version: value.to_owned(),
137 });
138 }
139 self.header_seen = true;
140 self.font.afm_version = Cow::Borrowed(value);
141 return Ok(());
142 }
143 if self.control(key, value, line)? {
144 return Ok(());
145 }
146 match self.section {
147 Section::Font if !self.kern_data => {
148 if !self.global(key, value, line)?
149 && !self.direction_metric(key, value, line, MetricsSets::Zero)?
150 {
151 self.extra_or_misplaced(key, value, line)?;
152 }
153 }
154 Section::Direction(selector) => {
155 if !self.direction_metric(key, value, line, selector)? {
156 self.extra_or_misplaced(key, value, line)?;
157 }
158 }
159 Section::Characters(_) => {
160 if character_line(text) {
161 let character = records::character(
162 text,
163 line,
164 self.font.character_metrics.len(),
165 self.font.source_records.to_mut(),
166 )?;
167 self.font.character_metrics.to_mut().push(character);
168 if let Section::Characters(count) = &mut self.section {
169 count.add(line)?;
170 }
171 } else {
172 self.extra_or_misplaced(key, value, line)?;
173 }
174 }
175 Section::Pairs(direction, _) if matches!(key, "KP" | "KPX" | "KPY" | "KPH") => {
176 self.font
177 .kerning_pairs
178 .to_mut()
179 .push(records::pair(key, value, line, direction)?);
180 if let Section::Pairs(_, count) = &mut self.section {
181 count.add(line)?;
182 }
183 }
184 Section::Tracks(_) if key == "TrackKern" => {
185 self.font
186 .track_kerns
187 .to_mut()
188 .push(records::track(value, line)?);
189 if let Section::Tracks(count) = &mut self.section {
190 count.add(line)?;
191 }
192 }
193 Section::Composites(_) if key == "CC" => {
194 let composite = records::composite(
195 text,
196 line,
197 self.font.composites.len(),
198 self.font.source_records.to_mut(),
199 )?;
200 self.font.composites.to_mut().push(composite);
201 if let Section::Composites(count) = &mut self.section {
202 count.add(line)?;
203 }
204 }
205 _ => self.extra_or_misplaced(key, value, line)?,
206 }
207 Ok(())
208 }
209
210 fn extra_or_misplaced(
211 &mut self,
212 key: &'a str,
213 value: &'a str,
214 line: usize,
215 ) -> Result<(), ParseError> {
216 if records::known_record(key) {
217 return Err(malformed(
218 line,
219 "section",
220 "modeled record in an incompatible section",
221 ));
222 }
223 self.retain(line, key, value);
224 Ok(())
225 }
226
227 fn require_font_section(
228 &self,
229 key: &'static str,
230 line: usize,
231 allow_kern: bool,
232 ) -> Result<(), ParseError> {
233 if !matches!(self.section, Section::Font) || (!allow_kern && self.kern_data) {
234 return Err(malformed(line, key, "incompatible or unclosed section"));
235 }
236 Ok(())
237 }
238
239 fn control(&mut self, key: &str, value: &str, line: usize) -> Result<bool, ParseError> {
240 match key {
241 "StartFontMetrics" => {
242 return Err(malformed(line, "StartFontMetrics", "duplicate header"));
243 }
244 "EndFontMetrics" => {
245 self.require_font_section("EndFontMetrics", line, false)?;
246 records::operands::<0>(value, "EndFontMetrics", line)?;
247 self.finished = true;
248 }
249 "StartDirection" => {
250 self.require_font_section("StartDirection", line, false)?;
251 self.section = Section::Direction(selector(value, "StartDirection", line)?);
252 }
253 "EndDirection" => {
254 if !matches!(self.section, Section::Direction(_)) {
255 return Err(malformed(line, "EndDirection", "no open direction section"));
256 }
257 records::operands::<0>(value, "EndDirection", line)?;
258 self.section = Section::Font;
259 }
260 "StartCharMetrics" => {
261 self.require_font_section("StartCharMetrics", line, false)?;
262 self.section = Section::Characters(Count::new(
263 value,
264 "StartCharMetrics",
265 line,
266 self.input_len,
267 )?);
268 }
269 "EndCharMetrics" => {
270 let Section::Characters(count) = self.section else {
271 return Err(malformed(
272 line,
273 "EndCharMetrics",
274 "no open character section",
275 ));
276 };
277 records::operands::<0>(value, "EndCharMetrics", line)?;
278 count.close(line)?;
279 self.section = Section::Font;
280 }
281 "StartKernData" => {
282 self.require_font_section("StartKernData", line, false)?;
283 records::operands::<0>(value, "StartKernData", line)?;
284 self.kern_data = true;
285 }
286 "EndKernData" => {
287 self.require_font_section("EndKernData", line, true)?;
288 if !self.kern_data {
289 return Err(malformed(line, "EndKernData", "no open kerning container"));
290 }
291 records::operands::<0>(value, "EndKernData", line)?;
292 self.kern_data = false;
293 }
294 "StartKernPairs" | "StartKernPairs0" | "StartKernPairs1" => {
295 self.require_font_section("StartKernPairs", line, true)?;
296 let (direction, field) = match key {
297 "StartKernPairs0" => (Direction::Zero, "StartKernPairs0"),
298 "StartKernPairs1" => (Direction::One, "StartKernPairs1"),
299 _ => (Direction::Zero, "StartKernPairs"),
300 };
301 self.section =
302 Section::Pairs(direction, Count::new(value, field, line, self.input_len)?);
303 }
304 "EndKernPairs" => {
305 let Section::Pairs(_, count) = self.section else {
306 return Err(malformed(line, "EndKernPairs", "no open pair section"));
307 };
308 records::operands::<0>(value, "EndKernPairs", line)?;
309 count.close(line)?;
310 self.section = Section::Font;
311 }
312 "StartTrackKern" => {
313 self.require_font_section("StartTrackKern", line, true)?;
314 self.section =
315 Section::Tracks(Count::new(value, "StartTrackKern", line, self.input_len)?);
316 }
317 "EndTrackKern" => {
318 let Section::Tracks(count) = self.section else {
319 return Err(malformed(line, "EndTrackKern", "no open track section"));
320 };
321 records::operands::<0>(value, "EndTrackKern", line)?;
322 count.close(line)?;
323 self.section = Section::Font;
324 }
325 "StartComposites" => {
326 self.require_font_section("StartComposites", line, false)?;
327 self.section = Section::Composites(Count::new(
328 value,
329 "StartComposites",
330 line,
331 self.input_len,
332 )?);
333 }
334 "EndComposites" => {
335 let Section::Composites(count) = self.section else {
336 return Err(malformed(
337 line,
338 "EndComposites",
339 "no open composite section",
340 ));
341 };
342 records::operands::<0>(value, "EndComposites", line)?;
343 count.close(line)?;
344 self.section = Section::Font;
345 }
346 _ => return Ok(false),
347 }
348 Ok(true)
349 }
350
351 fn direction_metric(
352 &mut self,
353 key: &str,
354 value: &str,
355 line: usize,
356 selector: MetricsSets,
357 ) -> Result<bool, ParseError> {
358 let mut changes = DirectionMetrics::default();
359 match key {
360 "UnderlinePosition" => {
361 changes.underline_position = Some(number(value, "UnderlinePosition", line)?);
362 }
363 "UnderlineThickness" => {
364 changes.underline_thickness = Some(number(value, "UnderlineThickness", line)?);
365 }
366 "ItalicAngle" => changes.italic_angle = Some(number(value, "ItalicAngle", line)?),
367 "CharWidth" => changes.char_width = Some(vector(value, "CharWidth", line)?),
368 "IsFixedPitch" => changes.is_fixed_pitch = Some(boolean(value, "IsFixedPitch", line)?),
369 _ => return Ok(false),
370 }
371 for direction in [Direction::Zero, Direction::One] {
372 if matches!(
373 (selector, direction),
374 (MetricsSets::Zero, Direction::One) | (MetricsSets::One, Direction::Zero)
375 ) {
376 continue;
377 }
378 let metrics = &mut self.font.directions[direction.index()];
379 metrics.underline_position = changes.underline_position.or(metrics.underline_position);
380 metrics.underline_thickness =
381 changes.underline_thickness.or(metrics.underline_thickness);
382 metrics.italic_angle = changes.italic_angle.or(metrics.italic_angle);
383 metrics.char_width = changes.char_width.or(metrics.char_width);
384 metrics.is_fixed_pitch = changes.is_fixed_pitch.or(metrics.is_fixed_pitch);
385 self.direction_lines[direction.index()] = line;
386 }
387 Ok(true)
388 }
389
390 fn global(&mut self, key: &'a str, value: &'a str, line: usize) -> Result<bool, ParseError> {
391 match key {
392 "FontName" => self.font.font_name = Cow::Borrowed(value),
393 "FontBBox" => {
394 self.font.font_bbox = bbox(value, "FontBBox", line)?;
395 self.bbox_seen = true;
396 }
397 "MetricsSets" => self.font.metrics_sets = Some(selector(value, "MetricsSets", line)?),
398 "VVector" => {
399 self.font.v_vector = Some(vector(value, "VVector", line)?);
400 self.vector_line = line;
401 }
402 "FullName" => self.font.full_name = Some(Cow::Borrowed(value)),
403 "FamilyName" => self.font.family_name = Some(Cow::Borrowed(value)),
404 "Weight" => self.font.weight = Some(Cow::Borrowed(value)),
405 "Version" => self.font.version = Some(Cow::Borrowed(value)),
406 "Notice" => self.font.notice = Some(Cow::Borrowed(value)),
407 "EncodingScheme" => self.font.encoding_scheme = Some(Cow::Borrowed(value)),
408 "CharacterSet" => self.font.character_set = Some(Cow::Borrowed(value)),
409 "MappingScheme" => {
410 self.font.mapping_scheme = Some(integer(value, "MappingScheme", line)?);
411 }
412 "EscChar" => self.font.esc_char = Some(integer(value, "EscChar", line)?),
413 "Characters" => self.font.characters = Some(integer(value, "Characters", line)?),
414 "IsBaseFont" => self.font.is_base_font = Some(boolean(value, "IsBaseFont", line)?),
415 "IsCIDFont" => self.font.is_cid_font = Some(boolean(value, "IsCIDFont", line)?),
416 "IsFixedV" => self.font.is_fixed_v = Some(boolean(value, "IsFixedV", line)?),
417 "CapHeight" => self.font.cap_height = Some(number(value, "CapHeight", line)?),
418 "XHeight" => self.font.x_height = Some(number(value, "XHeight", line)?),
419 "Ascender" => self.font.ascender = Some(number(value, "Ascender", line)?),
420 "Descender" => self.font.descender = Some(number(value, "Descender", line)?),
421 "StdHW" => self.font.std_hw = Some(number(value, "StdHW", line)?),
422 "StdVW" => self.font.std_vw = Some(number(value, "StdVW", line)?),
423 _ => return Ok(false),
424 }
425 Ok(true)
426 }
427
428 fn finish(self, line: usize) -> Result<FontMetrics<'a>, ParseError> {
429 if !self.header_seen {
430 return Err(ParseError::MissingHeader { line: 1 });
431 }
432 if self.font.font_name.is_empty() {
433 return Err(ParseError::MissingRequiredField { field: "FontName" });
434 }
435 if !self.bbox_seen {
436 return Err(ParseError::MissingRequiredField { field: "FontBBox" });
437 }
438 if !self.finished {
439 return Err(malformed(
440 line,
441 "EndFontMetrics",
442 "missing closing font marker",
443 ));
444 }
445 if self.font.mapping_scheme == Some(3) && self.font.esc_char.is_none() {
446 return Err(ParseError::MissingRequiredField { field: "EscChar" });
447 }
448 if self.font.is_fixed_v == Some(false) && self.font.v_vector.is_some() {
449 return Err(malformed(
450 self.vector_line,
451 "VVector",
452 "global vector conflicts with IsFixedV false",
453 ));
454 }
455 for direction in [Direction::Zero, Direction::One] {
456 let metrics = self.font.direction(direction);
457 if metrics.char_width.is_some() && metrics.is_fixed_pitch == Some(false) {
458 return Err(malformed(
459 self.direction_lines[direction.index()],
460 "CharWidth",
461 "global width conflicts with IsFixedPitch false",
462 ));
463 }
464 }
465 Ok(self.font)
466 }
467}
468
469fn selector(value: &str, field: &'static str, line: usize) -> Result<MetricsSets, ParseError> {
470 match integer::<u8>(value, field, line)? {
471 0 => Ok(MetricsSets::Zero),
472 1 => Ok(MetricsSets::One),
473 2 => Ok(MetricsSets::Both),
474 _ => Err(malformed(
475 line,
476 field,
477 "expected direction selector 0, 1, or 2",
478 )),
479 }
480}
481
482fn character_line(text: &str) -> bool {
483 text.split(';').any(|part| {
484 matches!(
485 keyword(part).0,
486 "C" | "CH"
487 | "N"
488 | "B"
489 | "WX"
490 | "WY"
491 | "W0X"
492 | "W0Y"
493 | "W1X"
494 | "W1Y"
495 | "W"
496 | "W0"
497 | "W1"
498 | "VV"
499 | "L"
500 )
501 })
502}
503
504fn validate_text(source: &[u8]) -> Result<(), ParseError> {
505 let mut line = 1;
506 for (offset, &value) in source.iter().enumerate() {
507 if !matches!(value, b'\t' | b'\n' | b'\r' | 0x1b | 0x20..=0x7e) {
508 return Err(ParseError::InvalidByte {
509 offset,
510 line,
511 value,
512 });
513 }
514 if value == b'\n' {
515 line += 1;
516 }
517 }
518 Ok(())
519}
520
521fn read(source: &str) -> Result<FontMetrics<'_>, ParseError> {
522 let mut reader = Reader::new(source.len());
523 let mut line = 0;
524 for raw in source.lines() {
525 line += 1;
526 reader.line(raw, line)?;
527 }
528 reader.finish(line + 1)
529}
530
531#[must_use = "parsing may return an error"]
540pub fn parse(source: &str) -> Result<FontMetrics<'_>, ParseError> {
541 validate_text(source.as_bytes())?;
542 read(source)
543}
544
545#[must_use = "parsing may return an error"]
552pub fn parse_bytes(source: &[u8]) -> Result<FontMetrics<'_>, ParseError> {
553 validate_text(source)?;
554 let text = std::str::from_utf8(source).map_err(|error| ParseError::InvalidByte {
555 offset: error.valid_up_to(),
556 line: 1,
557 value: source[error.valid_up_to()],
558 })?;
559 read(text)
560}