Skip to main content

read_fonts/ps/
type1.rs

1//! Type1 fonts.
2
3use super::{
4    charmap::Charmap,
5    cs::{self, CharstringContext, CharstringKind, CommandSink, NopFilterSink, TransformSink},
6    encoding::PredefinedEncoding,
7    error::Error,
8    transform::{self, FontMatrix, ScaledFontMatrix, Transform},
9};
10use crate::{
11    model::pen::OutlinePen,
12    types::{BoundingBox, Fixed, GlyphId},
13    ReadError,
14};
15use alloc::{string::String, vec::Vec};
16use core::ops::Range;
17
18/// A Type1 font.
19pub struct Type1Font {
20    name: Option<String>,
21    full_name: Option<String>,
22    family_name: Option<String>,
23    weight: Option<String>,
24    bbox: BoundingBox<Fixed>,
25    italic_angle: i32,
26    is_fixed_pitch: bool,
27    underline_position: i32,
28    underline_thickness: i32,
29    matrix: ScaledFontMatrix,
30    charstrings: Charstrings,
31    subrs: Subrs,
32    encoding: Option<RawEncoding>,
33    weight_vector: Vec<Fixed>,
34    unicode_charmap: Charmap,
35}
36
37impl Type1Font {
38    /// Creates a new Type1 font from the given data.
39    pub fn new(data: &[u8]) -> Result<Self, Error> {
40        // Any failure to parse is simply represented by an invalid font format
41        // error
42        Self::new_impl(data).ok_or(Error::InvalidFontFormat)
43    }
44
45    fn new_impl(data: &[u8]) -> Option<Self> {
46        let raw_dicts = RawDicts::new(data)?;
47        Self::from_dicts(raw_dicts.base, &raw_dicts.private)
48    }
49
50    fn empty() -> Self {
51        Self {
52            name: None,
53            full_name: None,
54            family_name: None,
55            weight: None,
56            italic_angle: 0,
57            is_fixed_pitch: false,
58            underline_position: 0,
59            underline_thickness: 0,
60            matrix: ScaledFontMatrix {
61                matrix: FontMatrix::IDENTITY,
62                scale: 1000,
63            },
64            bbox: BoundingBox::default(),
65            charstrings: Charstrings::default(),
66            subrs: Subrs::default(),
67            encoding: None,
68            weight_vector: Vec::new(),
69            unicode_charmap: Charmap::default(),
70        }
71    }
72
73    fn from_dicts(base: &[u8], private: &[u8]) -> Option<Self> {
74        let mut font = Self::empty();
75        // Read base dict entries
76        let mut encoding_offset = None;
77        let mut parser = Parser::new(base);
78        while let Some(token) = parser.next() {
79            match token {
80                Token::Name(b"FontName") => font.name = parser.read_string(),
81                Token::Name(b"FullName") => font.full_name = parser.read_string(),
82                Token::Name(b"FamilyName") => font.family_name = parser.read_string(),
83                Token::Name(b"Weight") => {
84                    // The /Weight token can appear elsewhere in MM fonts so take
85                    // the first one
86                    if font.weight.is_none() {
87                        font.weight = parser.read_string();
88                    }
89                }
90                Token::Name(b"ItalicAngle") => {
91                    font.italic_angle = parser.read_num_as_int().unwrap_or(0)
92                }
93                Token::Name(b"IsFixedPitch") => {
94                    font.is_fixed_pitch = parser.next() == Some(Token::Raw(b"true"))
95                }
96                Token::Name(b"UnderlinePosition") => {
97                    font.underline_position = parser.read_num_as_int().unwrap_or(0);
98                }
99                Token::Name(b"UnderlineThickness") => {
100                    font.underline_thickness = parser.read_num_as_int().unwrap_or(0);
101                }
102                Token::Name(b"FontBBox") => {
103                    if let Some([x_min, y_min, x_max, y_max]) = parser.read_font_bbox() {
104                        font.bbox = BoundingBox {
105                            x_min,
106                            y_min,
107                            x_max,
108                            y_max,
109                        };
110                    }
111                }
112                Token::Name(b"FontMatrix") => font.matrix = parser.read_font_matrix()?,
113                // Simply save the encoding offset. We'll parse it after
114                // we have read charstrings so we have an accurate mapping
115                // if we've synthesized or remapped a notdef glyph
116                Token::Name(b"Encoding") => encoding_offset = Some(parser.pos),
117                Token::Name(b"WeightVector") => {
118                    // Gated on a successful read because some fonts reference
119                    // the /WeightVector name outside of a proc, leading to
120                    // spurious field reads
121                    if let Some(weights) = parser.read_weight_vector() {
122                        font.weight_vector = weights;
123                    }
124                }
125                _ => {}
126            }
127        }
128        // Read private dict entries
129        let mut parser = Parser::new(private);
130        // Default value if not present
131        let mut len_iv = 4;
132        while let Some(token) = parser.next() {
133            match token {
134                Token::Name(b"lenIV") => len_iv = parser.read_int()?,
135                Token::Name(b"Subrs") => {
136                    // With synthetic fonts, it's possible to read subroutines
137                    // twice. FreeType ignores the second copy.
138                    // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L1855>
139                    if font.subrs.index.is_empty() {
140                        font.subrs = parser.read_subrs(len_iv)?;
141                    }
142                }
143                Token::Name(b"CharStrings") => {
144                    // Some non-standard fonts provide multiple copies of
145                    // outlines for different resolutions and FreeType only
146                    // retains the first copy, so skip parsing if we've
147                    // already read some charstrings.
148                    // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L2058>
149                    if font.charstrings.index.is_empty() {
150                        font.charstrings = parser.read_charstrings(len_iv)?;
151                    }
152                }
153                _ => {}
154            }
155        }
156        // Reject fonts that are missing a /CharStrings array
157        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L2665>
158        if font.charstrings.index.is_empty() {
159            return None;
160        }
161        if let Some(encoding_offset) = encoding_offset {
162            let mut parser = Parser::new(base.get(encoding_offset..)?);
163            font.encoding = Some(parser.read_encoding(&font.charstrings)?);
164        }
165        // We can only generate a Unicode cmap if we have the AGL available
166        #[cfg(feature = "agl")]
167        {
168            font.unicode_charmap = Charmap::from_glyph_names(font.glyph_names());
169        }
170        Some(font)
171    }
172
173    /// Returns the PostScript name.
174    pub fn name(&self) -> Option<&str> {
175        self.name.as_deref()
176    }
177
178    /// Returns the full font name.
179    pub fn full_name(&self) -> Option<&str> {
180        self.full_name.as_deref()
181    }
182
183    /// Returns the font family name.
184    pub fn family_name(&self) -> Option<&str> {
185        self.family_name.as_deref()
186    }
187
188    /// Returns the weight or style name.
189    pub fn weight(&self) -> Option<&str> {
190        self.weight.as_deref()
191    }
192
193    /// Returns the italic angle.
194    pub fn italic_angle(&self) -> i32 {
195        self.italic_angle
196    }
197
198    /// Returns true if the glyphs in this font have the same width.
199    pub fn is_fixed_pitch(&self) -> bool {
200        self.is_fixed_pitch
201    }
202
203    /// Returns the position of the top of an underline decoration.
204    pub fn underline_position(&self) -> i32 {
205        self.underline_position
206    }
207
208    /// Returns the suggested size for an underline decoration.
209    pub fn underline_thickness(&self) -> i32 {
210        self.underline_thickness
211    }
212
213    /// Returns the font bounding box.
214    pub fn bbox(&self) -> BoundingBox<Fixed> {
215        self.bbox
216    }
217
218    /// Returns the number of glyphs in the Type1 font.
219    pub fn num_glyphs(&self) -> u32 {
220        self.charstrings.num_glyphs()
221    }
222
223    /// Returns the units per em.
224    pub fn upem(&self) -> i32 {
225        self.matrix.scale
226    }
227
228    /// Returns the top level font matrix.
229    pub fn matrix(&self) -> FontMatrix {
230        self.matrix.matrix
231    }
232
233    /// Returns the appropriate transform for adjusting points and metrics.
234    pub fn transform(&self, ppem: Option<f32>) -> Transform {
235        let scale = ppem.map(|ppem| Transform::compute_scale(ppem, self.upem()));
236        Transform {
237            matrix: self.matrix(),
238            scale,
239        }
240    }
241
242    /// Returns the character encoding.
243    pub fn encoding(&self) -> Option<Encoding<'_>> {
244        self.encoding.as_ref().map(|enc| Encoding {
245            encoding: enc,
246            charstrings: &self.charstrings,
247        })
248    }
249
250    /// Returns the Unicode charmap for this font.
251    ///
252    /// Note that this is an empty mapping if the `agl` feature is not enabled.
253    pub fn unicode_charmap(&self) -> &Charmap {
254        &self.unicode_charmap
255    }
256
257    /// Returns the glyph name for the given id.
258    pub fn glyph_name(&self, gid: GlyphId) -> Option<&str> {
259        self.charstrings.name(gid.to_u32())
260    }
261
262    /// Returns an iterator over the pairs of glyph ids and associated names in
263    /// the Type1 font.
264    pub fn glyph_names(&self) -> impl Iterator<Item = (GlyphId, &str)> {
265        (0..self.num_glyphs())
266            .filter_map(|idx| Some((GlyphId::new(idx), self.charstrings.name(idx)?)))
267    }
268
269    /// Given a glyph identifier in the original glyph order, returns the
270    /// possibly remapped identifier.
271    ///
272    /// This occurs if we remap or synthesize a `.notdef` glyph.
273    pub fn remapped_gid(&self, original_gid: GlyphId) -> GlyphId {
274        if let Some(orig_notdef) = self.charstrings.orig_notdef_index {
275            if original_gid == GlyphId::NOTDEF {
276                GlyphId::new(orig_notdef as u32)
277            } else if orig_notdef == original_gid.to_u32() as usize {
278                GlyphId::NOTDEF
279            } else {
280                original_gid
281            }
282        } else {
283            original_gid
284        }
285    }
286
287    /// Evaluates the charstring for the requested glyph and sends the results
288    /// to the given sink.
289    ///
290    /// Returns the advance with of the glyph in font units if the charstring
291    /// provides one.
292    pub fn evaluate_charstring(
293        &self,
294        gid: GlyphId,
295        sink: &mut impl CommandSink,
296    ) -> Result<Option<Fixed>, Error> {
297        let charstring_data = self
298            .charstrings
299            .get(gid.to_u32())
300            .ok_or(ReadError::OutOfBounds)?;
301        cs::evaluate(self, None, charstring_data, sink)
302    }
303
304    /// Draws the glyph with an optional size in ppem to the given pen.
305    ///
306    /// Returns the advance width of the glyph if the charstring provides
307    /// one.
308    pub fn draw(
309        &self,
310        gid: GlyphId,
311        ppem: Option<f32>,
312        pen: &mut impl OutlinePen,
313    ) -> Result<Option<f32>, Error> {
314        let mut nop_filter = NopFilterSink::new(pen);
315        let transform = self.transform(ppem);
316        let mut transformer = TransformSink::new(&mut nop_filter, transform);
317        let width = self.evaluate_charstring(gid, &mut transformer)?;
318        Ok(width.map(|w| transform.transform_h_metric(w).to_f32().max(0.0)))
319    }
320}
321
322impl CharstringContext for Type1Font {
323    fn kind(&self) -> CharstringKind {
324        CharstringKind::Type1
325    }
326
327    fn seac_components(&self, base_code: i32, accent_code: i32) -> Result<[&[u8]; 2], Error> {
328        let decode = |code: i32| {
329            let name = PredefinedEncoding::Standard
330                .name(code.try_into().map_err(|_| Error::InvalidSeacCode(code))?);
331            self.charstrings
332                .index_for_name(name)
333                .and_then(|idx| self.charstrings.get(idx))
334                .ok_or(Error::InvalidSeacCode(code))
335        };
336        let base = decode(base_code)?;
337        let accent = decode(accent_code)?;
338        Ok([base, accent])
339    }
340
341    fn subr(&self, index: i32) -> Result<&[u8], Error> {
342        Ok(self.subrs.get(index as u32).ok_or(ReadError::OutOfBounds)?)
343    }
344
345    fn global_subr(&self, _index: i32) -> Result<&[u8], Error> {
346        // Type1 fonts don't have global subroutines
347        Err(Error::MissingSubroutines)
348    }
349
350    fn weight_vector(&self) -> &[Fixed] {
351        &self.weight_vector
352    }
353}
354
355/// Associates character codes with glyph names and ids.
356#[derive(Clone)]
357pub struct Encoding<'a> {
358    encoding: &'a RawEncoding,
359    charstrings: &'a Charstrings,
360}
361
362impl<'a> Encoding<'a> {
363    /// Returns the predefined encoding, if any.
364    pub fn predefined(&self) -> Option<PredefinedEncoding> {
365        if let RawEncoding::Predefined(pre) = self.encoding {
366            Some(*pre)
367        } else {
368            None
369        }
370    }
371
372    /// Returns the glyph name for the given character code.
373    pub fn glyph_name(&self, code: u8) -> Option<&'a str> {
374        match self.encoding {
375            RawEncoding::Predefined(pre) => Some(pre.name(code)),
376            RawEncoding::Custom(custom) => {
377                self.charstrings.name(custom.get(code as usize)?.to_u32())
378            }
379        }
380    }
381
382    /// Maps a character code to a glyph identifier.
383    pub fn map(&self, code: u8) -> Option<GlyphId> {
384        match self.encoding {
385            RawEncoding::Predefined(pre) => self
386                .charstrings
387                .index_for_name(pre.name(code))
388                .map(GlyphId::new),
389            RawEncoding::Custom(custom) => custom.get(code as usize).copied(),
390        }
391    }
392}
393
394/// Raw dictionary data for a Type1 font.
395struct RawDicts<'a> {
396    /// Data containing the base dicitionary.
397    base: &'a [u8],
398    /// Data containing the decrypted private dictionary.
399    private: Vec<u8>,
400}
401
402impl<'a> RawDicts<'a> {
403    fn new(data: &'a [u8]) -> Option<Self> {
404        if let Some((PFB_TEXT_SEGMENT_TAG, base_size)) = decode_pfb_tag(data, 0) {
405            // We have a PFB; skip the tag
406            let data = data.get(6..)?;
407            verify_header(data)?;
408            let (base_dict, raw_private_dict) = data.split_at_checked(base_size as usize)?;
409            // Decrypt private dict segments
410            let private_dict = decrypt(
411                decode_pfb_binary_segments(raw_private_dict)
412                    .flat_map(|segment| segment.iter().copied()),
413                EEXEC_SEED,
414            )
415            // First four bytes are random garbage
416            .skip(4)
417            .collect::<Vec<_>>();
418            Some(Self {
419                base: base_dict,
420                private: private_dict,
421            })
422        } else {
423            // We have a PFA
424            verify_header(data)?;
425            // Now find the start of the private dictionary
426            let start = find_eexec_data(data)?;
427            let (base_dict, raw_private_dict) = data.split_at_checked(start)?;
428            let private_dict = if raw_private_dict.len() > 3
429                && raw_private_dict[..4].iter().all(|b| b.is_ascii_hexdigit())
430            {
431                // Hex decode and then decrypt
432                decrypt(decode_hex(raw_private_dict.iter().copied()), EEXEC_SEED)
433                    .skip(4)
434                    .collect::<Vec<_>>()
435            } else {
436                // Just decrypt
437                decrypt(raw_private_dict.iter().copied(), EEXEC_SEED)
438                    .skip(4)
439                    .collect::<Vec<_>>()
440            };
441            Some(Self {
442                base: base_dict,
443                private: private_dict,
444            })
445        }
446    }
447}
448
449fn verify_header(data: &[u8]) -> Option<()> {
450    (data.starts_with(b"%!PS-AdobeFont") || data.starts_with(b"%!FontType")).then_some(())
451}
452
453const PFB_TEXT_SEGMENT_TAG: u16 = 0x8001;
454const PFB_BINARY_SEGMENT_TAG: u16 = 0x8002;
455
456/// Returns the PFB tag and segment size.
457///
458/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1parse.c#L69>
459fn decode_pfb_tag(data: &[u8], start: usize) -> Option<(u16, u32)> {
460    let header: [u8; 6] = data.get(start..start + 6)?.try_into().ok()?;
461    let tag = ((header[0] as u16) << 8) | header[1] as u16;
462    if matches!(tag, PFB_BINARY_SEGMENT_TAG | PFB_TEXT_SEGMENT_TAG) {
463        let size = u32::from_le_bytes(header[2..].try_into().unwrap());
464        Some((tag, size))
465    } else {
466        None
467    }
468}
469
470/// Returns an iterator over the sequence of PFB binary segments.
471fn decode_pfb_binary_segments(data: &[u8]) -> impl Iterator<Item = &[u8]> + '_ {
472    let mut pos = 0usize;
473    core::iter::from_fn(move || {
474        let (tag, len) = decode_pfb_tag(data, pos)?;
475        // FT only decodes the sequence of binary segments here
476        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1parse.c#L286>
477        if tag != PFB_BINARY_SEGMENT_TAG {
478            return None;
479        }
480        // Skip tag and size bytes
481        let start = pos + 6;
482        let end = start + len as usize;
483        let segment = data.get(start..end)?;
484        pos = end;
485        Some(segment)
486    })
487}
488
489/// Helper to find the position of the data following the 'eexec' token.
490///
491/// Unsurprisingly, more complicated than it should be.
492fn find_eexec_data(data: &[u8]) -> Option<usize> {
493    // Use a parser to avoid catching "eexec" in a comment or string
494    // which apparently occurs in some fonts.
495    let mut parser = Parser::new(data);
496    while let Some(token) = parser.next() {
497        if token != Token::Raw(b"eexec") {
498            continue;
499        }
500        let mut start = parser.pos;
501        // FreeType has some unfun logic for skipping whitespace
502        // after the eexec token
503        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1parse.c#L382>
504        let mut linefeed_pos = None;
505        while start < data.len() {
506            match data[start] {
507                b' ' | b'\t' => {}
508                b'\n' => linefeed_pos = Some(start),
509                b'\r' => {
510                    // If we've already seen \n or there is not a \n later
511                    // in the data, then stop at this \r
512                    if *linefeed_pos.get_or_insert_with(|| {
513                        data[start..]
514                            .iter()
515                            .position(|b| *b == b'\n')
516                            .map(|pos| pos + start)
517                            .unwrap_or(0)
518                    }) < start
519                    {
520                        break;
521                    }
522                }
523                _ => break,
524            }
525            start += 1;
526        }
527        if start == data.len() {
528            // eexec not properly terminated
529            return None;
530        }
531        return Some(start);
532    }
533    None
534}
535
536/// Converts hex formatted data to associated bytes.
537///
538/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psconv.c#L464>
539fn decode_hex(mut bytes: impl Iterator<Item = u8>) -> impl Iterator<Item = u8> {
540    /// Converts digits (as ASCII characters) into integer values.
541    const DIGIT_TO_NUM: [i8; 128] = [
542        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
543        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
544        -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15,
545        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1,
546        -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
547        30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
548    ];
549    let mut pad = 0x1_u32;
550    core::iter::from_fn(move || {
551        loop {
552            let Some(c) = bytes.next() else {
553                break;
554            };
555            if is_whitespace(c) {
556                continue;
557            }
558            if c >= 0x80 {
559                break;
560            }
561            let c = DIGIT_TO_NUM[(c & 0x7F) as usize] as u32;
562            if c >= 16 {
563                break;
564            }
565            pad = (pad << 4) | c;
566            if pad & 0x100 != 0 {
567                let res = pad as u8;
568                pad = 0x1;
569                return Some(res);
570            } else {
571                continue;
572            }
573        }
574        if pad != 0x1 {
575            let res = (pad << 4) as u8;
576            pad = 0x1;
577            return Some(res);
578        }
579        None
580    })
581}
582
583/// Decryption seed for eexec segment.
584const EEXEC_SEED: u32 = 55665;
585
586/// Decryption seed for charstring (and subroutine) data.
587const CHARSTRING_SEED: u32 = 4330;
588
589/// Returns an iterator yielding the decrypted bytes.
590///
591/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psconv.c#L557>
592fn decrypt(bytes: impl Iterator<Item = u8>, mut seed: u32) -> impl Iterator<Item = u8> {
593    bytes.map(move |b| {
594        let b = b as u32;
595        let plain = b ^ (seed >> 8);
596        seed = b.wrapping_add(seed).wrapping_mul(52845).wrapping_add(22719) & 0xFFFF;
597        plain as u8
598    })
599}
600
601fn is_whitespace(c: u8) -> bool {
602    if c <= 32 {
603        return matches!(c, b' ' | b'\n' | b'\r' | b'\t' | b'\0' | 0x0C);
604    }
605    false
606}
607
608/// Characters that always delimit tokens.
609///
610/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/include/freetype/internal/psaux.h#L1398>
611fn is_special(c: u8) -> bool {
612    matches!(
613        c,
614        b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
615    )
616}
617
618fn is_special_or_whitespace(c: u8) -> bool {
619    is_special(c) || is_whitespace(c)
620}
621
622#[derive(Copy, Clone, PartialEq, Eq, Debug)]
623enum Token<'a> {
624    /// Integers
625    Int(i64),
626    /// Literal strings, delimited by ()
627    LitString(&'a [u8]),
628    /// Hex strings, delimited by <>
629    HexString(&'a [u8]),
630    /// Procedures, delimited by {}
631    Proc(&'a [u8]),
632    /// Binary blobs
633    Binary(&'a [u8]),
634    /// Names, preceded by /
635    Name(&'a [u8]),
636    /// All other raw tokens (identifiers and self-delimiting punctuation)
637    Raw(&'a [u8]),
638}
639
640/// Collection of subroutines.
641#[derive(Default)]
642struct Subrs {
643    /// Packed data for all subroutines.
644    data: Vec<u8>,
645    /// Index mapping subroutine number to range in the packed data. Sorted
646    /// by subroutine number.
647    index: Vec<(u32, Range<usize>)>,
648    /// If true, subroutine number == index so we don't need to
649    /// bsearch.
650    is_dense: bool,
651}
652
653impl Subrs {
654    fn get(&self, index: u32) -> Option<&[u8]> {
655        let entry_idx = if self.is_dense {
656            index as usize
657        } else {
658            self.index.binary_search_by_key(&index, |e| e.0).ok()?
659        };
660        self.data.get(self.index.get(entry_idx)?.1.clone())
661    }
662}
663
664struct CharstringEntry {
665    name: Range<usize>,
666    data: Range<usize>,
667}
668
669/// Collection of charstrings.
670#[derive(Default)]
671struct Charstrings {
672    /// Packed data for all charstrings.
673    data: Vec<u8>,
674    /// Packed data for all glyph names.
675    names: Vec<u8>,
676    /// Index containing all charstrings.
677    index: Vec<CharstringEntry>,
678    /// If notdef was remapped, holds the original index of the notdef
679    /// charstring.
680    orig_notdef_index: Option<usize>,
681}
682
683impl Charstrings {
684    fn num_glyphs(&self) -> u32 {
685        self.index.len() as u32
686    }
687
688    fn get(&self, index: u32) -> Option<&[u8]> {
689        self.data.get(self.index.get(index as usize)?.data.clone())
690    }
691
692    fn name(&self, index: u32) -> Option<&str> {
693        core::str::from_utf8(
694            self.names
695                .get(self.index.get(index as usize)?.name.clone())?,
696        )
697        .ok()
698    }
699
700    fn index_for_name(&self, name: &str) -> Option<u32> {
701        let name = name.as_bytes();
702        for (idx, entry) in self.index.iter().enumerate() {
703            if self.names.get(entry.name.clone()) == Some(name) {
704                return Some(idx as u32);
705            }
706        }
707        None
708    }
709
710    fn push(&mut self, name: &[u8], data: &[u8], len_iv: i64) {
711        let start = self.data.len();
712        if len_iv >= 0 {
713            // use decryption; skip first len_iv bytes
714            self.data
715                .extend(decrypt(data.iter().copied(), CHARSTRING_SEED).skip(len_iv as usize));
716        } else {
717            // just add the data
718            self.data.extend_from_slice(data);
719        }
720        let end = self.data.len();
721        let name_start = self.names.len();
722        self.names.extend_from_slice(name);
723        let name_end = self.names.len();
724        self.index.push(CharstringEntry {
725            name: name_start..name_end,
726            data: start..end,
727        });
728    }
729}
730
731/// Encoding that maps characters to glyph identifiers.
732#[derive(PartialEq, Debug)]
733enum RawEncoding {
734    Predefined(PredefinedEncoding),
735    Custom(Vec<GlyphId>),
736}
737
738/// Simulated .notdef glyph, same as FreeType:
739///
740/// 0 333 hsbw endchar
741///
742/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L2192>
743const NOTDEF_GLYPH: &[u8] = &[0x8B, 0xF7, 0xE1, 0x0D, 0x0E];
744
745#[derive(Clone)]
746struct Parser<'a> {
747    data: &'a [u8],
748    pos: usize,
749}
750
751impl<'a> Parser<'a> {
752    fn new(data: &'a [u8]) -> Self {
753        Self { data, pos: 0 }
754    }
755
756    fn next(&mut self) -> Option<Token<'a>> {
757        // Roughly follows the logic of ps_parser_skip_PS_token
758        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psobjs.c#L482>
759        loop {
760            self.skip_whitespace()?;
761            let start = self.pos;
762            let c = self.next_byte()?;
763            match c {
764                // Line comment
765                b'%' => self.skip_line(),
766                // Procedures
767                b'{' => return self.read_proc(start),
768                // Literal strings
769                b'(' => return self.read_lit_string(start),
770                b'<' => {
771                    if self.peek_byte() == Some(b'<') {
772                        // Just ignore these
773                        self.pos += 1;
774                        continue;
775                    }
776                    // Hex string: hex digits and whitespace
777                    return self.read_hex_string(start);
778                }
779                b'>' => {
780                    // We consume single '>' when parsing hex strings so a
781                    // double >> is expected here
782                    if self.next_byte()? != b'>' {
783                        return None;
784                    }
785                }
786                // Name
787                b'/' => {
788                    if let Some(c) = self.peek_byte() {
789                        if is_whitespace(c) || is_special(c) {
790                            if !is_special(c) {
791                                self.pos += 1;
792                            }
793                            return Some(Token::Name(&[]));
794                        } else {
795                            let count = self.skip_until(|c| is_whitespace(c) || is_special(c));
796                            return self.data.get(start + 1..start + count).map(Token::Name);
797                        }
798                    }
799                }
800                // Brackets
801                b'[' | b']' => {
802                    let data = self.data.get(start..start + 1)?;
803                    return Some(Token::Raw(data));
804                }
805                _ => {
806                    let count = self.skip_until(is_special_or_whitespace);
807                    let content = self.data.get(start..start + count)?;
808                    // Look for numbers but don't try to parse fractional
809                    // values since we want to handle those with special
810                    // precision
811                    if (c.is_ascii_digit() || c == b'-') && !content.contains(&b'.') {
812                        if let Some(int) = decode_int(content) {
813                            // HACK: if we have an int followed by RD or -|
814                            // then is a binary blob in Type1. Hack because
815                            // this is not actually how PostScript works
816                            // but Type1 fonts define /RD procs and this
817                            // pattern is used by FreeType.
818                            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L1351>
819                            if matches!(
820                                self.peek(),
821                                Some(Token::Raw(b"RD")) | Some(Token::Raw(b"-|"))
822                            ) {
823                                // skip the token
824                                self.next();
825                                // and a single space
826                                self.pos += 1;
827                                // read the internal data
828                                let data = self.read_bytes(int as usize)?;
829                                // there's often some form of terminator here
830                                // but let the calling code handle it because
831                                // some buggy fonts may omit it
832                                return Some(Token::Binary(data));
833                            }
834                            return Some(Token::Int(int));
835                        }
836                    }
837                    return Some(Token::Raw(content));
838                }
839            }
840        }
841    }
842
843    fn peek(&self) -> Option<Token<'a>> {
844        self.clone().next()
845    }
846
847    fn accept(&mut self, token: Token) -> bool {
848        if self.peek() == Some(token) {
849            self.next();
850            true
851        } else {
852            false
853        }
854    }
855
856    fn expect(&mut self, token: Token) -> Option<()> {
857        (self.next()? == token).then_some(())
858    }
859
860    fn next_byte(&mut self) -> Option<u8> {
861        let byte = self.peek_byte()?;
862        self.pos += 1;
863        Some(byte)
864    }
865
866    fn peek_byte(&self) -> Option<u8> {
867        self.data.get(self.pos).copied()
868    }
869
870    fn read_bytes(&mut self, len: usize) -> Option<&'a [u8]> {
871        let end = self.pos.checked_add(len)?;
872        let content = self.data.get(self.pos..end)?;
873        self.pos = end;
874        Some(content)
875    }
876
877    fn skip_whitespace(&mut self) -> Option<()> {
878        while is_whitespace(*self.data.get(self.pos)?) {
879            self.pos += 1;
880        }
881        Some(())
882    }
883
884    fn skip_line(&mut self) {
885        while let Some(c) = self.next_byte() {
886            if c == b'\n' || c == b'\r' {
887                break;
888            }
889        }
890    }
891
892    fn skip_until(&mut self, f: impl Fn(u8) -> bool) -> usize {
893        let mut count = 0;
894        while let Some(byte) = self.peek_byte() {
895            if f(byte) {
896                break;
897            }
898            self.pos += 1;
899            count += 1;
900        }
901        count + 1
902    }
903
904    fn read_proc(&mut self, start: usize) -> Option<Token<'a>> {
905        while self.next_byte()? != b'}' {
906            // This handles nested procedures
907            self.next()?;
908            self.skip_whitespace();
909        }
910        let end = self.pos;
911        if self.data.get(end - 1) != Some(&b'}') {
912            // unterminated procedure
913            return None;
914        }
915        Some(Token::Proc(self.data.get(start + 1..end - 1)?))
916    }
917
918    fn read_lit_string(&mut self, start: usize) -> Option<Token<'a>> {
919        let mut nest_depth = 1;
920        while let Some(c) = self.next_byte() {
921            match c {
922                b'(' => nest_depth += 1,
923                b')' => {
924                    nest_depth -= 1;
925                    if nest_depth == 0 {
926                        break;
927                    }
928                }
929                // Escape sequence
930                b'\\' => {
931                    // Just eat the next byte. We only care
932                    // about avoiding \( and \) anyway.
933                    self.next_byte()?;
934                }
935                _ => {}
936            }
937        }
938        if nest_depth != 0 {
939            // unterminated string
940            return None;
941        }
942        let end = self.pos;
943        self.pos += 1;
944        Some(Token::LitString(self.data.get(start + 1..end - 1)?))
945    }
946
947    fn read_hex_string(&mut self, start: usize) -> Option<Token<'a>> {
948        while let Some(c) = self.next_byte() {
949            if !is_whitespace(c) && !c.is_ascii_hexdigit() {
950                break;
951            }
952        }
953        let end = self.pos;
954        if self.data.get(end - 1) != Some(&b'>') {
955            // unterminated hex string
956            return None;
957        }
958        Some(Token::HexString(self.data.get(start + 1..end - 1)?))
959    }
960
961    fn read_int(&mut self) -> Option<i64> {
962        self.next().and_then(|t| match t {
963            Token::Int(n) => Some(n),
964            _ => None,
965        })
966    }
967
968    fn read_num_as_int(&mut self) -> Option<i32> {
969        match self.next()? {
970            Token::Int(n) => Some(n as i32),
971            // Note, FT calls PS_Conv_ToInt for fields that might contain
972            // fractional bits but just ignores everything after the
973            // initial integer, so we do the same
974            Token::Raw(bytes) => decode_int_prefix(bytes, 0).map(|n| n.0 as i32),
975            _ => None,
976        }
977    }
978
979    fn read_string(&mut self) -> Option<String> {
980        use alloc::borrow::ToOwned;
981        let bytes = match self.next()? {
982            // FreeType accepts a name or a string here
983            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psobjs.c#L1123>
984            Token::Name(bytes) | Token::LitString(bytes) => bytes,
985            _ => return None,
986        };
987        core::str::from_utf8(bytes).ok().map(|s| s.to_owned())
988    }
989}
990
991impl Parser<'_> {
992    /// Parse a font matrix.
993    ///
994    /// Like FreeType, this is designed assuming a upem of 1000 and produces
995    /// an identity matrix in that case. This is, the result is scaled such
996    /// that 0.001 yields a value of 1.0.
997    ///
998    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L1403>
999    fn read_font_matrix(&mut self) -> Option<ScaledFontMatrix> {
1000        let mut components = [Fixed::ZERO; 6];
1001        // accept [ or { to match FreeType
1002        if !self.accept(Token::Raw(b"[")) {
1003            self.expect(Token::Raw(b"{"))?;
1004        }
1005        // read all components
1006        for component in &mut components {
1007            *component = match self.next()? {
1008                Token::Int(int) => Fixed::from_i32((int as i32).checked_mul(1000)?),
1009                Token::Raw(bytes) => decode_fixed(bytes, 3)?,
1010                _ => return None,
1011            }
1012        }
1013        // FreeType doesn't validate the closing delimiter, so just skip
1014        self.next()?;
1015        let temp_scale = components[3].abs();
1016        if temp_scale == Fixed::ZERO {
1017            return None;
1018        }
1019        let mut upem = 1000;
1020        if temp_scale != Fixed::ONE {
1021            upem = (Fixed::from_bits(1000) / temp_scale).to_bits();
1022            components[0] /= temp_scale;
1023            components[1] /= temp_scale;
1024            components[2] /= temp_scale;
1025            // don't scale components[3]
1026            components[4] /= temp_scale;
1027            components[5] /= temp_scale;
1028            if components[3] < Fixed::ZERO {
1029                components[3] = -Fixed::ONE;
1030            } else {
1031                components[3] = Fixed::ONE;
1032            }
1033        }
1034        // offsets must be expressed in integer font units
1035        for offset in components.iter_mut().skip(4) {
1036            *offset = Fixed::from_bits(offset.to_bits() >> 16);
1037        }
1038        let matrix = FontMatrix::from_elements(components);
1039        if transform::is_degenerate(&matrix) {
1040            return None;
1041        }
1042        Some(ScaledFontMatrix {
1043            matrix,
1044            scale: upem,
1045        })
1046    }
1047
1048    /// Parse the set of subroutines.
1049    ///
1050    /// The `len_iv` parameter defines the number of prefix padding bytes for
1051    /// encrypted data. If < 0, then the data is not encrypted.
1052    ///
1053    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L1720>
1054    fn read_subrs(&mut self, len_iv: i64) -> Option<Subrs> {
1055        let mut subrs = Subrs::default();
1056        let _: usize = match self.next()? {
1057            Token::Raw(b"[") => {
1058                // Just an empty array
1059                self.expect(Token::Raw(b"]"))?;
1060                return Some(subrs);
1061            }
1062            Token::Int(n) => n.try_into().ok()?,
1063            _ => return None,
1064        };
1065        self.expect(Token::Raw(b"array"))?;
1066        let mut is_dense = true;
1067        // The pattern for each subroutine is `dup <subr_num> <data>`
1068        while self.accept(Token::Raw(b"dup")) {
1069            let (Token::Int(n), Token::Binary(data)) = (self.next()?, self.next()?) else {
1070                return None;
1071            };
1072            // Skip the NP, | or noaccess. FreeType just skips whatever happens
1073            // to be here
1074            self.next();
1075            // There might be an additional put token following the binary data
1076            self.accept(Token::Raw(b"put"));
1077            let subr_num: u32 = n.try_into().ok()?;
1078            if subr_num as usize != subrs.index.len() {
1079                is_dense = false;
1080            }
1081            let start = subrs.data.len();
1082            if len_iv >= 0 {
1083                // use decryption; skip first len_iv bytes
1084                subrs
1085                    .data
1086                    .extend(decrypt(data.iter().copied(), CHARSTRING_SEED).skip(len_iv as usize));
1087            } else {
1088                // just add the data
1089                subrs.data.extend_from_slice(data);
1090            }
1091            let end = subrs.data.len();
1092            subrs.index.push((subr_num, start..end));
1093        }
1094        // If we don't have a dense set, sort the index by number
1095        if !is_dense {
1096            subrs.index.sort_unstable_by_key(|(n, ..)| *n);
1097        }
1098        subrs.is_dense = is_dense;
1099        subrs.data.shrink_to_fit();
1100        subrs.index.shrink_to_fit();
1101        Some(subrs)
1102    }
1103
1104    /// Parse the set of charstrings.
1105    ///
1106    /// The `len_iv` parameter defines the number of prefix padding bytes for
1107    /// encrypted data. If < 0, then the data is not encrypted.
1108    ///
1109    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L1919>
1110    fn read_charstrings(&mut self, len_iv: i64) -> Option<Charstrings> {
1111        let mut charstrings = Charstrings::default();
1112        let _: usize = match self.next()? {
1113            Token::Int(n) => n.try_into().ok()?,
1114            _ => return None,
1115        };
1116        let mut notdef_idx = None;
1117        while let Some(token) = self.next() {
1118            let name = match token {
1119                // Stop when we find a `def` or `end` keyword.
1120                // The ugliness matches the FT logic to handle some malformed
1121                // fonts:
1122                // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L2006>
1123                Token::Raw(b"end") => {
1124                    if self
1125                        .peek_byte()
1126                        .map(is_special_or_whitespace)
1127                        .unwrap_or_default()
1128                    {
1129                        break;
1130                    } else {
1131                        continue;
1132                    }
1133                }
1134                Token::Raw(b"def") => {
1135                    // but ignore `def` if no charstring has been seen
1136                    if self
1137                        .peek_byte()
1138                        .map(is_special_or_whitespace)
1139                        .unwrap_or_default()
1140                        && !charstrings.index.is_empty()
1141                    {
1142                        break;
1143                    } else {
1144                        continue;
1145                    }
1146                }
1147                Token::Name(name) => name,
1148                _ => continue,
1149            };
1150            if name == b".notdef" {
1151                notdef_idx = Some(charstrings.index.len());
1152            }
1153            let Token::Binary(data) = self.next()? else {
1154                return None;
1155            };
1156            charstrings.push(name, data, len_iv);
1157        }
1158        match notdef_idx {
1159            Some(0) => {
1160                // .notdef found and at correct location
1161            }
1162            Some(idx) => {
1163                // .notdef found but at incorrect location. Swap with the
1164                // glyph at 0
1165                charstrings.index.swap(0, idx);
1166                charstrings.orig_notdef_index = Some(idx);
1167            }
1168            None => {
1169                // .notdef not found. Add it to the end and then swap with
1170                // the glyph at 0
1171                let idx = charstrings.index.len();
1172                charstrings.push(b".notdef", NOTDEF_GLYPH, -1);
1173                charstrings.index.swap(0, idx);
1174                charstrings.orig_notdef_index = Some(idx);
1175            }
1176        }
1177        charstrings.data.shrink_to_fit();
1178        charstrings.names.shrink_to_fit();
1179        charstrings.index.shrink_to_fit();
1180        Some(charstrings)
1181    }
1182
1183    fn read_font_bbox(&mut self) -> Option<[Fixed; 4]> {
1184        let mut bbox = [Fixed::ZERO; 4];
1185        // accept [ or { to match FreeType
1186        // Note that we parse { as a procedure so this needs some special
1187        // handling
1188        let mut parser;
1189        let parser = if self.accept(Token::Raw(b"[")) {
1190            self
1191        } else if let Token::Proc(proc) = self.next()? {
1192            parser = Parser::new(proc);
1193            &mut parser
1194        } else {
1195            return None;
1196        };
1197        // read all components
1198        for component in &mut bbox {
1199            *component = match parser.next()? {
1200                Token::Int(int) => Fixed::from_i32(int as i32),
1201                Token::Raw(bytes) => decode_fixed(bytes, 0)?,
1202                _ => return None,
1203            }
1204        }
1205        Some(bbox)
1206    }
1207
1208    fn read_weight_vector(&mut self) -> Option<Vec<Fixed>> {
1209        self.accept(Token::Raw(b"["));
1210        let mut weights = Vec::new();
1211        while let Some(token) = self.next() {
1212            match token {
1213                Token::Raw(b"]") => break,
1214                Token::Int(val) => weights.push(Fixed::from_i32(val as _)),
1215                Token::Raw(raw) => weights.push(decode_fixed(raw, 0)?),
1216                _ => return None,
1217            }
1218        }
1219        Some(weights)
1220    }
1221
1222    /// Parse the encoding.
1223    ///
1224    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L1474>
1225    fn read_encoding(&mut self, charstrings: &Charstrings) -> Option<RawEncoding> {
1226        match self.next()? {
1227            // Array of names where index == character code
1228            Token::Raw(b"[") => {
1229                let mut map = Vec::new();
1230                // Should always be 256 entries but preset values to notdef
1231                map.resize(256, GlyphId::NOTDEF);
1232                self.read_dense_encoding(|idx, name| {
1233                    if let Some((slot, gid)) = map
1234                        .get_mut(idx as usize)
1235                        .zip(charstrings.index_for_name(name))
1236                    {
1237                        *slot = gid.into();
1238                    }
1239                });
1240                Some(RawEncoding::Custom(map))
1241            }
1242            // Map of index to glyph name
1243            Token::Int(count) => {
1244                // We're limited to 256 character codes
1245                // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L1518>
1246                let count: usize = count.clamp(0, 256) as usize;
1247                let mut map = Vec::new();
1248                // Start with all glyphs mapped to notdef
1249                map.resize(count, GlyphId::NOTDEF);
1250                self.read_sparse_encoding(|idx, name| {
1251                    if let Some((slot, gid)) = map
1252                        .get_mut(idx as usize)
1253                        .zip(charstrings.index_for_name(name))
1254                    {
1255                        *slot = gid.into();
1256                    }
1257                });
1258                Some(RawEncoding::Custom(map))
1259            }
1260            Token::Raw(b"StandardEncoding") => {
1261                Some(RawEncoding::Predefined(PredefinedEncoding::Standard))
1262            }
1263            Token::Raw(b"ExpertEncoding") => {
1264                Some(RawEncoding::Predefined(PredefinedEncoding::Expert))
1265            }
1266            Token::Raw(b"ISOLatin1Encoding") => {
1267                Some(RawEncoding::Predefined(PredefinedEncoding::IsoLatin1))
1268            }
1269            _ => None,
1270        }
1271    }
1272
1273    /// Returns a custom encoding defined by an array of `/<name>`, where
1274    /// the index represents the character code, and invokes the given
1275    /// callback for each.
1276    fn read_dense_encoding(&mut self, mut f: impl FnMut(i64, &str)) -> Option<()> {
1277        // Eat the opening brace if present
1278        self.accept(Token::Raw(b"["));
1279        // Always expect 256 entries
1280        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L1510>
1281        let mut idx = 0;
1282        while let Some(token) = self.next() {
1283            match token {
1284                Token::Raw(b"]") => break,
1285                Token::Name(name) => {
1286                    let code = idx;
1287                    idx += 1;
1288                    let Ok(name) = core::str::from_utf8(name) else {
1289                        continue;
1290                    };
1291                    f(code, name);
1292                }
1293                _ => {
1294                    // FreeType fails if missing a literal name here
1295                    return None;
1296                }
1297            }
1298        }
1299        Some(())
1300    }
1301
1302    /// Reads a custom encoding defined by a map of `<charcode> /<name>`
1303    /// and invokes the given callback for each.
1304    fn read_sparse_encoding(&mut self, mut f: impl FnMut(i64, &str)) -> Option<()> {
1305        while let Some(token) = self.next() {
1306            match token {
1307                // The 'def' keyword ends the mapping
1308                Token::Raw(b"def") => break,
1309                Token::Int(code) => {
1310                    // read the name
1311                    let Some(Token::Name(name)) = self.next() else {
1312                        continue;
1313                    };
1314                    let Ok(name) = core::str::from_utf8(name) else {
1315                        continue;
1316                    };
1317                    f(code, name);
1318                }
1319                _ => {}
1320            }
1321        }
1322        Some(())
1323    }
1324}
1325
1326/// Decode an integer, optionally with a base.
1327///
1328/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psconv.c#L161>
1329fn decode_int(bytes: &[u8]) -> Option<i64> {
1330    let s = std::str::from_utf8(bytes).ok()?;
1331    if let Some(hash_idx) = s.find('#') {
1332        if hash_idx == 1 || hash_idx == 2 {
1333            // It's a radix number, like 8#40.
1334            let radix_str = s.get(0..hash_idx)?;
1335            let number_str = s.get(hash_idx + 1..)?;
1336            let radix = radix_str
1337                .parse::<u32>()
1338                .ok()
1339                .filter(|n| (2..=36).contains(n))?;
1340            i64::from_str_radix(number_str, radix).ok()
1341        } else {
1342            s.parse::<i64>().ok()
1343        }
1344    } else {
1345        s.parse::<i64>().ok()
1346    }
1347}
1348
1349/// Decode an integer at the given position, returning the value and the
1350/// index of the position following the decoded integer.
1351fn decode_int_prefix(bytes: &[u8], start: usize) -> Option<(i64, usize)> {
1352    let tail = bytes.get(start..)?;
1353    let end = tail
1354        .iter()
1355        .position(|c| *c != b'-' && !c.is_ascii_digit())
1356        .unwrap_or(tail.len());
1357    let int = decode_int(tail.get(..end)?)?;
1358    Some((int, start + end))
1359}
1360
1361/// Decode a fixed point value, scaling to a specific power of
1362/// ten.
1363///
1364/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psconv.c#L195>
1365fn decode_fixed(bytes: &[u8], mut power_ten: i32) -> Option<Fixed> {
1366    const LIMIT: i32 = 0xCCCCCCC;
1367    let mut idx = 0;
1368    let &first = bytes.get(idx)?;
1369    let sign = if first == b'-' || first == b'+' {
1370        idx += 1;
1371        if first == b'-' {
1372            -1
1373        } else {
1374            1
1375        }
1376    } else {
1377        1
1378    };
1379    let overflow = || Some(Fixed::from_bits(0x7FFFFFFF * sign));
1380    let mut integral = 0;
1381    if *bytes.get(idx)? != b'.' {
1382        let (int, end_idx) = decode_int_prefix(bytes, idx)?;
1383        if int > 0x7FFF {
1384            return overflow();
1385        }
1386        integral = (int << 16) as i32;
1387        idx = end_idx;
1388    }
1389    let mut decimal = 0;
1390    let mut divider = 1;
1391    if bytes.get(idx) == Some(&b'.') {
1392        idx += 1;
1393        while let Some(byte) = bytes.get(idx).copied() {
1394            if !byte.is_ascii_digit() {
1395                break;
1396            }
1397            let digit = (byte - b'0') as i32;
1398            if divider < LIMIT && decimal < LIMIT {
1399                decimal = decimal * 10 + digit;
1400                if integral == 0 && power_ten > 0 {
1401                    power_ten -= 1;
1402                } else {
1403                    divider *= 10;
1404                }
1405            }
1406            idx += 1;
1407        }
1408    }
1409    if bytes.get(idx).map(|b| b.to_ascii_lowercase()) == Some(b'e') {
1410        idx += 1;
1411        let (exponent, _) = decode_int_prefix(bytes, idx)?;
1412        if exponent > 1000 {
1413            return overflow();
1414        } else if exponent < -1000 {
1415            // underflow
1416            return Some(Fixed::ZERO);
1417        } else {
1418            power_ten = power_ten.checked_add(exponent as i32)?;
1419        }
1420    }
1421    if integral == 0 && decimal == 0 {
1422        return Some(Fixed::ZERO);
1423    }
1424    while power_ten > 0 {
1425        if integral >= LIMIT {
1426            return overflow();
1427        }
1428        integral *= 10;
1429        if decimal >= LIMIT {
1430            if divider == 1 {
1431                return overflow();
1432            }
1433            divider /= 10;
1434        } else {
1435            decimal *= 10;
1436        }
1437        power_ten -= 1;
1438    }
1439    while power_ten < 0 {
1440        integral /= 10;
1441        if divider < LIMIT {
1442            divider *= 10;
1443        } else {
1444            decimal /= 10;
1445        }
1446        if integral == 0 && decimal == 0 {
1447            return Some(Fixed::ZERO);
1448        }
1449        power_ten += 1;
1450    }
1451    if decimal != 0 {
1452        decimal = (Fixed::from_bits(decimal) / Fixed::from_bits(divider)).to_bits();
1453        integral += decimal;
1454    }
1455    Some(Fixed::from_bits(integral * sign))
1456}
1457
1458#[cfg(test)]
1459mod tests {
1460    use super::*;
1461    use cs::test_helpers::*;
1462
1463    #[test]
1464    fn pfb_tags() {
1465        // Text segment tag
1466        let data = [0x80, 0x01, 0x01, 0x02, 0x00, 0x00];
1467        let (tag, len) = decode_pfb_tag(&data, 0).unwrap();
1468        assert_eq!(tag, PFB_TEXT_SEGMENT_TAG);
1469        assert_eq!(len, 513);
1470        // Binary segment tag
1471        let data = [0x80, 0x02, 0x01, 0x03, 0x00, 0x00];
1472        let (tag, len) = decode_pfb_tag(&data, 0).unwrap();
1473        assert_eq!(tag, PFB_BINARY_SEGMENT_TAG);
1474        assert_eq!(len, 769);
1475        // Invalid tag
1476        let data = [0x00; 6];
1477        assert!(decode_pfb_tag(&data, 0).is_none());
1478        // Not enough data
1479        let data = [0x00; 5];
1480        assert!(decode_pfb_tag(&data, 0).is_none());
1481    }
1482
1483    #[test]
1484    fn pfb_segments() {
1485        let segments = [
1486            vec![0x01; 8],
1487            vec![0x02; 10],
1488            vec![0x03; 4],
1489            vec![0x04; 255],
1490        ];
1491        // Write each segment to a buffer
1492        let mut buf = vec![];
1493        for segment in &segments {
1494            buf.push(0x80);
1495            buf.push(0x02);
1496            buf.push(segment.len() as u8);
1497            buf.extend_from_slice(&[0; 3]);
1498            for byte in segment {
1499                buf.push(*byte);
1500            }
1501        }
1502        // Now parse and compare
1503        let mut parsed_count = 0;
1504        for (parsed, expected) in decode_pfb_binary_segments(&buf).zip(&segments) {
1505            assert_eq!(parsed, expected);
1506            parsed_count += 1;
1507        }
1508        assert_eq!(parsed_count, segments.len());
1509    }
1510
1511    #[test]
1512    fn hex_decode() {
1513        check_hex_decode(
1514            b"743F8413F3636CA85A9FFEFB50B4BB27",
1515            &[
1516                116, 63, 132, 19, 243, 99, 108, 168, 90, 159, 254, 251, 80, 180, 187, 39,
1517            ],
1518        );
1519    }
1520
1521    #[test]
1522    fn hex_decode_ignores_whitespace() {
1523        check_hex_decode(
1524            b"743F 8413F3636C\nA85A9FFEF\tB50B     4BB27",
1525            &[
1526                116, 63, 132, 19, 243, 99, 108, 168, 90, 159, 254, 251, 80, 180, 187, 39,
1527            ],
1528        );
1529    }
1530
1531    #[test]
1532    fn hex_decode_truncate() {
1533        check_hex_decode(b"743F.8413F3636CA85A9FFEFB50B4BB27", &[116, 63]);
1534    }
1535
1536    #[test]
1537    fn hex_decode_odd_chars() {
1538        check_hex_decode(b"743", &[116, 48]);
1539    }
1540
1541    #[track_caller]
1542    fn check_hex_decode(hex: &[u8], expected: &[u8]) {
1543        let decoded = decode_hex(hex.iter().copied()).collect::<Vec<_>>();
1544        assert_eq!(decoded, expected);
1545    }
1546
1547    #[test]
1548    fn decrypt_bytes() {
1549        let cipher = [
1550            0x74, 0x3f, 0x84, 0x13, 0xf3, 0x63, 0x6c, 0xa8, 0x5a, 0x9f, 0xfe, 0xfb, 0x50, 0xb4,
1551            0xbb, 0x27,
1552        ];
1553        let plain = decrypt(cipher.iter().copied(), EEXEC_SEED).collect::<Vec<_>>();
1554        // First 4 bytes are random garbage
1555        assert_eq!(&plain[4..], b"dup\n/Private");
1556    }
1557
1558    #[test]
1559    fn find_eexec() {
1560        // Just a space
1561        assert_eq!(
1562            find_eexec_data(b"dup\n/Private\ncurrentfile eexec *&&FW"),
1563            Some(31)
1564        );
1565        // Multiple spaces
1566        assert_eq!(
1567            find_eexec_data(b"dup\n/Private\ncurrentfile eexec     *&&FW"),
1568            Some(35)
1569        );
1570        // New lines
1571        assert_eq!(
1572            find_eexec_data(b"dup\n/Private\ncurrentfile eexec\n\n*&&FW"),
1573            Some(32)
1574        );
1575        // Only skip \r when it precedes \n
1576        assert_eq!(
1577            find_eexec_data(b"dup\n/Private\ncurrentfile eexec\r\n\r*&&FW"),
1578            Some(32)
1579        );
1580        // Skip eexec in comments and strings
1581        assert_eq!(
1582            find_eexec_data(b"% eexec in comment\n(eexec in string) currentfile eexec $$$$"),
1583            Some(55)
1584        );
1585        // No eexec
1586        assert!(find_eexec_data(b"% eexec in comment\n(eexec in string) currentfile").is_none());
1587    }
1588
1589    #[test]
1590    fn read_pfb_raw_dicts() {
1591        let dicts = RawDicts::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB).unwrap();
1592        check_noto_serif_base(dicts.base);
1593        check_noto_serif_private(&dicts.private);
1594    }
1595
1596    #[test]
1597    fn read_pfa_raw_dicts() {
1598        let dicts = RawDicts::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA).unwrap();
1599        check_noto_serif_base(dicts.base);
1600        check_noto_serif_private(&dicts.private);
1601    }
1602
1603    fn check_noto_serif_base(base: &[u8]) {
1604        const EXPECTED_PREFIX: &str = r#"%!PS-AdobeFont-1.0: NotoSerif-Regular 2.007; ttfautohint (v1.8) -l 8 -r 50 -G 200 -x 14 -D latn -f none -a qsq -X ""
1605%%Title: NotoSerif-Regular
1606%Version: 2.007; ttfautohint (v1.8) -l 8 -r 50 -G 200 -x 14 -D latn -f none -a qsq -X ""
1607%%CreationDate: Tue Feb 10 16:07:25 2026
1608%%Creator: www-data
1609%Copyright: Copyright 2015-2021 Google LLC. All Rights Reserved.
1610% Generated by FontForge 20190801 (http://fontforge.sf.net/)
1611%%EndComments
1612
161310 dict begin
1614/FontType 1 def
1615/FontMatrix [0.001 0 0 0.001 0 0 ]readonly def
1616/FontName /NotoSerif-Regular def
1617/FontBBox {5 0 989 775 }readonly def
1618"#;
1619        assert!(base.starts_with(EXPECTED_PREFIX.as_bytes()));
1620    }
1621
1622    fn check_noto_serif_private(private: &[u8]) {
1623        const EXPECTED_PREFIX: &str = r#"dup
1624/Private 8 dict dup begin
1625/RD{string currentfile exch readstring pop}executeonly def
1626/ND{noaccess def}executeonly def
1627/NP{noaccess put}executeonly def
1628/MinFeature{16 16}ND
1629/password 5839 def
1630/BlueValues [0 0 536 536 714 714 770 770 ]ND
1631/OtherSubrs"#;
1632        assert!(private.starts_with(EXPECTED_PREFIX.as_bytes()))
1633    }
1634
1635    #[test]
1636    fn parse_ints() {
1637        check_tokens(
1638            "% a comment\n20 -30 2#1011 10#-5 %another!\r 16#fC",
1639            &[
1640                Token::Int(20),
1641                Token::Int(-30),
1642                Token::Int(11),
1643                Token::Int(-5),
1644                Token::Int(252),
1645            ],
1646        );
1647    }
1648
1649    #[test]
1650    fn parse_num_to_int() {
1651        let mut parser =
1652            Parser::new(b"102 102.1 102.4 102.5 102.9 -102.1 -102.5 -102.9 8#146 16#66");
1653        for _ in 0..10 {
1654            assert_eq!(parser.read_num_as_int().unwrap().abs(), 102);
1655        }
1656        assert!(parser.next().is_none());
1657    }
1658
1659    #[test]
1660    fn parse_strings() {
1661        check_tokens(
1662            "(string (nested) 1) % and a hex string:\n <DEAD BEEF>",
1663            &[
1664                Token::LitString(b"string (nested) 1"),
1665                Token::HexString(b"DEAD BEEF"),
1666            ],
1667        );
1668    }
1669
1670    #[test]
1671    fn parse_unterminated_strings() {
1672        check_tokens("(string (nested) 1", &[]);
1673        check_tokens("<DEAD BEEF", &[]);
1674    }
1675
1676    #[test]
1677    fn parse_procs() {
1678        check_tokens(
1679            "{a {nested 20} proc } % and a\n {simple proc}",
1680            &[
1681                Token::Proc(b"a {nested 20} proc "),
1682                Token::Proc(b"simple proc"),
1683            ],
1684        );
1685    }
1686
1687    #[test]
1688    fn parse_unterminated_procs() {
1689        check_tokens("{a {nested 20} proc", &[]);
1690    }
1691
1692    #[test]
1693    fn parse_names() {
1694        check_tokens(
1695            "/FontMatrix\r %comment\n /CharStrings",
1696            &[Token::Name(b"FontMatrix"), Token::Name(b"CharStrings")],
1697        );
1698    }
1699
1700    #[test]
1701    fn parse_binary_blobs() {
1702        check_tokens(
1703            "/.notdef 4 RD abcd \n5 11\n \t-| a83jnshf7 3 ",
1704            &[
1705                // simulates a charstring: name followed by data
1706                Token::Name(b".notdef"),
1707                Token::Binary(b"abcd"),
1708                // simulates a subr: index followed by data
1709                Token::Int(5),
1710                Token::Binary(b"a83jnshf7 3"),
1711            ],
1712        )
1713    }
1714
1715    #[test]
1716    fn parse_base_dict_prefix() {
1717        let dicts = RawDicts::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA).unwrap();
1718        let ts = parse_to_tokens(dicts.base);
1719        assert_eq!(
1720            &ts[..19],
1721            &[
1722                Token::Int(10),
1723                Token::Raw(b"dict"),
1724                Token::Raw(b"begin"),
1725                Token::Name(b"FontType"),
1726                Token::Int(1),
1727                Token::Raw(b"def"),
1728                Token::Name(b"FontMatrix"),
1729                Token::Raw(b"["),
1730                Token::Raw(b"0.001"),
1731                Token::Int(0),
1732                Token::Int(0),
1733                Token::Raw(b"0.001"),
1734                Token::Int(0),
1735                Token::Int(0),
1736                Token::Raw(b"]"),
1737                Token::Raw(b"readonly"),
1738                Token::Raw(b"def"),
1739                Token::Name(b"FontName"),
1740                Token::Name(b"NotoSerif-Regular"),
1741            ]
1742        );
1743    }
1744
1745    #[track_caller]
1746    fn check_tokens(source: &str, expected: &[Token]) {
1747        let ts = parse_to_tokens(source.as_bytes());
1748        assert_eq!(ts, expected);
1749    }
1750
1751    fn parse_to_tokens(data: &'_ [u8]) -> Vec<Token<'_>> {
1752        let mut tokens = vec![];
1753        let mut parser = Parser::new(data);
1754        while let Some(token) = parser.next() {
1755            tokens.push(token);
1756        }
1757        tokens
1758    }
1759
1760    #[test]
1761    fn parse_fixed() {
1762        // Direct conversions (power_ten = 0)
1763        assert_eq!(decode_fixed(b"42.5", 0).unwrap(), Fixed::from_f64(42.5));
1764        assert_eq!(
1765            decode_fixed(b"0.0015", 0).unwrap(),
1766            Fixed::from_f64(0.001495361328125)
1767        );
1768        assert_eq!(
1769            decode_fixed(b"425.000e-1", 0).unwrap(),
1770            Fixed::from_f64(42.5)
1771        );
1772        assert_eq!(
1773            decode_fixed(b"1.5e-3", 0).unwrap(),
1774            Fixed::from_f64(0.001495361328125)
1775        );
1776        // Scaled by 1000 (power_ten = 3)
1777        assert_eq!(decode_fixed(b"1.5", 3).unwrap(), Fixed::from_f64(1500.0));
1778        assert_eq!(decode_fixed(b"0.001", 3).unwrap(), Fixed::from_f64(1.0));
1779        assert_eq!(
1780            decode_fixed(b"15000e-4", 3).unwrap(),
1781            Fixed::from_f64(1500.0)
1782        );
1783        assert_eq!(decode_fixed(b"1.000e-3", 3).unwrap(), Fixed::from_f64(1.0));
1784    }
1785
1786    #[test]
1787    fn parse_font_matrix() {
1788        // Standard matrix for 1000 upem
1789        assert_eq!(
1790            Parser::new(b"[0.001 0 0 0.001 0 0]")
1791                .read_font_matrix()
1792                .unwrap()
1793                .matrix,
1794            FontMatrix::IDENTITY,
1795        );
1796        // Matrix with a stretch along the x axis and a small
1797        // offset
1798        assert_eq!(
1799            Parser::new(b"[0.002 0 0 0.001 1 2e1]")
1800                .read_font_matrix()
1801                .unwrap()
1802                .matrix,
1803            FontMatrix::from_elements([
1804                Fixed::from_i32(2),
1805                Fixed::ZERO,
1806                Fixed::ZERO,
1807                Fixed::ONE,
1808                Fixed::from_bits(1000),
1809                Fixed::from_bits(20000)
1810            ])
1811        );
1812        // Matrix with modified upem
1813        assert_eq!(
1814            Parser::new(b"[0.001 0 0 0.0005 0.0 0.0]")
1815                .read_font_matrix()
1816                .unwrap(),
1817            ScaledFontMatrix {
1818                matrix: FontMatrix::from_elements([
1819                    Fixed::from_i32(2),
1820                    Fixed::ZERO,
1821                    Fixed::ZERO,
1822                    Fixed::ONE,
1823                    Fixed::from_i32(0),
1824                    Fixed::from_i32(0)
1825                ]),
1826                scale: 2000,
1827            }
1828        );
1829    }
1830
1831    #[test]
1832    fn parse_subrs() {
1833        let dicts = RawDicts::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA).unwrap();
1834        let mut parser = Parser::new(&dicts.private);
1835        let mut subrs = None;
1836        while let Some(token) = parser.next() {
1837            if let Token::Name(b"Subrs") = token {
1838                subrs = parser.read_subrs(4);
1839                break;
1840            }
1841        }
1842        let mut subrs = subrs.unwrap();
1843        // The decrypted subroutines extracted from FreeType
1844        let expected_subrs: [&[u8]; 5] = [
1845            &[142, 139, 12, 16, 12, 17, 12, 17, 12, 33, 11],
1846            &[139, 140, 12, 16, 11],
1847            &[139, 141, 12, 16, 11],
1848            &[11],
1849            &[140, 142, 12, 16, 12, 17, 10, 11],
1850        ];
1851        assert_eq!(subrs.index.len(), expected_subrs.len());
1852        assert!(subrs.is_dense);
1853        // These subrs are densely allocated but check binary search mode
1854        // as well
1855        for is_dense in [true, false] {
1856            subrs.is_dense = is_dense;
1857            for (idx, &expected) in expected_subrs.iter().enumerate() {
1858                let subr = subrs.get(idx as u32).unwrap();
1859                assert_eq!(subr, expected);
1860            }
1861        }
1862    }
1863
1864    #[test]
1865    fn parse_empty_array_subrs() {
1866        let subrs = Parser::new(b"[ ]").read_subrs(4).unwrap();
1867        assert!(subrs.data.is_empty());
1868        assert!(subrs.index.is_empty());
1869    }
1870
1871    #[test]
1872    fn parse_empty_subrs() {
1873        let subrs = Parser::new(b" 0 array\nND\n").read_subrs(4).unwrap();
1874        assert!(subrs.data.is_empty());
1875        assert!(subrs.index.is_empty());
1876    }
1877
1878    #[test]
1879    fn parse_malformed_subrs() {
1880        assert!(Parser::new(b" 20 \nND\n").read_subrs(4).is_none());
1881    }
1882
1883    #[test]
1884    fn parse_subrs_duplicate_def() {
1885        // Two definitions of subrs.. we want to keep the first
1886        // one which has two entries at 5 and 42
1887        let private = b"/Subrs 2 array dup 5 2 RD nd NP dup 42 2 RD ab NP ND\n/Subrs 1 array dup 0 2 RD xy NP ND /CharStrings 0";
1888        let font = Type1Font::from_dicts(b"", private).unwrap();
1889        assert_eq!(font.subrs.index.len(), 2);
1890        assert_eq!(font.subrs.index[0].0, 5);
1891        assert_eq!(font.subrs.index[1].0, 42);
1892    }
1893
1894    #[test]
1895    fn parse_charstrings() {
1896        let dicts = RawDicts::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA).unwrap();
1897        let mut parser = Parser::new(&dicts.private);
1898        let mut charstrings = None;
1899        while let Some(token) = parser.next() {
1900            if let Token::Name(b"CharStrings") = token {
1901                charstrings = parser.read_charstrings(4);
1902                break;
1903            }
1904        }
1905        let charstrings = charstrings.unwrap();
1906        assert_eq!(charstrings.num_glyphs(), 9);
1907        assert!(charstrings.orig_notdef_index.is_none());
1908        let expected_names = [
1909            ".notdef",
1910            "H",
1911            "f",
1912            "i",
1913            "x",
1914            "f_f.liga",
1915            "f_f_i.liga",
1916            "f_i.liga",
1917            "H.c2sc",
1918        ];
1919        let names = (0..charstrings.num_glyphs())
1920            .map(|idx| charstrings.name(idx).unwrap())
1921            .collect::<Vec<_>>();
1922        assert_eq!(names, expected_names);
1923        // Prefix (up to 8 bytes), extracted from FreeType
1924        let expected_charstrings_prefix: [&[u8]; 9] = [
1925            &[139, 248, 236, 13, 14],
1926            &[177, 249, 173, 13, 139, 4, 247, 183],
1927            &[166, 248, 5, 13, 139, 4, 247, 201],
1928            &[162, 247, 212, 13, 247, 30, 249, 16],
1929            &[144, 248, 214, 13, 139, 4, 247, 130],
1930            &[166, 249, 88, 13, 139, 4, 247, 181],
1931            &[166, 250, 126, 13, 139, 4, 247, 181],
1932            &[166, 249, 43, 13, 139, 4, 247, 181],
1933            &[180, 249, 60, 13, 139, 4, 247, 141],
1934        ];
1935        for (idx, &expected) in expected_charstrings_prefix.iter().enumerate() {
1936            let charstring = charstrings.get(idx as u32).unwrap();
1937            assert_eq!(&charstring[..expected.len()], expected);
1938        }
1939    }
1940
1941    #[test]
1942    fn parse_charstrings_duplicate_def() {
1943        // Two definitions of charstrings.. we want to keep the first
1944        // one which has three glyphs: .notdef, H and I
1945        let private = b"/CharStrings 2 /.notdef 2 RD nd ND /H 2 RD ab ND /I 2 RD cd ND def\n/CharStrings 1 /B 2 RD xy ND def";
1946        let font = Type1Font::from_dicts(b"", private).unwrap();
1947        assert_eq!(font.num_glyphs(), 3);
1948        assert_eq!(font.charstrings.name(0).unwrap(), ".notdef");
1949        assert_eq!(font.charstrings.name(1).unwrap(), "H");
1950        assert_eq!(font.charstrings.name(2).unwrap(), "I");
1951    }
1952
1953    #[test]
1954    fn parse_charstrings_missing_notdef() {
1955        let mut parser = Parser::new(b"1 /H 2 RD ab ND /B 2 RD xy ND");
1956        let charstrings = parser.read_charstrings(-1).unwrap();
1957        assert_eq!(charstrings.num_glyphs(), 3);
1958        assert_eq!(charstrings.orig_notdef_index, Some(2));
1959        let expected_glyphs: &[(&str, &[u8])] =
1960            &[(".notdef", NOTDEF_GLYPH), ("B", b"xy"), ("H", b"ab")];
1961        check_charstrings(&charstrings, expected_glyphs);
1962        let mut font = Type1Font::empty();
1963        font.charstrings = charstrings;
1964        assert_eq!(font.remapped_gid(GlyphId::new(0)), GlyphId::new(2));
1965        assert_eq!(font.remapped_gid(GlyphId::new(1)), GlyphId::new(1));
1966        assert_eq!(font.remapped_gid(GlyphId::new(2)), GlyphId::new(0));
1967    }
1968
1969    #[test]
1970    fn parse_charstrings_notdef_moved() {
1971        let mut parser = Parser::new(b"1 /H 2 RD ab ND /.notdef 2 RD nd ND /B 2 RD xy ND");
1972        let charstrings = parser.read_charstrings(-1).unwrap();
1973        assert_eq!(charstrings.num_glyphs(), 3);
1974        assert_eq!(charstrings.orig_notdef_index, Some(1));
1975        let expected_glyphs: &[(&str, &[u8])] = &[(".notdef", b"nd"), ("H", b"ab"), ("B", b"xy")];
1976        check_charstrings(&charstrings, expected_glyphs);
1977        let mut font = Type1Font::empty();
1978        font.charstrings = charstrings;
1979        assert_eq!(font.remapped_gid(GlyphId::new(0)), GlyphId::new(1));
1980        assert_eq!(font.remapped_gid(GlyphId::new(1)), GlyphId::new(0));
1981        assert_eq!(font.remapped_gid(GlyphId::new(2)), GlyphId::new(2));
1982    }
1983
1984    #[track_caller]
1985    fn check_charstrings(charstrings: &Charstrings, expected_glyphs: &[(&str, &[u8])]) {
1986        for (idx, expected) in expected_glyphs.iter().enumerate() {
1987            let idx = idx as u32;
1988            let name = charstrings.name(idx).unwrap();
1989            let data = charstrings.get(idx).unwrap();
1990            assert_eq!((name, data), *expected);
1991        }
1992    }
1993
1994    #[test]
1995    fn parse_weight_vector() {
1996        let mut parser = Parser::new(b"[0 0.125, 1.25 -0.87]");
1997        let weights = parser
1998            .read_weight_vector()
1999            .unwrap()
2000            .drain(..)
2001            .map(|w| w.to_f32())
2002            .collect::<Vec<_>>();
2003        assert_eq!(weights, &[0.0, 0.125, 1.25, -0.8699951]);
2004    }
2005
2006    #[test]
2007    fn parse_type1_font_pfb() {
2008        check_type1_font(
2009            &Type1Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB).unwrap(),
2010        );
2011    }
2012
2013    #[test]
2014    fn parse_type1_font_pfa() {
2015        check_type1_font(
2016            &Type1Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA).unwrap(),
2017        );
2018    }
2019
2020    #[track_caller]
2021    fn check_type1_font(font: &Type1Font) {
2022        assert_eq!(font.name(), Some("NotoSerif-Regular"));
2023        assert_eq!(font.full_name(), Some("Noto Serif Regular"));
2024        assert_eq!(font.family_name(), Some("Noto Serif"));
2025        assert_eq!(font.weight(), Some("Book"));
2026        assert_eq!(font.italic_angle(), 0);
2027        assert!(!font.is_fixed_pitch());
2028        assert_eq!(font.underline_position(), -125);
2029        assert_eq!(font.underline_thickness(), 50);
2030        assert_eq!(
2031            font.bbox(),
2032            BoundingBox {
2033                x_min: Fixed::from_i32(5),
2034                y_min: Fixed::ZERO,
2035                x_max: Fixed::from_i32(989),
2036                y_max: Fixed::from_i32(775)
2037            }
2038        );
2039        assert_eq!(font.num_glyphs(), 9);
2040        assert_eq!(font.subrs.index.len(), 5);
2041        assert_eq!(
2042            font.matrix,
2043            ScaledFontMatrix {
2044                matrix: FontMatrix::IDENTITY,
2045                scale: 1000
2046            }
2047        );
2048        assert!(font
2049            .glyph_names()
2050            .map(|(_, name)| name)
2051            .take(4)
2052            .eq([".notdef", "H", "f", "i"].into_iter()))
2053    }
2054
2055    #[test]
2056    fn parse_encoding() {
2057        assert!(matches!(
2058            Type1Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA)
2059                .unwrap()
2060                .encoding,
2061            Some(RawEncoding::Predefined(PredefinedEncoding::Standard)),
2062        ));
2063    }
2064
2065    #[test]
2066    fn parse_known_encodings() {
2067        for (blob, encoding) in [
2068            (
2069                "StandardEncoding",
2070                RawEncoding::Predefined(PredefinedEncoding::Standard),
2071            ),
2072            (
2073                "ExpertEncoding",
2074                RawEncoding::Predefined(PredefinedEncoding::Expert),
2075            ),
2076            (
2077                "ISOLatin1Encoding",
2078                RawEncoding::Predefined(PredefinedEncoding::IsoLatin1),
2079            ),
2080        ] {
2081            assert_eq!(
2082                Parser::new(blob.as_bytes())
2083                    .read_encoding(&Charstrings::default())
2084                    .unwrap(),
2085                encoding
2086            );
2087        }
2088    }
2089
2090    #[test]
2091    fn parse_custom_dense_encoding() {
2092        let mut map = Vec::new();
2093        map.resize(256, ".notdef".to_string());
2094        let mut parser = Parser::new(b"[/.notdef /A /b /.notdef /comma /at]");
2095        parser.read_dense_encoding(|idx, name| {
2096            map[idx as usize] = name.to_string();
2097        });
2098        for (ch, entry) in map.iter().enumerate() {
2099            let expected = match ch {
2100                1 => "A",
2101                2 => "b",
2102                4 => "comma",
2103                5 => "at",
2104                _ => ".notdef",
2105            };
2106            assert_eq!(entry, expected);
2107        }
2108    }
2109
2110    #[test]
2111    fn parse_custom_sparse_encoding() {
2112        let mut map = Vec::new();
2113        map.resize(256, ".notdef".to_string());
2114        let mut parser = Parser::new(CUSTOM_SPARSE_ENCODING.as_bytes());
2115        parser.read_sparse_encoding(|idx, name| {
2116            map[idx as usize] = name.to_string();
2117        });
2118        for (ch, entry) in map.iter().enumerate() {
2119            let expected = match ch {
2120                66 => "B",
2121                97 => "a",
2122                64 => "at",
2123                44 => "comma",
2124                56 => "eight",
2125                _ => ".notdef",
2126            };
2127            assert_eq!(entry, expected);
2128        }
2129    }
2130
2131    const CUSTOM_SPARSE_ENCODING: &str = r#"
2132        array
2133        0 1 255 {1 index exch /.notdef put} for
2134        dup 66 /B put
2135        dup 97 /a put
2136        dup 64 /at put
2137        dup 44 /comma put
2138        dup 56 /eight put
2139        readonly def    
2140    "#;
2141
2142    #[test]
2143    fn eval_charstrings() {
2144        let font = Type1Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA).unwrap();
2145        let expected_eval_prefix = [
2146            "M38,0 L329,0 L329,42 L316,42 C293,42 274,46 258,54 C242,63 234,83 234,114",
2147            "M27,0 L336,0 L336,42 L298,42 C275,42 256,46 240,54 C224,63 216,83 216,114",
2148            "M161,636 C176,636 190,641 201,650 C212,659 218,675 218,698 C218,721 212,738 201,746",
2149            "M5,0 L243,0 L243,42 L240,42 C218,42 202,44 192,50 C183,54 178,62 178,73",
2150            "M27,0 L316,0 L316,42 L298,42 C275,42 256,46 240,54 C224,63 216,83 216,114",
2151            "M27,0 L316,0 L316,42 L298,42 C275,42 256,46 240,54 C224,63 216,83 216,114",
2152            "M27,0 L316,0 L316,42 L298,42 C275,42 256,46 240,54 C224,63 216,83 216,114",
2153            "M41,0 L290,0 L290,42 L269,42 C254,42 240,45 229,52 C218,58 212,73 212,98",
2154        ];
2155        // -1 to ignore the .notdef glyph
2156        assert_eq!(font.num_glyphs() as usize - 1, expected_eval_prefix.len());
2157        let mut commands = CaptureCommandSink::default();
2158        for (gid, expected_prefix) in (1..font.num_glyphs()).zip(&expected_eval_prefix) {
2159            commands.0.clear();
2160            font.evaluate_charstring(gid.into(), &mut commands).unwrap();
2161            assert!(commands.to_svg().starts_with(expected_prefix));
2162        }
2163    }
2164
2165    #[test]
2166    fn eval_charstring_widths() {
2167        let font = Type1Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA).unwrap();
2168        let expected_widths = [
2169            600.0, 793.0, 369.0, 320.0, 578.0, 708.0, 1002.0, 663.0, 680.0,
2170        ];
2171        let mut commands = CaptureCommandSink::default();
2172        let widths = (0..font.num_glyphs())
2173            .map(|gid| {
2174                commands.0.clear();
2175                font.evaluate_charstring(gid.into(), &mut commands)
2176                    .unwrap()
2177                    .unwrap()
2178                    .to_f32()
2179            })
2180            .collect::<Vec<_>>();
2181        assert_eq!(widths, expected_widths);
2182    }
2183
2184    #[test]
2185    fn csctx_seac_components() {
2186        let font = Type1Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA).unwrap();
2187        // Standard encoding for 'x' and 'i'
2188        let x_code = 120;
2189        let i_code = 105;
2190        let [x_data, i_data] = font.seac_components(x_code, i_code).unwrap();
2191        let name_to_gid = |name| {
2192            font.glyph_names()
2193                .find_map(|(gid, gname)| (name == gname).then_some(gid.to_u32()))
2194                .unwrap()
2195        };
2196        assert_eq!(x_data, font.charstrings.get(name_to_gid("x")).unwrap());
2197        assert_eq!(i_data, font.charstrings.get(name_to_gid("i")).unwrap());
2198    }
2199
2200    #[test]
2201    fn csctx_subrs() {
2202        let font = Type1Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA).unwrap();
2203        assert!(!font.subrs.index.is_empty());
2204        for subr_idx in 0..font.subrs.index.len() {
2205            assert_eq!(
2206                font.subrs.get(subr_idx as u32).unwrap(),
2207                font.subr(subr_idx as _).unwrap()
2208            )
2209        }
2210    }
2211
2212    #[test]
2213    fn encoding_mapping() {
2214        let font = Type1Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA).unwrap();
2215        let encoding = font.encoding().unwrap();
2216        let expected = [
2217            // code, gid, name
2218            (0, 0, ".notdef"),
2219            (72, 1, "H"),
2220            (102, 2, "f"),
2221            (105, 3, "i"),
2222            (120, 4, "x"),
2223        ];
2224        for (code, gid, name) in expected {
2225            assert_eq!(encoding.glyph_name(code).unwrap(), name);
2226            assert_eq!(encoding.map(code).unwrap().to_u32(), gid);
2227        }
2228    }
2229}