Skip to main content

docling_pdf/render/font/
type1.rs

1//! Type 1 font programs (`/FontFile`): the clear-text header's `/FontMatrix`
2//! and built-in `/Encoding`, the eexec-encrypted private portion's `/Subrs`
3//! and `/CharStrings`, and a Type 1 charstring interpreter (Adobe's *Type 1
4//! Font Format*, chapters 6–8) that turns a glyph into a path in font units:
5//! `hsbw`/`sbw`, the move/line/curve operators, `closepath`, `callsubr`,
6//! `div`, `seac` accent composition, and flex / hint replacement through
7//! `callothersubr`/`pop`/`setcurrentpoint`. Hints are parsed and dropped.
8
9use std::collections::HashMap;
10
11use super::encodings;
12use super::GlyphPath;
13
14pub struct Type1Font {
15    /// Glyph name → decrypted charstring.
16    charstrings: HashMap<String, Vec<u8>>,
17    subrs: Vec<Vec<u8>>,
18    /// The program's own `/Encoding`: code → glyph name.
19    pub encoding: HashMap<u8, String>,
20    pub font_matrix: [f64; 6],
21}
22
23const EEXEC_R: u16 = 55665;
24const CHARSTRING_R: u16 = 4330;
25
26fn decrypt(data: &[u8], mut r: u16, skip: usize) -> Vec<u8> {
27    const C1: u16 = 52845;
28    const C2: u16 = 22719;
29    let mut out = Vec::with_capacity(data.len());
30    for &c in data {
31        let p = c ^ (r >> 8) as u8;
32        r = (u16::from(c))
33            .wrapping_add(r)
34            .wrapping_mul(C1)
35            .wrapping_add(C2);
36        out.push(p);
37    }
38    if out.len() > skip {
39        out.drain(..skip);
40    } else {
41        out.clear();
42    }
43    out
44}
45
46fn is_hex(b: u8) -> bool {
47    b.is_ascii_hexdigit()
48}
49
50/// PFB segments (`0x80 0x01/0x02 len32`) concatenated into a plain stream.
51fn unwrap_pfb(data: &[u8]) -> Vec<u8> {
52    if data.len() < 6 || data[0] != 0x80 {
53        return data.to_vec();
54    }
55    let mut out = Vec::with_capacity(data.len());
56    let mut pos = 0;
57    while pos + 6 <= data.len() && data[pos] == 0x80 {
58        let kind = data[pos + 1];
59        if kind == 3 {
60            break;
61        }
62        let len = u32::from_le_bytes([data[pos + 2], data[pos + 3], data[pos + 4], data[pos + 5]])
63            as usize;
64        pos += 6;
65        let end = (pos + len).min(data.len());
66        out.extend_from_slice(&data[pos..end]);
67        pos = end;
68    }
69    out
70}
71
72impl Type1Font {
73    pub fn parse(raw: &[u8]) -> Option<Type1Font> {
74        let data = unwrap_pfb(raw);
75        let eexec = find(&data, b"eexec")?;
76        let clear = &data[..eexec];
77        let mut pos = eexec + 5;
78        while pos < data.len() && matches!(data[pos], b'\r' | b'\n' | b' ' | b'\t') {
79            pos += 1;
80        }
81        let enc_part = &data[pos..];
82        // Hex or binary: four hex digits in a row mean the PFA hex form.
83        let bin: Vec<u8> = if enc_part.len() >= 4 && enc_part[..4].iter().all(|&b| is_hex(b)) {
84            let mut out = Vec::with_capacity(enc_part.len() / 2);
85            let mut hi: Option<u8> = None;
86            for &b in enc_part {
87                let v = match b {
88                    b'0'..=b'9' => b - b'0',
89                    b'a'..=b'f' => b - b'a' + 10,
90                    b'A'..=b'F' => b - b'A' + 10,
91                    _ => continue,
92                };
93                match hi.take() {
94                    None => hi = Some(v),
95                    Some(h) => out.push((h << 4) | v),
96                }
97            }
98            out
99        } else {
100            enc_part.to_vec()
101        };
102        let private = decrypt(&bin, EEXEC_R, 4);
103        let len_iv = find(&private, b"/lenIV")
104            .and_then(|p| parse_int_after(&private, p + 6))
105            .unwrap_or(4)
106            .max(0) as usize;
107
108        let font_matrix = find(clear, b"/FontMatrix")
109            .and_then(|p| parse_array6(clear, p + 11))
110            .unwrap_or([0.001, 0.0, 0.0, 0.001, 0.0, 0.0]);
111
112        let mut encoding = HashMap::new();
113        if let Some(p) = find(clear, b"/Encoding") {
114            let rest = &clear[p + 9..];
115            let head = &rest[..rest.len().min(40)];
116            if find(head, b"StandardEncoding").is_some() {
117                for &(c, n) in encodings::STANDARD {
118                    encoding.insert(c, n.to_string());
119                }
120            } else {
121                // `dup <code> /<name> put` entries up to `readonly def` / ` def`.
122                let end = find(rest, b" def").map(|e| e + 4).unwrap_or(rest.len());
123                let section = &rest[..end];
124                let mut i = 0;
125                while let Some(d) = find(&section[i..], b"dup ") {
126                    let at = i + d + 4;
127                    let toks: Vec<&[u8]> = section[at..]
128                        .split(|b| b.is_ascii_whitespace())
129                        .filter(|t| !t.is_empty())
130                        .take(3)
131                        .collect();
132                    if toks.len() >= 2 {
133                        if let (Ok(code), Some(nm)) = (
134                            std::str::from_utf8(toks[0])
135                                .ok()
136                                .and_then(|s| s.parse::<u32>().ok())
137                                .ok_or(()),
138                            toks[1].strip_prefix(b"/"),
139                        ) {
140                            if code <= 255 {
141                                encoding
142                                    .insert(code as u8, String::from_utf8_lossy(nm).into_owned());
143                            }
144                        }
145                    }
146                    i = at;
147                }
148            }
149        }
150
151        let subrs = parse_subrs(&private, len_iv);
152        let charstrings = parse_charstrings(&private, len_iv)?;
153        Some(Type1Font {
154            charstrings,
155            subrs,
156            encoding,
157            font_matrix,
158        })
159    }
160
161    pub fn has_glyph(&self, name: &str) -> bool {
162        self.charstrings.contains_key(name)
163    }
164
165    pub fn glyph_names(&self) -> impl Iterator<Item = &String> {
166        self.charstrings.keys()
167    }
168
169    /// The glyph's outline and advance width, in font units (`FontMatrix`
170    /// maps them to text space).
171    pub fn glyph(&self, name: &str) -> Option<GlyphPath> {
172        let cs = self.charstrings.get(name)?;
173        let mut st = Interp {
174            font: self,
175            path: tiny_skia::PathBuilder::new(),
176            stack: Vec::new(),
177            ps_stack: Vec::new(),
178            x: 0.0,
179            y: 0.0,
180            width: 0.0,
181            sbx: 0.0,
182            sby: 0.0,
183            flex: None,
184            open: false,
185            depth: 0,
186        };
187        st.run(cs);
188        st.close();
189        let width = st.width;
190        Some(GlyphPath {
191            path: st.path.finish(),
192            advance: width,
193        })
194    }
195}
196
197fn find(hay: &[u8], needle: &[u8]) -> Option<usize> {
198    hay.windows(needle.len()).position(|w| w == needle)
199}
200
201fn parse_int_after(data: &[u8], pos: usize) -> Option<i64> {
202    let s = &data[pos..data.len().min(pos + 32)];
203    let tok = s
204        .split(|b| b.is_ascii_whitespace())
205        .find(|t| !t.is_empty())?;
206    std::str::from_utf8(tok).ok()?.parse().ok()
207}
208
209fn parse_array6(data: &[u8], pos: usize) -> Option<[f64; 6]> {
210    let open = pos + find(&data[pos..], b"[")?;
211    let close = open + find(&data[open..], b"]")?;
212    let vals: Vec<f64> = std::str::from_utf8(&data[open + 1..close])
213        .ok()?
214        .split_whitespace()
215        .filter_map(|t| t.parse().ok())
216        .collect();
217    if vals.len() != 6 {
218        return None;
219    }
220    Some([vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]])
221}
222
223/// `/Subrs N array` followed by `dup <i> <n> RD <n bytes> NP` entries.
224fn parse_subrs(private: &[u8], len_iv: usize) -> Vec<Vec<u8>> {
225    let mut out = Vec::new();
226    let Some(p) = find(private, b"/Subrs") else {
227        return out;
228    };
229    let count = parse_int_after(private, p + 6).unwrap_or(0).clamp(0, 65536) as usize;
230    out.resize(count, Vec::new());
231    let mut pos = p + 6;
232    for _ in 0..count {
233        let Some(d) = find(&private[pos..], b"dup ") else {
234            break;
235        };
236        let at = pos + d + 4;
237        let Some((idx, rest)) = read_int(private, at) else {
238            break;
239        };
240        let Some((n, rest)) = read_int(private, rest) else {
241            break;
242        };
243        // The RD token (`RD` or `-|`), then one space, then the bytes.
244        let Some(tok_end) = skip_token(private, rest) else {
245            break;
246        };
247        let start = tok_end + 1;
248        let end = start + n.max(0) as usize;
249        if end > private.len() {
250            break;
251        }
252        if idx >= 0 && (idx as usize) < count {
253            out[idx as usize] = decrypt(&private[start..end], CHARSTRING_R, len_iv);
254        }
255        pos = end;
256    }
257    out
258}
259
260/// `/CharStrings N dict dup begin` followed by `/<name> <n> RD <bytes> ND`.
261fn parse_charstrings(private: &[u8], len_iv: usize) -> Option<HashMap<String, Vec<u8>>> {
262    let p = find(private, b"/CharStrings")?;
263    let mut map = HashMap::new();
264    let mut pos = p
265        + 12
266        + find(&private[p + 12..], b"begin")
267            .map(|b| b + 5)
268            .unwrap_or(0);
269    // Each iteration consumes one `/name <len> RD <bytes> ND` entry.
270    while let Some(slash) = private[pos..].iter().position(|&b| b == b'/') {
271        let name_start = pos + slash + 1;
272        let name_end = name_start
273            + private[name_start..]
274                .iter()
275                .position(|b| b.is_ascii_whitespace() || *b == b'{' || *b == b'(')
276                .unwrap_or(0);
277        if name_end == name_start {
278            pos = name_start;
279            continue;
280        }
281        let name = String::from_utf8_lossy(&private[name_start..name_end]).into_owned();
282        let Some((n, rest)) = read_int(private, name_end) else {
283            // `end` reached, or a non-charstring entry.
284            if find(&private[pos..pos + slash], b"end").is_some() {
285                break;
286            }
287            pos = name_end;
288            if !map.is_empty()
289                && find(
290                    &private[name_end..(name_end + 8).min(private.len())],
291                    b"end",
292                )
293                .is_some()
294            {
295                break;
296            }
297            continue;
298        };
299        let Some(tok_end) = skip_token(private, rest) else {
300            break;
301        };
302        let start = tok_end + 1;
303        let end = start + n.max(0) as usize;
304        if end > private.len() {
305            break;
306        }
307        map.insert(name, decrypt(&private[start..end], CHARSTRING_R, len_iv));
308        pos = end;
309        if map.len() > 70_000 {
310            break;
311        }
312    }
313    if map.is_empty() {
314        None
315    } else {
316        Some(map)
317    }
318}
319
320/// An integer token at `pos` (skipping whitespace): `(value, position after)`.
321fn read_int(data: &[u8], mut pos: usize) -> Option<(i64, usize)> {
322    while pos < data.len() && data[pos].is_ascii_whitespace() {
323        pos += 1;
324    }
325    let start = pos;
326    while pos < data.len() && (data[pos].is_ascii_digit() || (pos == start && data[pos] == b'-')) {
327        pos += 1;
328    }
329    if pos == start {
330        return None;
331    }
332    let v = std::str::from_utf8(&data[start..pos]).ok()?.parse().ok()?;
333    Some((v, pos))
334}
335
336/// Skip whitespace and one token; the position of its last byte + 1.
337fn skip_token(data: &[u8], mut pos: usize) -> Option<usize> {
338    while pos < data.len() && data[pos].is_ascii_whitespace() {
339        pos += 1;
340    }
341    let start = pos;
342    while pos < data.len() && !data[pos].is_ascii_whitespace() {
343        pos += 1;
344    }
345    if pos == start {
346        return None;
347    }
348    Some(pos)
349}
350
351struct Interp<'a> {
352    font: &'a Type1Font,
353    path: tiny_skia::PathBuilder,
354    stack: Vec<f64>,
355    ps_stack: Vec<f64>,
356    x: f64,
357    y: f64,
358    width: f64,
359    sbx: f64,
360    sby: f64,
361    /// Flex in progress: the collected points (the reference point excluded).
362    flex: Option<Vec<(f64, f64)>>,
363    open: bool,
364    depth: usize,
365}
366
367impl Interp<'_> {
368    fn close(&mut self) {
369        if self.open {
370            self.path.close();
371            self.open = false;
372        }
373    }
374
375    fn move_to(&mut self, x: f64, y: f64) {
376        self.close();
377        self.path.move_to(x as f32, y as f32);
378        self.open = true;
379    }
380
381    fn line_to(&mut self, x: f64, y: f64) {
382        if !self.open {
383            self.path.move_to(self.x as f32, self.y as f32);
384            self.open = true;
385        }
386        self.path.line_to(x as f32, y as f32);
387    }
388
389    fn curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) {
390        if !self.open {
391            self.path.move_to(self.x as f32, self.y as f32);
392            self.open = true;
393        }
394        self.path.cubic_to(
395            x1 as f32, y1 as f32, x2 as f32, y2 as f32, x3 as f32, y3 as f32,
396        );
397    }
398
399    /// Returns `true` when `endchar` was reached.
400    fn run(&mut self, cs: &[u8]) -> bool {
401        if self.depth > 30 {
402            return true;
403        }
404        let mut i = 0;
405        while i < cs.len() {
406            let v = cs[i];
407            i += 1;
408            match v {
409                32..=246 => self.stack.push(f64::from(v) - 139.0),
410                247..=250 => {
411                    let w = f64::from(*cs.get(i).unwrap_or(&0));
412                    i += 1;
413                    self.stack.push((f64::from(v) - 247.0) * 256.0 + w + 108.0);
414                }
415                251..=254 => {
416                    let w = f64::from(*cs.get(i).unwrap_or(&0));
417                    i += 1;
418                    self.stack.push(-(f64::from(v) - 251.0) * 256.0 - w - 108.0);
419                }
420                255 => {
421                    if i + 4 > cs.len() {
422                        return true;
423                    }
424                    let n = i32::from_be_bytes([cs[i], cs[i + 1], cs[i + 2], cs[i + 3]]);
425                    i += 4;
426                    self.stack.push(f64::from(n));
427                }
428                13 => {
429                    // hsbw: sbx wx
430                    if self.stack.len() >= 2 {
431                        self.sbx = self.stack[0];
432                        self.width = self.stack[1];
433                        self.x = self.sbx;
434                        self.y = 0.0;
435                    }
436                    self.stack.clear();
437                }
438                9 => {
439                    self.close();
440                    self.stack.clear();
441                }
442                1 | 3 => self.stack.clear(),
443                21 => {
444                    let (dx, dy) = self.take2();
445                    self.x += dx;
446                    self.y += dy;
447                    self.after_move();
448                }
449                22 => {
450                    let dx = self.take1();
451                    self.x += dx;
452                    self.after_move();
453                }
454                4 => {
455                    let dy = self.take1();
456                    self.y += dy;
457                    self.after_move();
458                }
459                5 => {
460                    let (dx, dy) = self.take2();
461                    self.x += dx;
462                    self.y += dy;
463                    let (x, y) = (self.x, self.y);
464                    self.line_to(x, y);
465                }
466                6 => {
467                    let dx = self.take1();
468                    self.x += dx;
469                    let (x, y) = (self.x, self.y);
470                    self.line_to(x, y);
471                }
472                7 => {
473                    let dy = self.take1();
474                    self.y += dy;
475                    let (x, y) = (self.x, self.y);
476                    self.line_to(x, y);
477                }
478                8 => {
479                    if self.stack.len() >= 6 {
480                        let s = self.stack.clone();
481                        self.rrcurveto(s[0], s[1], s[2], s[3], s[4], s[5]);
482                    }
483                    self.stack.clear();
484                }
485                30 => {
486                    // vhcurveto: dy1 dx2 dy2 dx3
487                    if self.stack.len() >= 4 {
488                        let s = self.stack.clone();
489                        self.rrcurveto(0.0, s[0], s[1], s[2], s[3], 0.0);
490                    }
491                    self.stack.clear();
492                }
493                31 => {
494                    // hvcurveto: dx1 dx2 dy2 dy3
495                    if self.stack.len() >= 4 {
496                        let s = self.stack.clone();
497                        self.rrcurveto(s[0], 0.0, s[1], s[2], 0.0, s[3]);
498                    }
499                    self.stack.clear();
500                }
501                10 => {
502                    let Some(n) = self.stack.pop() else { continue };
503                    let n = n as i64;
504                    if n >= 0 {
505                        if let Some(sub) = self.font.subrs.get(n as usize) {
506                            // The standard flex/hint subrs 0–3 are emulated
507                            // through callothersubr; running them is what
508                            // real fonts expect anyway.
509                            self.depth += 1;
510                            let sub = sub.clone();
511                            let done = self.run(&sub);
512                            self.depth -= 1;
513                            if done {
514                                return true;
515                            }
516                        }
517                    }
518                }
519                11 => return false,
520                14 => {
521                    self.close();
522                    return true;
523                }
524                12 => {
525                    let v2 = *cs.get(i).unwrap_or(&0);
526                    i += 1;
527                    match v2 {
528                        0 => self.stack.clear(),     // dotsection
529                        1 | 2 => self.stack.clear(), // vstem3 hstem3
530                        6 => {
531                            // seac: asb adx ady bchar achar
532                            if self.stack.len() >= 5 {
533                                let s = self.stack.clone();
534                                self.stack.clear();
535                                self.seac(s[0], s[1], s[2], s[3] as i64, s[4] as i64);
536                            }
537                            return true;
538                        }
539                        7 => {
540                            // sbw: sbx sby wx wy
541                            if self.stack.len() >= 4 {
542                                self.sbx = self.stack[0];
543                                self.sby = self.stack[1];
544                                self.width = self.stack[2];
545                                self.x = self.sbx;
546                                self.y = self.sby;
547                            }
548                            self.stack.clear();
549                        }
550                        12 => {
551                            let b = self.stack.pop().unwrap_or(1.0);
552                            let a = self.stack.pop().unwrap_or(0.0);
553                            self.stack.push(if b != 0.0 { a / b } else { 0.0 });
554                        }
555                        16 => self.callothersubr(),
556                        17 => {
557                            let v = self.ps_stack.pop().unwrap_or(0.0);
558                            self.stack.push(v);
559                        }
560                        33 => {
561                            // setcurrentpoint
562                            if self.stack.len() >= 2 {
563                                self.x = self.stack[0];
564                                self.y = self.stack[1];
565                            }
566                            self.stack.clear();
567                        }
568                        _ => self.stack.clear(),
569                    }
570                }
571                _ => self.stack.clear(),
572            }
573        }
574        false
575    }
576
577    fn take1(&mut self) -> f64 {
578        let v = self.stack.first().copied().unwrap_or(0.0);
579        self.stack.clear();
580        v
581    }
582
583    fn take2(&mut self) -> (f64, f64) {
584        let a = self.stack.first().copied().unwrap_or(0.0);
585        let b = self.stack.get(1).copied().unwrap_or(0.0);
586        self.stack.clear();
587        (a, b)
588    }
589
590    fn after_move(&mut self) {
591        if let Some(pts) = &mut self.flex {
592            pts.push((self.x, self.y));
593        } else {
594            let (x, y) = (self.x, self.y);
595            self.move_to(x, y);
596        }
597    }
598
599    fn rrcurveto(&mut self, dx1: f64, dy1: f64, dx2: f64, dy2: f64, dx3: f64, dy3: f64) {
600        let x1 = self.x + dx1;
601        let y1 = self.y + dy1;
602        let x2 = x1 + dx2;
603        let y2 = y1 + dy2;
604        self.x = x2 + dx3;
605        self.y = y2 + dy3;
606        let (x, y) = (self.x, self.y);
607        self.curve_to(x1, y1, x2, y2, x, y);
608    }
609
610    fn callothersubr(&mut self) {
611        let Some(othersubr) = self.stack.pop() else {
612            return;
613        };
614        let Some(n) = self.stack.pop() else { return };
615        let n = (n.max(0.0) as usize).min(self.stack.len());
616        let args: Vec<f64> = self.stack.split_off(self.stack.len() - n);
617        match othersubr as i64 {
618            1 => {
619                // Flex start: the next rmovetos collect points.
620                self.flex = Some(Vec::new());
621            }
622            0 => {
623                // Flex end: 17 args in PostScript terms; here the collected
624                // points carry the geometry, the args' last two are the end
625                // point handed back through two `pop`s.
626                if let Some(pts) = self.flex.take() {
627                    // The first collected point is the reference point.
628                    if pts.len() >= 7 {
629                        let p = &pts[1..7];
630                        let (sx, sy) = (self.x, self.y);
631                        // Curves from the point before the flex; the current
632                        // point moved with the rmovetos, so rebuild from p.
633                        let _ = (sx, sy);
634                        self.curve_to(p[0].0, p[0].1, p[1].0, p[1].1, p[2].0, p[2].1);
635                        self.curve_to(p[3].0, p[3].1, p[4].0, p[4].1, p[5].0, p[5].1);
636                        self.x = p[5].0;
637                        self.y = p[5].1;
638                    } else if let Some(last) = pts.last() {
639                        self.line_to(last.0, last.1);
640                    }
641                }
642                let end_y = self.y;
643                let end_x = self.x;
644                // `pop pop setcurrentpoint` → x then y.
645                self.ps_stack = vec![end_y, end_x];
646            }
647            2 => {}
648            3 => {
649                // Hint replacement: `subr# 1 3 callothersubr pop callsubr`.
650                self.ps_stack = vec![3.0];
651                let _ = args;
652            }
653            _ => {
654                // Unknown: hand the arguments back to the following `pop`s.
655                let mut a = args;
656                a.reverse();
657                self.ps_stack = a;
658            }
659        }
660    }
661
662    fn seac(&mut self, asb: f64, adx: f64, ady: f64, bchar: i64, achar: i64) {
663        let name_of = |c: i64| -> Option<&'static str> {
664            if !(0..=255).contains(&c) {
665                return None;
666            }
667            encodings::lookup(encodings::STANDARD, c as u8)
668        };
669        let (Some(bname), Some(aname)) = (name_of(bchar), name_of(achar)) else {
670            return;
671        };
672        let sbx = self.sbx;
673        let width = self.width;
674        if let Some(base) = self.font.glyph(bname).and_then(|g| g.path) {
675            self.path.push_path(&base);
676        }
677        if let Some(acc) = self.font.glyph_raw(aname) {
678            let dx = sbx - acc.sbx + adx - asb;
679            let dy = ady;
680            if let Some(p) = acc
681                .path
682                .transform(tiny_skia::Transform::from_translate(dx as f32, dy as f32))
683            {
684                self.path.push_path(&p);
685            }
686        }
687        self.width = width;
688        self.open = false;
689    }
690}
691
692struct RawGlyph {
693    path: tiny_skia::Path,
694    sbx: f64,
695}
696
697impl Type1Font {
698    /// A glyph with its side bearing, for accent placement in `seac`.
699    fn glyph_raw(&self, name: &str) -> Option<RawGlyph> {
700        let cs = self.charstrings.get(name)?;
701        let mut st = Interp {
702            font: self,
703            path: tiny_skia::PathBuilder::new(),
704            stack: Vec::new(),
705            ps_stack: Vec::new(),
706            x: 0.0,
707            y: 0.0,
708            width: 0.0,
709            sbx: 0.0,
710            sby: 0.0,
711            flex: None,
712            open: false,
713            depth: 1,
714        };
715        st.run(cs);
716        st.close();
717        let sbx = st.sbx;
718        Some(RawGlyph {
719            path: st.path.finish()?,
720            sbx,
721        })
722    }
723}
724
725#[cfg(test)]
726mod tests {
727    use super::*;
728
729    /// Encrypt with the charstring/eexec cipher (the inverse of `decrypt`).
730    fn encrypt(data: &[u8], mut r: u16, lead: usize) -> Vec<u8> {
731        const C1: u16 = 52845;
732        const C2: u16 = 22719;
733        let mut out = Vec::new();
734        let plain: Vec<u8> = std::iter::repeat_n(0u8, lead)
735            .chain(data.iter().copied())
736            .collect();
737        for &p in &plain {
738            let c = p ^ (r >> 8) as u8;
739            r = (u16::from(c))
740                .wrapping_add(r)
741                .wrapping_mul(C1)
742                .wrapping_add(C2);
743            out.push(c);
744        }
745        out
746    }
747
748    fn num(v: i32) -> Vec<u8> {
749        if (-107..=107).contains(&v) {
750            vec![(v + 139) as u8]
751        } else {
752            let mut out = vec![255];
753            out.extend_from_slice(&v.to_be_bytes());
754            out
755        }
756    }
757
758    /// A square glyph: `50 600 hsbw 0 0 rmoveto 500 hlineto 500 vlineto -500 hlineto closepath endchar`.
759    #[test]
760    fn parses_a_synthetic_program() {
761        let mut cs = Vec::new();
762        cs.extend(num(50));
763        cs.extend(num(600));
764        cs.push(13);
765        cs.extend(num(0));
766        cs.extend(num(0));
767        cs.push(21);
768        cs.extend(num(500));
769        cs.push(6);
770        cs.extend(num(500));
771        cs.push(7);
772        cs.extend(num(-500));
773        cs.push(6);
774        cs.push(9);
775        cs.push(14);
776        let enc_cs = encrypt(&cs, CHARSTRING_R, 4);
777        let mut private = Vec::new();
778        private.extend_from_slice(
779            b"dup /Private 8 dict dup begin /lenIV 4 def /Subrs 1 array\ndup 0 1 RD ",
780        );
781        private.extend(encrypt(&[11], CHARSTRING_R, 4));
782        private.extend_from_slice(b" NP\n/CharStrings 2 dict dup begin\n/square ");
783        private.extend_from_slice(format!("{} RD ", enc_cs.len()).as_bytes());
784        private.extend(&enc_cs);
785        private.extend_from_slice(b" ND\n/.notdef 1 RD ");
786        private.extend(encrypt(&[14], CHARSTRING_R, 4));
787        private.extend_from_slice(b" ND\nend\n");
788        let mut data = Vec::new();
789        data.extend_from_slice(
790            b"%!PS-AdobeFont-1.0: Test\n/FontMatrix [0.001 0 0 0.001 0 0] readonly def\n/Encoding 256 array\n0 1 255 {1 index exch /.notdef put} for\ndup 65 /square put\nreadonly def\ncurrentdict end\ncurrentfile eexec\n",
791        );
792        data.extend(encrypt(&private, EEXEC_R, 4));
793        let font = Type1Font::parse(&data).expect("parses");
794        assert_eq!(font.encoding.get(&65).map(String::as_str), Some("square"));
795        assert_eq!(font.font_matrix[0], 0.001);
796        let g = font.glyph("square").expect("glyph");
797        assert_eq!(g.advance, 600.0);
798        let b = g.path.as_ref().expect("path").bounds();
799        assert_eq!(
800            (b.left(), b.top(), b.right(), b.bottom()),
801            (50.0, 0.0, 550.0, 500.0)
802        );
803    }
804}