read_fonts/tables/postscript/
dict.rs

1//! Parsing for PostScript DICTs.
2
3use std::ops::Range;
4
5use super::{BlendState, Error, Number, Stack, StringId};
6use crate::{types::Fixed, Cursor, ReadError};
7
8/// PostScript DICT operator.
9///
10/// See "Table 9 Top DICT Operator Entries" and "Table 23 Private DICT
11/// Operators" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf>
12#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
13pub enum Operator {
14    Version,
15    Notice,
16    FullName,
17    FamilyName,
18    Weight,
19    FontBbox,
20    CharstringsOffset,
21    PrivateDictRange,
22    VariationStoreOffset,
23    Copyright,
24    IsFixedPitch,
25    ItalicAngle,
26    UnderlinePosition,
27    UnderlineThickness,
28    PaintType,
29    CharstringType,
30    FontMatrix,
31    StrokeWidth,
32    FdArrayOffset,
33    FdSelectOffset,
34    BlueValues,
35    OtherBlues,
36    FamilyBlues,
37    FamilyOtherBlues,
38    SubrsOffset,
39    VariationStoreIndex,
40    BlueScale,
41    BlueShift,
42    BlueFuzz,
43    LanguageGroup,
44    ExpansionFactor,
45    Encoding,
46    Charset,
47    UniqueId,
48    Xuid,
49    SyntheticBase,
50    PostScript,
51    BaseFontName,
52    BaseFontBlend,
53    Ros,
54    CidFontVersion,
55    CidFontRevision,
56    CidFontType,
57    CidCount,
58    UidBase,
59    FontName,
60    StdHw,
61    StdVw,
62    DefaultWidthX,
63    NominalWidthX,
64    Blend,
65    StemSnapH,
66    StemSnapV,
67    ForceBold,
68    InitialRandomSeed,
69}
70
71impl Operator {
72    fn from_opcode(opcode: u8) -> Option<Self> {
73        use Operator::*;
74        Some(match opcode {
75            // Top DICT operators
76            0 => Version,
77            1 => Notice,
78            2 => FullName,
79            3 => FamilyName,
80            4 => Weight,
81            5 => FontBbox,
82            13 => UniqueId,
83            14 => Xuid,
84            15 => Charset,
85            16 => Encoding,
86            17 => CharstringsOffset,
87            18 => PrivateDictRange,
88            24 => VariationStoreOffset,
89            // Private DICT operators
90            6 => BlueValues,
91            7 => OtherBlues,
92            8 => FamilyBlues,
93            9 => FamilyOtherBlues,
94            10 => StdHw,
95            11 => StdVw,
96            19 => SubrsOffset,
97            20 => DefaultWidthX,
98            21 => NominalWidthX,
99            22 => VariationStoreIndex,
100            23 => Blend,
101            // Font DICT only uses PrivateDictRange
102            _ => return None,
103        })
104    }
105
106    fn from_extended_opcode(opcode: u8) -> Option<Self> {
107        use Operator::*;
108        Some(match opcode {
109            // Top DICT operators
110            0 => Copyright,
111            1 => IsFixedPitch,
112            2 => ItalicAngle,
113            3 => UnderlinePosition,
114            4 => UnderlineThickness,
115            5 => PaintType,
116            6 => CharstringType,
117            7 => FontMatrix,
118            8 => StrokeWidth,
119            20 => SyntheticBase,
120            21 => PostScript,
121            22 => BaseFontName,
122            23 => BaseFontBlend,
123            30 => Ros,
124            31 => CidFontVersion,
125            32 => CidFontRevision,
126            33 => CidFontType,
127            34 => CidCount,
128            35 => UidBase,
129            36 => FdArrayOffset,
130            37 => FdSelectOffset,
131            38 => FontName,
132            // Private DICT operators
133            9 => BlueScale,
134            10 => BlueShift,
135            11 => BlueFuzz,
136            12 => StemSnapH,
137            13 => StemSnapV,
138            14 => ForceBold,
139            17 => LanguageGroup,
140            18 => ExpansionFactor,
141            19 => InitialRandomSeed,
142            _ => return None,
143        })
144    }
145}
146
147/// Either a PostScript DICT operator or a (numeric) operand.
148#[derive(Copy, Clone, PartialEq, Eq, Debug)]
149pub enum Token {
150    Operator(Operator),
151    Operand(Number),
152}
153
154impl From<Operator> for Token {
155    fn from(value: Operator) -> Self {
156        Self::Operator(value)
157    }
158}
159
160impl<T> From<T> for Token
161where
162    T: Into<Number>,
163{
164    fn from(value: T) -> Self {
165        Self::Operand(value.into())
166    }
167}
168
169/// Given a byte slice containing DICT data, returns an iterator yielding
170/// raw operands and operators.
171///
172/// This does not perform any additional processing such as type conversion,
173/// delta decoding or blending.
174pub fn tokens(dict_data: &[u8]) -> impl Iterator<Item = Result<Token, Error>> + '_ + Clone {
175    let mut cursor = crate::FontData::new(dict_data).cursor();
176    std::iter::from_fn(move || {
177        if cursor.remaining_bytes() == 0 {
178            None
179        } else {
180            Some(parse_token(&mut cursor))
181        }
182    })
183}
184
185fn parse_token(cursor: &mut Cursor) -> Result<Token, Error> {
186    // Escape opcode for accessing extensions.
187    const ESCAPE: u8 = 12;
188    let b0 = cursor.read::<u8>()?;
189    Ok(if b0 == ESCAPE {
190        let b1 = cursor.read::<u8>()?;
191        Token::Operator(Operator::from_extended_opcode(b1).ok_or(Error::InvalidDictOperator(b1))?)
192    } else {
193        // See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-3-operand-encoding>
194        match b0 {
195            28 | 29 | 32..=254 => Token::Operand(parse_int(cursor, b0)?.into()),
196            30 => Token::Operand(parse_bcd(cursor)?.into()),
197            _ => Token::Operator(Operator::from_opcode(b0).ok_or(Error::InvalidDictOperator(b0))?),
198        }
199    })
200}
201
202/// PostScript DICT Operator with its associated operands.
203#[derive(Clone, PartialEq, Eq, Debug)]
204pub enum Entry {
205    Version(StringId),
206    Notice(StringId),
207    FullName(StringId),
208    FamilyName(StringId),
209    Weight(StringId),
210    FontBbox([Fixed; 4]),
211    CharstringsOffset(usize),
212    PrivateDictRange(Range<usize>),
213    VariationStoreOffset(usize),
214    Copyright(StringId),
215    IsFixedPitch(bool),
216    ItalicAngle(Fixed),
217    UnderlinePosition(Fixed),
218    UnderlineThickness(Fixed),
219    PaintType(i32),
220    CharstringType(i32),
221    FontMatrix([Fixed; 6]),
222    StrokeWidth(Fixed),
223    FdArrayOffset(usize),
224    FdSelectOffset(usize),
225    BlueValues(Blues),
226    OtherBlues(Blues),
227    FamilyBlues(Blues),
228    FamilyOtherBlues(Blues),
229    SubrsOffset(usize),
230    VariationStoreIndex(u16),
231    BlueScale(Fixed),
232    BlueShift(Fixed),
233    BlueFuzz(Fixed),
234    LanguageGroup(i32),
235    ExpansionFactor(Fixed),
236    Encoding(usize),
237    Charset(usize),
238    UniqueId(i32),
239    Xuid,
240    SyntheticBase(i32),
241    PostScript(StringId),
242    BaseFontName(StringId),
243    BaseFontBlend,
244    Ros {
245        registry: StringId,
246        ordering: StringId,
247        supplement: Fixed,
248    },
249    CidFontVersion(Fixed),
250    CidFontRevision(Fixed),
251    CidFontType(i32),
252    CidCount(u32),
253    UidBase(i32),
254    FontName(StringId),
255    StdHw(Fixed),
256    StdVw(Fixed),
257    DefaultWidthX(Fixed),
258    NominalWidthX(Fixed),
259    StemSnapH(StemSnaps),
260    StemSnapV(StemSnaps),
261    ForceBold(bool),
262    InitialRandomSeed(i32),
263}
264
265/// Given a byte slice containing DICT data, returns an iterator yielding
266/// each operator with its associated operands.
267///
268/// This performs appropriate type conversions, decodes deltas and applies
269/// blending.
270///
271/// If processing a Private DICT from a CFF2 table and an item variation
272/// store is present, then `blend_state` must be provided.
273pub fn entries<'a>(
274    dict_data: &'a [u8],
275    mut blend_state: Option<BlendState<'a>>,
276) -> impl Iterator<Item = Result<Entry, Error>> + 'a {
277    let mut stack = Stack::new();
278    let mut token_iter = tokens(dict_data);
279    std::iter::from_fn(move || loop {
280        let token = match token_iter.next()? {
281            Ok(token) => token,
282            Err(e) => return Some(Err(e)),
283        };
284        match token {
285            Token::Operand(number) => match stack.push(number) {
286                Ok(_) => continue,
287                Err(e) => return Some(Err(e)),
288            },
289            Token::Operator(op) => {
290                if op == Operator::Blend || op == Operator::VariationStoreIndex {
291                    let state = match blend_state.as_mut() {
292                        Some(state) => state,
293                        None => return Some(Err(Error::MissingBlendState)),
294                    };
295                    if op == Operator::VariationStoreIndex {
296                        match stack
297                            .get_i32(0)
298                            .and_then(|ix| state.set_store_index(ix as u16))
299                        {
300                            Ok(_) => {}
301                            Err(e) => return Some(Err(e)),
302                        }
303                    }
304                    if op == Operator::Blend {
305                        match stack.apply_blend(state) {
306                            Ok(_) => continue,
307                            Err(e) => return Some(Err(e)),
308                        }
309                    }
310                }
311                let entry = parse_entry(op, &mut stack);
312                stack.clear();
313                return Some(entry);
314            }
315        }
316    })
317}
318
319fn parse_entry(op: Operator, stack: &mut Stack) -> Result<Entry, Error> {
320    use Operator::*;
321    Ok(match op {
322        Version => Entry::Version(stack.pop_i32()?.into()),
323        Notice => Entry::Notice(stack.pop_i32()?.into()),
324        FullName => Entry::FullName(stack.pop_i32()?.into()),
325        FamilyName => Entry::FamilyName(stack.pop_i32()?.into()),
326        Weight => Entry::Weight(stack.pop_i32()?.into()),
327        FontBbox => Entry::FontBbox([
328            stack.get_fixed(0)?,
329            stack.get_fixed(1)?,
330            stack.get_fixed(2)?,
331            stack.get_fixed(3)?,
332        ]),
333        CharstringsOffset => Entry::CharstringsOffset(stack.pop_i32()? as usize),
334        PrivateDictRange => {
335            let len = stack.get_i32(0)? as usize;
336            let start = stack.get_i32(1)? as usize;
337            let end = start.checked_add(len).ok_or(ReadError::OutOfBounds)?;
338            Entry::PrivateDictRange(start..end)
339        }
340        VariationStoreOffset => Entry::VariationStoreOffset(stack.pop_i32()? as usize),
341        Copyright => Entry::Copyright(stack.pop_i32()?.into()),
342        IsFixedPitch => Entry::IsFixedPitch(stack.pop_i32()? != 0),
343        ItalicAngle => Entry::ItalicAngle(stack.pop_fixed()?),
344        UnderlinePosition => Entry::UnderlinePosition(stack.pop_fixed()?),
345        UnderlineThickness => Entry::UnderlineThickness(stack.pop_fixed()?),
346        PaintType => Entry::PaintType(stack.pop_i32()?),
347        CharstringType => Entry::CharstringType(stack.pop_i32()?),
348        FontMatrix => Entry::FontMatrix([
349            stack.get_fixed(0)?,
350            stack.get_fixed(1)?,
351            stack.get_fixed(2)?,
352            stack.get_fixed(3)?,
353            stack.get_fixed(4)?,
354            stack.get_fixed(5)?,
355        ]),
356        StrokeWidth => Entry::StrokeWidth(stack.pop_fixed()?),
357        FdArrayOffset => Entry::FdArrayOffset(stack.pop_i32()? as usize),
358        FdSelectOffset => Entry::FdSelectOffset(stack.pop_i32()? as usize),
359        BlueValues => {
360            stack.apply_delta_prefix_sum();
361            Entry::BlueValues(Blues::new(stack.fixed_values()))
362        }
363        OtherBlues => {
364            stack.apply_delta_prefix_sum();
365            Entry::OtherBlues(Blues::new(stack.fixed_values()))
366        }
367        FamilyBlues => {
368            stack.apply_delta_prefix_sum();
369            Entry::FamilyBlues(Blues::new(stack.fixed_values()))
370        }
371        FamilyOtherBlues => {
372            stack.apply_delta_prefix_sum();
373            Entry::FamilyOtherBlues(Blues::new(stack.fixed_values()))
374        }
375        SubrsOffset => Entry::SubrsOffset(stack.pop_i32()? as usize),
376        VariationStoreIndex => Entry::VariationStoreIndex(stack.pop_i32()? as u16),
377        BlueScale => Entry::BlueScale(stack.pop_fixed()?),
378        BlueShift => Entry::BlueShift(stack.pop_fixed()?),
379        BlueFuzz => Entry::BlueFuzz(stack.pop_fixed()?),
380        LanguageGroup => Entry::LanguageGroup(stack.pop_i32()?),
381        ExpansionFactor => Entry::ExpansionFactor(stack.pop_fixed()?),
382        Encoding => Entry::Encoding(stack.pop_i32()? as usize),
383        Charset => Entry::Charset(stack.pop_i32()? as usize),
384        UniqueId => Entry::UniqueId(stack.pop_i32()?),
385        Xuid => Entry::Xuid,
386        SyntheticBase => Entry::SyntheticBase(stack.pop_i32()?),
387        PostScript => Entry::PostScript(stack.pop_i32()?.into()),
388        BaseFontName => Entry::BaseFontName(stack.pop_i32()?.into()),
389        BaseFontBlend => Entry::BaseFontBlend,
390        Ros => Entry::Ros {
391            registry: stack.get_i32(0)?.into(),
392            ordering: stack.get_i32(1)?.into(),
393            supplement: stack.get_fixed(2)?,
394        },
395        CidFontVersion => Entry::CidFontVersion(stack.pop_fixed()?),
396        CidFontRevision => Entry::CidFontRevision(stack.pop_fixed()?),
397        CidFontType => Entry::CidFontType(stack.pop_i32()?),
398        CidCount => Entry::CidCount(stack.pop_i32()? as u32),
399        UidBase => Entry::UidBase(stack.pop_i32()?),
400        FontName => Entry::FontName(stack.pop_i32()?.into()),
401        StdHw => Entry::StdHw(stack.pop_fixed()?),
402        StdVw => Entry::StdVw(stack.pop_fixed()?),
403        DefaultWidthX => Entry::DefaultWidthX(stack.pop_fixed()?),
404        NominalWidthX => Entry::NominalWidthX(stack.pop_fixed()?),
405        StemSnapH => {
406            stack.apply_delta_prefix_sum();
407            Entry::StemSnapH(StemSnaps::new(stack.fixed_values()))
408        }
409        StemSnapV => {
410            stack.apply_delta_prefix_sum();
411            Entry::StemSnapV(StemSnaps::new(stack.fixed_values()))
412        }
413        ForceBold => Entry::ForceBold(stack.pop_i32()? != 0),
414        InitialRandomSeed => Entry::InitialRandomSeed(stack.pop_i32()?),
415        // Blend is handled at the layer above
416        Blend => unreachable!(),
417    })
418}
419
420/// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psblues.h#L141>
421const MAX_BLUE_VALUES: usize = 7;
422
423/// Operand for the `BlueValues`, `OtherBlues`, `FamilyBlues` and
424/// `FamilyOtherBlues` operators.
425///
426/// These are used to generate zones when applying hints.
427#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
428pub struct Blues {
429    values: [(Fixed, Fixed); MAX_BLUE_VALUES],
430    len: u32,
431}
432
433impl Blues {
434    pub fn new(values: impl Iterator<Item = Fixed>) -> Self {
435        let mut blues = Self::default();
436        let mut stash = Fixed::ZERO;
437        for (i, value) in values.take(MAX_BLUE_VALUES * 2).enumerate() {
438            if (i & 1) == 0 {
439                stash = value;
440            } else {
441                blues.values[i / 2] = (stash, value);
442                blues.len += 1;
443            }
444        }
445        blues
446    }
447
448    pub fn values(&self) -> &[(Fixed, Fixed)] {
449        &self.values[..self.len as usize]
450    }
451}
452
453/// Summary: older PostScript interpreters accept two values, but newer ones
454/// accept 12. We'll assume that as maximum.
455/// <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5049.StemSnap.pdf>
456const MAX_STEM_SNAPS: usize = 12;
457
458/// Operand for the `StemSnapH` and `StemSnapV` operators.
459///
460/// These are used for stem darkening when applying hints.
461#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
462pub struct StemSnaps {
463    values: [Fixed; MAX_STEM_SNAPS],
464    len: u32,
465}
466
467impl StemSnaps {
468    fn new(values: impl Iterator<Item = Fixed>) -> Self {
469        let mut snaps = Self::default();
470        for (value, target_value) in values.take(MAX_STEM_SNAPS).zip(&mut snaps.values) {
471            *target_value = value;
472            snaps.len += 1;
473        }
474        snaps
475    }
476
477    pub fn values(&self) -> &[Fixed] {
478        &self.values[..self.len as usize]
479    }
480}
481
482pub(crate) fn parse_int(cursor: &mut Cursor, b0: u8) -> Result<i32, Error> {
483    // Size   b0 range     Value range              Value calculation
484    //--------------------------------------------------------------------------------
485    // 1      32 to 246    -107 to +107             b0 - 139
486    // 2      247 to 250   +108 to +1131            (b0 - 247) * 256 + b1 + 108
487    // 2      251 to 254   -1131 to -108            -(b0 - 251) * 256 - b1 - 108
488    // 3      28           -32768 to +32767         b1 << 8 | b2
489    // 5      29           -(2^31) to +(2^31 - 1)   b1 << 24 | b2 << 16 | b3 << 8 | b4
490    // <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-3-operand-encoding>
491    Ok(match b0 {
492        32..=246 => b0 as i32 - 139,
493        247..=250 => (b0 as i32 - 247) * 256 + cursor.read::<u8>()? as i32 + 108,
494        251..=254 => -(b0 as i32 - 251) * 256 - cursor.read::<u8>()? as i32 - 108,
495        28 => cursor.read::<i16>()? as i32,
496        29 => cursor.read::<i32>()?,
497        _ => {
498            return Err(Error::InvalidNumber);
499        }
500    })
501}
502
503/// Parse a binary coded decimal number.
504fn parse_bcd(cursor: &mut Cursor) -> Result<Fixed, Error> {
505    // fonttools says:
506    // "Note: 14 decimal digits seems to be the limitation for CFF real numbers
507    // in macOS. However, we use 8 here to match the implementation of AFDKO."
508    // <https://github.com/fonttools/fonttools/blob/84cebca6a1709085b920783400ceb1a147d51842/Lib/fontTools/misc/psCharStrings.py#L269>
509    // So, 32 should be big enough for anybody?
510    const MAX_LEN: usize = 32;
511    let mut buf = [0u8; MAX_LEN];
512    let mut n = 0;
513    let mut push = |byte| {
514        if n < MAX_LEN {
515            buf[n] = byte;
516            n += 1;
517            Ok(())
518        } else {
519            Err(Error::InvalidNumber)
520        }
521    };
522    // Nibble value    Represents
523    //----------------------------------
524    // 0 to 9          0 to 9
525    // a               . (decimal point)
526    // b               E
527    // c               E-
528    // d               <reserved>
529    // e               - (minus)
530    // f               end of number
531    // <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-5-nibble-definitions>
532    'outer: loop {
533        let b = cursor.read::<u8>()?;
534        for nibble in [(b >> 4) & 0xF, b & 0xF] {
535            match nibble {
536                0x0..=0x9 => push(b'0' + nibble)?,
537                0xA => push(b'.')?,
538                0xB => push(b'E')?,
539                0xC => {
540                    push(b'E')?;
541                    push(b'-')?;
542                }
543                0xE => push(b'-')?,
544                0xF => break 'outer,
545                _ => return Err(Error::InvalidNumber),
546            }
547        }
548    }
549    std::str::from_utf8(&buf[..n])
550        .map_or(None, |buf| buf.parse::<f64>().ok())
551        .map(Fixed::from_f64)
552        .ok_or(Error::InvalidNumber)
553}
554
555#[cfg(test)]
556mod tests {
557    use font_test_data::bebuffer::BeBuffer;
558
559    use super::*;
560    use crate::{
561        tables::variations::ItemVariationStore, types::F2Dot14, FontData, FontRead, FontRef,
562        TableProvider,
563    };
564
565    #[test]
566    fn int_operands() {
567        // Test the boundary conditions of the ranged int operators
568        let empty = FontData::new(&[]);
569        let min_byte = FontData::new(&[0]);
570        let max_byte = FontData::new(&[255]);
571        // 32..=246 => -107..=107
572        assert_eq!(parse_int(&mut empty.cursor(), 32).unwrap(), -107);
573        assert_eq!(parse_int(&mut empty.cursor(), 246).unwrap(), 107);
574        // 247..=250 => +108 to +1131
575        assert_eq!(parse_int(&mut min_byte.cursor(), 247).unwrap(), 108);
576        assert_eq!(parse_int(&mut max_byte.cursor(), 250).unwrap(), 1131);
577        // 251..=254 => -1131 to -108
578        assert_eq!(parse_int(&mut min_byte.cursor(), 251).unwrap(), -108);
579        assert_eq!(parse_int(&mut max_byte.cursor(), 254).unwrap(), -1131);
580    }
581
582    #[test]
583    fn binary_coded_decimal_operands() {
584        // From <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-5-nibble-definitions>:
585        //
586        // "A real number is terminated by one (or two) 0xf nibbles so that it is always padded
587        // to a full byte. Thus, the value -2.25 is encoded by the byte sequence (1e e2 a2 5f)
588        // and the value 0.140541E-3 by the sequence (1e 0a 14 05 41 c3 ff)."
589        //
590        // The initial 1e byte in the examples above is the dictionary operator to trigger
591        // parsing of BCD so it is dropped in the tests here.
592        let bytes = FontData::new(&[0xe2, 0xa2, 0x5f]);
593        assert_eq!(
594            parse_bcd(&mut bytes.cursor()).unwrap(),
595            Fixed::from_f64(-2.25)
596        );
597        let bytes = FontData::new(&[0x0a, 0x14, 0x05, 0x41, 0xc3, 0xff]);
598        assert_eq!(
599            parse_bcd(&mut bytes.cursor()).unwrap(),
600            Fixed::from_f64(0.140541E-3)
601        );
602    }
603
604    #[test]
605    fn example_top_dict_tokens() {
606        use Operator::*;
607        let top_dict_data = &font_test_data::cff2::EXAMPLE[5..12];
608        let tokens: Vec<_> = tokens(top_dict_data).map(|entry| entry.unwrap()).collect();
609        let expected: &[Token] = &[
610            68.into(),
611            FdArrayOffset.into(),
612            56.into(),
613            CharstringsOffset.into(),
614            16.into(),
615            VariationStoreOffset.into(),
616        ];
617        assert_eq!(&tokens, expected);
618    }
619
620    #[test]
621    fn example_top_dict_entries() {
622        use Entry::*;
623        let top_dict_data = &font_test_data::cff2::EXAMPLE[0x5..=0xB];
624        let entries: Vec<_> = entries(top_dict_data, None)
625            .map(|entry| entry.unwrap())
626            .collect();
627        let expected: &[Entry] = &[
628            FdArrayOffset(68),
629            CharstringsOffset(56),
630            VariationStoreOffset(16),
631        ];
632        assert_eq!(&entries, expected);
633    }
634
635    #[test]
636    fn example_private_dict_entries() {
637        use Entry::*;
638        let private_dict_data = &font_test_data::cff2::EXAMPLE[0x4f..=0xc0];
639        let store =
640            ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
641        let coords = &[F2Dot14::from_f32(0.0)];
642        let blend_state = BlendState::new(store, coords, 0).unwrap();
643        let entries: Vec<_> = entries(private_dict_data, Some(blend_state))
644            .map(|entry| entry.unwrap())
645            .collect();
646        fn make_blues(values: &[f64]) -> Blues {
647            Blues::new(values.iter().copied().map(Fixed::from_f64))
648        }
649        fn make_stem_snaps(values: &[f64]) -> StemSnaps {
650            StemSnaps::new(values.iter().copied().map(Fixed::from_f64))
651        }
652        let expected: &[Entry] = &[
653            BlueValues(make_blues(&[
654                -20.0, 0.0, 472.0, 490.0, 525.0, 540.0, 645.0, 660.0, 670.0, 690.0, 730.0, 750.0,
655            ])),
656            OtherBlues(make_blues(&[-250.0, -240.0])),
657            FamilyBlues(make_blues(&[
658                -20.0, 0.0, 473.0, 491.0, 525.0, 540.0, 644.0, 659.0, 669.0, 689.0, 729.0, 749.0,
659            ])),
660            FamilyOtherBlues(make_blues(&[-249.0, -239.0])),
661            BlueScale(Fixed::from_f64(0.037506103515625)),
662            BlueFuzz(Fixed::ZERO),
663            StdHw(Fixed::from_f64(55.0)),
664            StdVw(Fixed::from_f64(80.0)),
665            StemSnapH(make_stem_snaps(&[40.0, 55.0])),
666            StemSnapV(make_stem_snaps(&[80.0, 90.0])),
667            SubrsOffset(114),
668        ];
669        assert_eq!(&entries, expected);
670    }
671
672    #[test]
673    fn noto_serif_display_top_dict_entries() {
674        use Entry::*;
675        let top_dict_data = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED)
676            .unwrap()
677            .cff()
678            .unwrap()
679            .top_dicts()
680            .get(0)
681            .unwrap();
682        let entries: Vec<_> = entries(top_dict_data, None)
683            .map(|entry| entry.unwrap())
684            .collect();
685        let expected = &[
686            Version(StringId::new(391)),
687            Notice(StringId::new(392)),
688            Copyright(StringId::new(393)),
689            FullName(StringId::new(394)),
690            FamilyName(StringId::new(395)),
691            FontBbox([-693.0, -470.0, 2797.0, 1048.0].map(Fixed::from_f64)),
692            Charset(517),
693            PrivateDictRange(549..587),
694            CharstringsOffset(521),
695        ];
696        assert_eq!(&entries, expected);
697    }
698
699    // Fuzzer caught add with overflow when constructing private DICT
700    // range.
701    // See <https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=71746>
702    // and <https://oss-fuzz.com/testcase?key=4591358306746368>
703    #[test]
704    fn private_dict_range_avoid_overflow() {
705        // A Private DICT that tries to construct a range from -1..(-1 + -1)
706        // which overflows when converted to usize
707        let private_dict = BeBuffer::new()
708            .push(29u8) // integer operator
709            .push(-1i32) // integer value
710            .push(29u8) // integer operator
711            .push(-1i32) // integer value
712            .push(18u8) // PrivateDICT operator
713            .to_vec();
714        // Just don't panic
715        let _ = entries(&private_dict, None).count();
716    }
717}