Skip to main content

afm/
lib.rs

1use pom::char_class;
2use pom::Parser;
3use pom::parser::*;
4use std::str::FromStr;
5use std::string::*;
6use std::str;
7use std::char;
8
9use std::collections::HashMap;
10
11extern crate geo;
12extern crate pom;
13
14use geo::Bbox;
15
16#[derive(PartialEq, Debug)]
17pub struct FontMetrics {
18    pub metric_sets: i32,
19    pub font_name: String,
20    pub full_name: String,
21    pub family_name: String,
22    pub weight: String,
23    pub font_bbox: Bbox<f64>,
24    pub font_version: String,
25    pub notice: String,
26    pub encoding_scheme: String,
27    pub mapping_scheme: u32,
28    pub esc_char: u32,
29    pub character_set: String,
30    pub characters: u32,
31    pub is_base_font: bool,
32    pub v_vector: (f64, f64),
33    pub is_fixed_v: bool,
34    pub cap_height: f64,
35    pub x_height: f64,
36    pub ascender: f64,
37    pub descender: f64,
38    pub comments: Vec<String>,
39
40    pub underline_position: f64,
41    pub underline_thickness: f64,
42    pub italic_angle: f64,
43    pub char_width: (f64, f64),
44    pub is_fixed_pitch: bool,
45    pub standard_horizontal_width: f64,
46    pub standard_vertical_width: f64,
47
48    pub char_metrics: Vec<CharMetric>,
49    pub char_metrics_map: HashMap<String, CharMetric>,
50    pub track_kern: Vec<TrackKern>,
51    pub composites: Vec<Composite>,
52    pub kern_pairs: Vec<KernPair>,
53    pub kern_pairs0: Vec<KernPair>,
54    pub kern_pairs1: Vec<KernPair>,
55}
56
57impl Default for FontMetrics {
58    fn default() -> FontMetrics {
59        FontMetrics {
60            // TODO check for actual default values
61            metric_sets: 0,
62            font_name: String::new(),
63            full_name: String::new(),
64            family_name: String::new(),
65            weight: String::new(),
66            font_bbox: Bbox {
67                xmin: 0.,
68                xmax: 0.,
69                ymin: 0.,
70                ymax: 0.,
71            },
72            font_version: String::new(),
73            notice: String::new(),
74            encoding_scheme: String::new(),
75            mapping_scheme: 0,
76            esc_char: 0,
77            character_set: String::new(),
78            characters: 0,
79            is_base_font: true,
80            v_vector: (0.0, 0.0),
81            is_fixed_v: true,
82            cap_height: 0.0,
83            x_height: 0.0,
84            ascender: 0.0,
85            descender: 0.0,
86            comments: Vec::new(),
87
88            underline_position: 0.0,
89            underline_thickness: 0.0,
90            italic_angle: 0.0,
91            char_width: (0.0, 0.0),
92            is_fixed_pitch: true,
93            standard_horizontal_width: 0.0,
94            standard_vertical_width: 0.0,
95
96            char_metrics: Vec::new(),
97            char_metrics_map: HashMap::new(),
98            track_kern: Vec::new(),
99            composites: Vec::new(),
100            kern_pairs: Vec::new(),
101            kern_pairs0: Vec::new(),
102            kern_pairs1: Vec::new(),
103        }
104    }
105}
106
107#[derive(PartialEq, Debug)]
108pub struct CharMetric {
109    pub name: String,
110    pub bbox: Bbox<f64>,
111    pub ligatures: Vec<Ligature>,
112    pub character_code: i32,
113    pub wx: f64,
114    pub w0x: f64,
115    pub w1x: f64,
116    pub wy: f64,
117    pub w0y: f64,
118    pub w1y: f64,
119    pub w: (f64, f64),
120    pub w0: (f64, f64),
121    pub w1: (f64, f64),
122    pub vv: (f64, f64),
123}
124
125impl Default for CharMetric {
126    fn default() -> CharMetric {
127        CharMetric {
128            name: String::new(),
129            bbox: Bbox {
130                xmin: 0.,
131                xmax: 0.,
132                ymin: 0.,
133                ymax: 0.,
134            },
135            ligatures: Vec::new(),
136            character_code: 0,
137            wx: 0.0,
138            w0x: 0.0,
139            w1x: 0.0,
140            wy: 0.0,
141            w0y: 0.0,
142            w1y: 0.0,
143            w: (0.0, 0.0),
144            w0: (0.0, 0.0),
145            w1: (0.0, 0.0),
146            vv: (0.0, 0.0),
147        }
148    }
149}
150
151#[derive(PartialEq, Debug)]
152pub struct TrackKern {
153    pub degree: i32,
154    pub min_point_size: f64,
155    pub min_kern: f64,
156    pub max_point_size: f64,
157    pub max_kern: f64,
158}
159
160#[derive(PartialEq, Debug)]
161pub struct KernPair {
162    pub first_kern_character: String,
163    pub second_kern_character: String,
164    pub x: f64,
165    pub y: f64,
166}
167
168#[derive(PartialEq, Debug)]
169pub struct Ligature {
170    pub successor: String,
171    pub ligature: String,
172}
173
174#[derive(PartialEq, Debug)]
175pub struct Composite {
176    pub name: String,
177    pub parts: Vec<CompositePart>,
178}
179
180#[derive(PartialEq, Debug)]
181pub struct CompositePart {
182    pub name: String,
183    pub x_displacement: i32,
184    pub y_displacement: i32,
185}
186
187fn string_char(c: u8) -> bool {
188    c >= 0x20 && c <= 0x7E
189}
190
191fn name_char(c: u8) -> bool {
192    string_char(c) && (!char_class::space(c))
193}
194
195fn digit(c: u8) -> bool {
196    c >= b'0' && c <= b'9'
197}
198
199fn space() -> Parser<u8, ()> {
200    is_a(char_class::space).repeat(1..).discard()
201}
202
203fn eol() -> Parser<u8, ()> {
204    (is_a(char_class::space).repeat(0..) - one_of(b"\r\n").repeat(1..)).discard()
205}
206
207// Types
208
209fn string() -> Parser<u8, String> {
210    is_a(string_char).repeat(1..).convert(String::from_utf8)
211}
212
213fn name() -> Parser<u8, String> {
214    is_a(name_char).repeat(1..).convert(String::from_utf8)
215}
216
217fn boolean() -> Parser<u8, bool> {
218    seq(b"true").map(|_| true) | seq(b"false").map(|_| false)
219}
220
221fn integer() -> Parser<u8, i32> {
222    let integer =
223        sym(b'-').opt() - (one_of(b"123456789") - one_of(b"0123456789").repeat(0..) | sym(b'0'));
224    integer
225        .collect()
226        .convert(String::from_utf8)
227        .convert(|s| i32::from_str(&s))
228}
229
230fn uinteger() -> Parser<u8, u32> {
231    let integer = one_of(b"123456789") - one_of(b"0123456789").repeat(0..) | sym(b'0');
232    integer
233        .collect()
234        .convert(String::from_utf8)
235        .convert(|s| u32::from_str(&s))
236}
237
238fn hex_integer() -> Parser<u8, i32> {
239    let hex_digits = is_a(char_class::hex_digit).repeat(1..).collect();
240    sym(b'<') * hex_digits.convert(|v| i32::from_str_radix(&String::from_utf8(v).unwrap(), 16))
241}
242
243fn number() -> Parser<u8, f64> {
244    let integer = one_of(b"123456789") - one_of(b"0123456789").repeat(0..) | sym(b'0');
245    let frac = sym(b'.') + one_of(b"0123456789").repeat(1..);
246    let exp = one_of(b"eE") + one_of(b"+-").opt() + one_of(b"0123456789").repeat(1..);
247    let number = sym(b'-').opt() + integer + frac.opt() + exp.opt();
248    number
249        .collect()
250        .convert(String::from_utf8)
251        .convert(|s| f64::from_str(&s))
252}
253
254fn bbox() -> Parser<u8, Bbox<f64>> {
255    let numbers = (number() - space()).repeat(3) + number();
256    numbers.map(|(nums, num)| Bbox {
257        xmin: nums[0],
258        xmax: nums[1],
259        ymin: nums[2],
260        ymax: num,
261    })
262}
263
264// Combinators
265
266fn start_command(command: &'static [u8]) -> Parser<u8, ()> {
267    (seq(command) * space()).discard()
268}
269
270fn string_command(
271    command: &'static [u8],
272    build: &'static Fn(String) -> Command,
273) -> Parser<u8, Command> {
274    start_command(command) * string().map(build)
275}
276
277fn integer_command(
278    command: &'static [u8],
279    build: &'static Fn(i32) -> Command,
280) -> Parser<u8, Command> {
281    start_command(command) * integer().map(build)
282}
283
284fn uinteger_command(
285    command: &'static [u8],
286    build: &'static Fn(u32) -> Command,
287) -> Parser<u8, Command> {
288    start_command(command) * uinteger().map(build)
289}
290
291fn number_command(
292    command: &'static [u8],
293    build: &'static Fn(f64) -> Command,
294) -> Parser<u8, Command> {
295    start_command(command) * number().map(build)
296}
297
298fn bool_command(
299    command: &'static [u8],
300    build: &'static Fn(bool) -> Command,
301) -> Parser<u8, Command> {
302    start_command(command) * boolean().map(build)
303}
304
305fn bbox_command(
306    command: &'static [u8],
307    build: &'static Fn(Bbox<f64>) -> Command,
308) -> Parser<u8, Command> {
309    start_command(command) * bbox().map(build)
310}
311
312fn num_num_command(
313    command: &'static [u8],
314    build: &'static Fn(f64, f64) -> Command,
315) -> Parser<u8, Command> {
316    start_command(command) * (number() - space() + number()).map(move |(a, b)| build(a, b))
317}
318
319// Kern Pairs
320fn kp_cmd() -> Parser<u8, KernPair> {
321    let cmd = seq(b"KP") - space();
322    let names = name() - space() + name() - space();
323    let nums = number() - space() + number();
324    cmd * (names + nums).map(move |((n1, n2), (numx, numy))| KernPair {
325        first_kern_character: n1,
326        second_kern_character: n2,
327        x: numx,
328        y: numy,
329    })
330}
331
332static ERROR_MSG: Result<String, &str> = Err("hex value not in ascii");
333
334fn hex_string() -> Parser<u8, String> {
335    let hex_bytes = is_a(char_class::hex_digit).repeat(2).collect();
336    let hex_int = hex_bytes.convert(|b| u32::from_str_radix(str::from_utf8(&b).unwrap(), 16));
337    let hex_char = hex_int.convert(|u| char::from_u32(u).ok_or(ERROR_MSG.clone()));
338    sym(b'<') * hex_char.repeat(0..).map(|v| v.into_iter().collect()) - sym(b'>')
339}
340
341fn kph_cmd() -> Parser<u8, KernPair> {
342    let cmd = seq(b"KPH") - space();
343    let names = hex_string() - space() + hex_string() - space();
344    let nums = number() - space() + number();
345    cmd * (names + nums).map(move |((n1, n2), (numx, numy))| KernPair {
346        first_kern_character: n1,
347        second_kern_character: n2,
348        x: numx,
349        y: numy,
350    })
351}
352
353fn kpx_cmd() -> Parser<u8, KernPair> {
354    let cmd = seq(b"KPX") - space();
355    let names = name() - space() + name() - space();
356    let num = number();
357    cmd * (names + num).map(|((name1, name2), num)| KernPair {
358        first_kern_character: name1,
359        second_kern_character: name2,
360        x: num,
361        y: 0.0,
362    })
363}
364
365fn kpy_cmd() -> Parser<u8, KernPair> {
366    let cmd = seq(b"KPY") - space();
367    let names = name() - space() + name() - space();
368    let num = number();
369    cmd * (names + num).map(|((name1, name2), num)| KernPair {
370        first_kern_character: name1,
371        second_kern_character: name2,
372        x: 0.0,
373        y: num,
374    })
375}
376
377fn kern_pair() -> Parser<u8, KernPair> {
378    kp_cmd() | kph_cmd() | kpx_cmd() | kpy_cmd()
379}
380
381fn kern_pairs() -> Parser<u8, (Option<u8>, Vec<KernPair>)> {
382    let command = seq(b"StartKernPairs") * one_of(b"01").opt();
383    command - space() + uinteger() - eol().repeat(1..) >> move |(idx, len)| {
384        let end = seq(b"EndKernPairs") - eol().repeat(1..);
385        let pairs = (kern_pair() - eol().repeat(1..)).repeat(len as usize);
386        pairs.map(move |pairs| (idx, pairs)) - end
387    }
388}
389
390fn track_kern() -> Parser<u8, TrackKern> {
391    let begin = seq(b"TrackKern") - space();
392    let num_space = || number() - space();
393    let content = integer() - space() + num_space() + num_space() + num_space() + num_space();
394    begin
395        * content.map(
396            |((((deg, min_size), min_kern), max_size), max_kern)| TrackKern {
397                degree: deg,
398                min_point_size: min_size,
399                min_kern: min_kern,
400                max_point_size: max_size,
401                max_kern: max_kern,
402            },
403        )
404}
405
406fn track_kerns() -> Parser<u8, Vec<TrackKern>> {
407    let begin = (seq(b"StartTrackKern") - space()) * uinteger() - eol();
408    begin >> move |len| (track_kern() - eol()).repeat(len as usize) - seq(b"EndTrackKern") - eol()
409}
410
411fn kern_data() -> Parser<u8, Vec<KernDataCmd>> {
412    let kernpairs = kern_pairs().map(|(idx, pairs)| match idx {
413        Some(0) => KernDataCmd::Kernpairs0(pairs),
414        Some(1) => KernDataCmd::Kernpairs1(pairs),
415        Some(_) => unreachable!(),
416        None => KernDataCmd::Kernpairs(pairs),
417    });
418    let trackkern = track_kerns().map(|kerns| KernDataCmd::TrackKern(kerns));
419    let begin = seq(b"StartKernData") - eol().repeat(1..);
420    let content = (kernpairs | trackkern).repeat(1..);
421    begin * content - seq(b"EndKernData")
422}
423
424// Composites
425
426fn composite_part() -> Parser<u8, CompositePart> {
427    let params =
428        (name() - space() + integer() - space() + integer()).map(|((n, x), y)| CompositePart {
429            name: n,
430            x_displacement: x,
431            y_displacement: y,
432        });
433    (seq(b"PCC") - space()) * params
434}
435
436fn composite() -> Parser<u8, Composite> {
437    (seq(b"CC") - space()) * name() - space() + uinteger() >> |(name, len): (String, u32)| {
438        (space() * composite_part())
439            .repeat(len as usize)
440            .map(move |parts| Composite {
441                name: name.to_owned(),
442                parts: parts,
443            })
444    }
445}
446
447fn composites() -> Parser<u8, Vec<Composite>> {
448    (seq(b"StartComposites") - space()) * uinteger() - eol().repeat(1..) >> move |len| {
449        (composite() - eol().repeat(1..)).repeat(len as usize) - seq(b"EndComposites")
450    }
451}
452
453// Char Metrics
454
455#[derive(PartialEq, Debug)]
456enum CharMetricCommand {
457    C(i32),
458    WX(f64),
459    W0X(f64),
460    W1X(f64),
461    WY(f64),
462    W0Y(f64),
463    W1Y(f64),
464    W(f64, f64),
465    W0(f64, f64),
466    W1(f64, f64),
467    VV(f64, f64),
468    N(String),
469    B(Bbox<f64>),
470    L(Ligature),
471}
472
473fn ligature() -> Parser<u8, Ligature> {
474    (name() - space() + name()).map(|(s, l)| Ligature {
475        successor: s,
476        ligature: l,
477    })
478}
479
480fn charcommand() -> Parser<u8, CharMetricCommand> {
481    (sym(b'C') - space()) * integer().map(&CharMetricCommand::C)
482        | (seq(b"CH") - space()) * hex_integer().map(&CharMetricCommand::C)
483        | (seq(b"WX") - space()) * number().map(&CharMetricCommand::WX)
484        | (seq(b"W0X") - space()) * number().map(&CharMetricCommand::W0X)
485        | (seq(b"W1X") - space()) * number().map(&CharMetricCommand::W1X)
486        | (seq(b"WY") - space()) * number().map(&CharMetricCommand::WY)
487        | (seq(b"W0Y") - space()) * number().map(&CharMetricCommand::W0Y)
488        | (seq(b"W1Y") - space()) * number().map(&CharMetricCommand::W1Y)
489        | (seq(b"W") - space())
490            * (number() - space() + number()).map(|(x, y)| CharMetricCommand::W(x, y))
491        | (seq(b"W0") - space())
492            * (number() - space() + number()).map(|(x, y)| CharMetricCommand::W0(x, y))
493        | (seq(b"W1") - space())
494            * (number() - space() + number()).map(|(x, y)| CharMetricCommand::W1(x, y))
495        | (seq(b"VV") - space())
496            * (number() - space() + number()).map(|(x, y)| CharMetricCommand::VV(x, y))
497        | (sym(b'N') - space()) * name().map(CharMetricCommand::N)
498        | (sym(b'B') - space()) * bbox().map(CharMetricCommand::B)
499        | (sym(b'L') - space()) * ligature().map(CharMetricCommand::L)
500}
501
502fn char_metric() -> Parser<u8, CharMetric> {
503    let seperator = || space().opt() * sym(b';') - space().opt();
504    let cmds = list(charcommand(), seperator()) - seperator().opt();
505    cmds.map(|commands| {
506        commands.into_iter().fold(
507            CharMetric::default(),
508            |mut metric: CharMetric, command: CharMetricCommand| {
509                match command {
510                    CharMetricCommand::C(c) => metric.character_code = c,
511                    CharMetricCommand::WX(wx) => metric.wx = wx,
512                    CharMetricCommand::W0X(w0x) => metric.w0x = w0x,
513                    CharMetricCommand::W1X(w1x) => metric.w1x = w1x,
514                    CharMetricCommand::WY(wy) => metric.wy = wy,
515                    CharMetricCommand::W0Y(w0y) => metric.w0y = w0y,
516                    CharMetricCommand::W1Y(w1y) => metric.w1y = w1y,
517                    CharMetricCommand::W(w1, w2) => metric.w = (w1, w2),
518                    CharMetricCommand::W0(w1, w2) => metric.w0 = (w1, w2),
519                    CharMetricCommand::W1(w1, w2) => metric.w1 = (w1, w2),
520                    CharMetricCommand::VV(vv1, vv2) => metric.vv = (vv1, vv2),
521                    CharMetricCommand::N(name) => metric.name = name,
522                    CharMetricCommand::B(bbox) => metric.bbox = bbox,
523                    CharMetricCommand::L(lig) => metric.ligatures.push(lig),
524                }
525                metric
526            },
527        )
528    })
529}
530
531fn char_metrics() -> Parser<u8, Vec<CharMetric>> {
532    let begin = seq(b"StartCharMetrics") * space() * uinteger() - eol().repeat(1..);
533    begin >> move |len: u32| {
534        (char_metric() - eol().repeat(1..)).repeat(len as usize) - seq(b"EndCharMetrics")
535    }
536}
537
538fn comment() -> Parser<u8, Command> {
539    let cmd =
540        (seq(b"Comment") - space()) * string().opt().map(|o| o.unwrap_or_else(|| String::new()));
541    cmd.map(Command::Comment)
542}
543
544// Commands
545
546#[derive(PartialEq, Debug)]
547enum Command {
548    //    AfmVersion((i32, i32)),
549    MetricsSet(i32),
550    FontName(String),
551    FullName(String),
552    FamilyName(String),
553    Weight(String),
554    FontBBox(Bbox<f64>),
555    Version(String),
556    Notice(String),
557    EncodingScheme(String),
558    MappingScheme(u32),
559    EscChar(u32),
560    CharacterSet(String),
561    Characters(u32),
562    IsBaseFont(bool),
563    VVector(f64, f64),
564    IsFixedV(bool),
565    CapHeight(f64),
566    XHeight(f64),
567    Ascender(f64),
568    Descender(f64),
569    StdHW(f64),
570    StdVW(f64),
571    Comment(String),
572    UnderlinePosition(f64),
573    UnderlineThickness(f64),
574    ItalicAngle(f64),
575    CharWidth(f64, f64),
576    IsFixedPitch(bool),
577    CharMetrics(Vec<CharMetric>),
578    Composites(Vec<Composite>),
579    KernData(Vec<KernDataCmd>),
580}
581
582#[derive(PartialEq, Debug)]
583enum KernDataCmd {
584    TrackKern(Vec<TrackKern>),
585    Kernpairs(Vec<KernPair>),
586    Kernpairs0(Vec<KernPair>),
587    Kernpairs1(Vec<KernPair>),
588}
589
590fn command<'a>() -> Parser<u8, Command> {
591    comment() | string_command(b"Version", &Command::Version)
592        | integer_command(b"MetricsSet", &Command::MetricsSet)
593        | string_command(b"FontName", &Command::FontName)
594        | string_command(b"FullName", &Command::FullName)
595        | string_command(b"FamilyName", &Command::FamilyName)
596        | string_command(b"Weight", &Command::Weight)
597        | bbox_command(b"FontBBox", &Command::FontBBox)
598        | string_command(b"Weight", &Command::Weight)
599        | string_command(b"Version", &Command::Version)
600        | string_command(b"Notice", &Command::Notice)
601        | string_command(b"EncodingScheme", &Command::EncodingScheme)
602        | uinteger_command(b"MappingScheme", &Command::MappingScheme)
603        | uinteger_command(b"EscChar", &Command::EscChar)
604        | string_command(b"CharacterSet", &Command::CharacterSet)
605        | uinteger_command(b"Characters", &Command::Characters)
606        | bool_command(b"IsBaseFont", &Command::IsBaseFont)
607        | num_num_command(b"VVector", &Command::VVector)
608        | bool_command(b"IsFixedV", &Command::IsFixedV)
609        | number_command(b"CapHeight", &Command::CapHeight)
610        | number_command(b"XHeight", &Command::XHeight)
611        | number_command(b"Ascender", &Command::Ascender)
612        | number_command(b"Descender", &Command::Descender)
613        | number_command(b"StdHW", &Command::StdHW) | number_command(b"StdVW", &Command::StdVW)
614        | number_command(b"UnderlinePosition", &Command::UnderlinePosition)
615        | number_command(b"UnderlineThickness", &Command::UnderlineThickness)
616        | number_command(b"ItalicAngle", &Command::ItalicAngle)
617        | num_num_command(b"CharWidth", &Command::CharWidth)
618        | bool_command(b"IsFixedPitch", &Command::IsFixedPitch)
619        | char_metrics().map(Command::CharMetrics) | composites().map(Command::Composites)
620        | kern_data().map(Command::KernData)
621}
622
623// Public functions
624
625pub fn afm() -> Parser<u8, FontMetrics> {
626    let begin =
627        start_command(b"StartFontMetrics") * (is_a(digit) - sym(b'.') + is_a(digit)) - eol();
628    let end = eol().opt() * seq(b"EndFontMetrics") * eol().repeat(0..) * end();
629    let elems = list(command(), eol());
630
631    let commands = begin * elems.expect("AFM commands") - end.expect("EndFontMetrics");
632    commands.map(|commands| {
633        commands.into_iter().fold(
634            FontMetrics::default(),
635            |mut metric: FontMetrics, command: Command| {
636                match command {
637                    Command::MetricsSet(metric_sets) => metric.metric_sets = metric_sets,
638                    Command::FontName(name) => metric.font_name = name,
639                    Command::FullName(name) => metric.full_name = name,
640                    Command::FamilyName(name) => metric.family_name = name,
641                    Command::Weight(weight) => metric.weight = weight,
642                    Command::FontBBox(bbox) => metric.font_bbox = bbox,
643                    Command::Version(version) => metric.font_version = version,
644                    Command::Notice(notice) => metric.notice = notice,
645                    Command::EncodingScheme(scheme) => metric.encoding_scheme = scheme,
646                    Command::MappingScheme(scheme) => metric.mapping_scheme = scheme,
647                    Command::EscChar(c) => metric.esc_char = c,
648                    Command::CharacterSet(charset) => metric.character_set = charset,
649                    Command::Characters(c) => metric.characters = c,
650                    Command::IsBaseFont(base_font) => metric.is_base_font = base_font,
651                    Command::VVector(v1, v2) => metric.v_vector = (v1, v2),
652                    Command::IsFixedV(fixed) => metric.is_fixed_v = fixed,
653                    Command::CapHeight(height) => metric.cap_height = height,
654                    Command::XHeight(height) => metric.x_height = height,
655                    Command::Ascender(asc) => metric.ascender = asc,
656                    Command::Descender(desc) => metric.descender = desc,
657                    Command::StdHW(stdhw) => metric.standard_horizontal_width = stdhw,
658                    Command::StdVW(stdvw) => metric.standard_vertical_width = stdvw,
659                    Command::Comment(comment) => metric.comments.push(comment),
660                    Command::UnderlinePosition(pos) => metric.underline_position = pos,
661                    Command::UnderlineThickness(thickness) => {
662                        metric.underline_thickness = thickness
663                    }
664                    Command::ItalicAngle(angle) => metric.italic_angle = angle,
665                    Command::CharWidth(w1, w2) => metric.char_width = (w1, w2),
666                    Command::IsFixedPitch(fixed) => metric.is_fixed_pitch = fixed,
667                    Command::CharMetrics(char_metrics) => metric.char_metrics = char_metrics,
668                    Command::Composites(composites) => metric.composites = composites,
669                    Command::KernData(cmds) => for cmd in cmds {
670                        match cmd {
671                            KernDataCmd::TrackKern(kerns) => metric.track_kern = kerns,
672                            KernDataCmd::Kernpairs(pairs) => metric.kern_pairs = pairs,
673                            KernDataCmd::Kernpairs0(pairs) => metric.kern_pairs0 = pairs,
674                            KernDataCmd::Kernpairs1(pairs) => metric.kern_pairs1 = pairs,
675                        }
676                    },
677                }
678                metric
679            },
680        )
681    })
682}
683
684#[cfg(test)]
685mod tests {
686    use super::*;
687    use pom::DataInput;
688    use std::path::Path;
689    use std::fs::File;
690    use std::io::prelude::*;
691
692    #[test]
693    fn parse_demo_file() {
694        let input = br#"StartFontMetrics 4.1
695Comment UniqueID 43050
696Comment Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated.  All Rights Reserved.
697Comment Creation Date: Thu May  1 17:27:09 1997
698Comment VMusage 39754 50779
699FontName Courier
700FullName Courier
701FamilyName Courier
702Weight Medium
703ItalicAngle 0
704IsFixedPitch true
705CharacterSet ExtendedRoman
706FontBBox -23 -250 715 805
707UnderlinePosition -100
708UnderlineThickness 50
709Version 003.000
710Notice Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated.  All Rights Reserved.
711EncodingScheme AdobeStandardEncoding
712CapHeight 562
713XHeight 426
714Ascender 629
715Descender -157
716StdHW 51
717StdVW 51
718StartCharMetrics 5
719C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
720C 33 ; WX 600 ; N exclam ; B 236 -15 364 572 ;
721C 34 ; WX 600 ; N quotedbl ; B 187 328 413 562 ;
722C 35 ; WX 600 ; N numbersign ; B 93 -32 507 639 ;
723C 36 ; WX 600 ; N dollar ; B 105 -126 496 662 ;
724EndCharMetrics
725StartKernData
726StartKernPairs 3
727KPX A Cacute -30
728KPX ecaron period -15
729KPX zdotaccent ohungarumlaut -15
730EndKernPairs
731EndKernData
732EndFontMetrics
733"#;
734        let mut buf = DataInput::new(input);
735        let parser = afm().parse(&mut buf);
736        assert!(parser.is_ok());
737        let r = parser.unwrap();
738        assert_eq!(r.font_name, "Courier");
739        assert_eq!(r.family_name, "Courier");
740        assert_eq!(r.full_name, "Courier");
741        assert_eq!(r.font_version, "003.000");
742        assert_eq!(r.comments[0], "UniqueID 43050");
743        assert_eq!(r.comments.len(), 4);
744        assert_eq!(r.weight, "Medium");
745        assert_eq!(r.italic_angle, 0.0);
746        assert_eq!(r.is_fixed_pitch, true);
747        assert_eq!(r.character_set, "ExtendedRoman");
748        assert_eq!(r.underline_position, -100.0);
749        assert_eq!(r.underline_thickness, 50.0);
750        assert_eq!(r.font_version, "003.000");
751        assert_eq!(r.notice.len(), 98);
752        assert_eq!(r.cap_height, 562.0);
753        assert_eq!(r.x_height, 426.0);
754        assert_eq!(r.ascender, 629.0);
755        assert_eq!(r.descender, -157.0);
756        assert_eq!(r.standard_horizontal_width, 51.0);
757        assert_eq!(r.standard_vertical_width, 51.0);
758        assert_eq!(r.char_metrics.len(), 5);
759        assert_eq!(r.kern_pairs.len(), 3);
760    }
761
762    #[test]
763    fn parse_standard_14_pdf_fonts() {
764        use std::fs;
765        use std::ffi::OsStr;
766
767        let assets_dir = Path::new("assets");
768        for file in fs::read_dir(assets_dir).unwrap() {
769            let path = file.expect("I/O error when reading a asset").path();
770            if path.is_file() && path.extension() == Some(OsStr::new("afm")) {
771                println!("Parse file {}", path.display());
772                let mut file = File::open(&path).expect("Could not open an asset file");
773                let mut v = Vec::new();
774                file.read_to_end(&mut v)
775                    .expect(&format!("Could not read {}", path.display()));
776
777                let mut buf = DataInput::new(&v);
778                let parse_result = afm().parse(&mut buf);
779                assert!(
780                    parse_result.is_ok(),
781                    "Could not parse the asset file {}",
782                    path.display()
783                );
784            }
785        }
786    }
787}