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.
504/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/82090e67c24259c343c83fd9cefe6ff0be7a7eca/src/cff/cffparse.c#L183>
505fn parse_bcd(cursor: &mut Cursor) -> Result<Fixed, Error> {
506    // Value returned on overflow
507    const OVERFLOW: Fixed = Fixed::from_bits(0x7FFFFFFF);
508    // Value returned on underflow
509    const UNDERFLOW: Fixed = Fixed::ZERO;
510    // Limit at which we stop accumulating `number` and increase
511    // the exponent instead
512    const NUMBER_LIMIT: i32 = 0xCCCCCCC;
513    // Limit for the integral part of the result
514    const INTEGER_LIMIT: i32 = 0x7FFF;
515    // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/82090e67c24259c343c83fd9cefe6ff0be7a7eca/src/cff/cffparse.c#L150>
516    const POWER_TENS: [i32; 10] = [
517        1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000,
518    ];
519    enum Phase {
520        Integer,
521        Fraction,
522        Exponent,
523    }
524    let mut phase = Phase::Integer;
525    let mut sign = 1i32;
526    let mut exponent_sign = 1i32;
527    let mut number = 0i32;
528    let mut exponent = 0i32;
529    let mut exponent_add = 0i32;
530    let mut integer_len = 0;
531    let mut fraction_len = 0;
532    // Nibble value    Represents
533    //----------------------------------
534    // 0 to 9          0 to 9
535    // a               . (decimal point)
536    // b               E
537    // c               E-
538    // d               <reserved>
539    // e               - (minus)
540    // f               end of number
541    // <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-5-nibble-definitions>
542    'outer: loop {
543        let b = cursor.read::<u8>()?;
544        for nibble in [(b >> 4) & 0xF, b & 0xF] {
545            match phase {
546                Phase::Integer => match nibble {
547                    0x0..=0x9 => {
548                        if number >= NUMBER_LIMIT {
549                            exponent_add += 1;
550                        } else if nibble != 0 || number != 0 {
551                            number = number * 10 + nibble as i32;
552                            integer_len += 1;
553                        }
554                    }
555                    0xE => sign = -1,
556                    0xA => {
557                        phase = Phase::Fraction;
558                    }
559                    0xB => {
560                        phase = Phase::Exponent;
561                    }
562                    0xC => {
563                        phase = Phase::Exponent;
564                        exponent_sign = -1;
565                    }
566                    _ => break 'outer,
567                },
568                Phase::Fraction => match nibble {
569                    0x0..=0x9 => {
570                        if nibble == 0 && number == 0 {
571                            exponent_add -= 1;
572                        } else if number < NUMBER_LIMIT && fraction_len < 9 {
573                            number = number * 10 + nibble as i32;
574                            fraction_len += 1;
575                        }
576                    }
577                    0xB => {
578                        phase = Phase::Exponent;
579                    }
580                    0xC => {
581                        phase = Phase::Exponent;
582                        exponent_sign = -1;
583                    }
584                    _ => break 'outer,
585                },
586                Phase::Exponent => {
587                    match nibble {
588                        0x0..=0x9 => {
589                            // Arbitrarily limit exponent
590                            if exponent > 1000 {
591                                return if exponent_sign == -1 {
592                                    Ok(UNDERFLOW)
593                                } else {
594                                    Ok(OVERFLOW)
595                                };
596                            } else {
597                                exponent = exponent * 10 + nibble as i32;
598                            }
599                        }
600                        _ => break 'outer,
601                    }
602                }
603            }
604        }
605    }
606    if number == 0 {
607        return Ok(Fixed::ZERO);
608    }
609    exponent *= exponent_sign;
610    exponent += exponent_add;
611    integer_len += exponent;
612    fraction_len -= exponent;
613    if integer_len > 5 {
614        return Ok(OVERFLOW);
615    }
616    if integer_len < -5 {
617        return Ok(UNDERFLOW);
618    }
619    // Remove non-significant digits
620    if integer_len < 0 {
621        number /= POWER_TENS[(-integer_len) as usize];
622        fraction_len += integer_len;
623    }
624    // Can only happen if exponent was non-zero
625    if fraction_len == 10 {
626        number /= 10;
627        fraction_len -= 1;
628    }
629    // Convert to fixed
630    let result = if fraction_len > 0 {
631        let b = POWER_TENS[fraction_len as usize];
632        if number / b > INTEGER_LIMIT {
633            0
634        } else {
635            (Fixed::from_bits(number) / Fixed::from_bits(b)).to_bits()
636        }
637    } else {
638        number = number.wrapping_mul(-fraction_len);
639        if number > INTEGER_LIMIT {
640            return Ok(OVERFLOW);
641        } else {
642            number << 16
643        }
644    };
645    Ok(Fixed::from_bits(result * sign))
646}
647
648#[cfg(test)]
649mod tests {
650    use font_test_data::bebuffer::BeBuffer;
651
652    use super::*;
653    use crate::{
654        tables::variations::ItemVariationStore, types::F2Dot14, FontData, FontRead, FontRef,
655        TableProvider,
656    };
657
658    #[test]
659    fn int_operands() {
660        // Test the boundary conditions of the ranged int operators
661        let empty = FontData::new(&[]);
662        let min_byte = FontData::new(&[0]);
663        let max_byte = FontData::new(&[255]);
664        // 32..=246 => -107..=107
665        assert_eq!(parse_int(&mut empty.cursor(), 32).unwrap(), -107);
666        assert_eq!(parse_int(&mut empty.cursor(), 246).unwrap(), 107);
667        // 247..=250 => +108 to +1131
668        assert_eq!(parse_int(&mut min_byte.cursor(), 247).unwrap(), 108);
669        assert_eq!(parse_int(&mut max_byte.cursor(), 250).unwrap(), 1131);
670        // 251..=254 => -1131 to -108
671        assert_eq!(parse_int(&mut min_byte.cursor(), 251).unwrap(), -108);
672        assert_eq!(parse_int(&mut max_byte.cursor(), 254).unwrap(), -1131);
673    }
674
675    #[test]
676    fn binary_coded_decimal_operands() {
677        // From <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-5-nibble-definitions>:
678        //
679        // "A real number is terminated by one (or two) 0xf nibbles so that it is always padded
680        // to a full byte. Thus, the value -2.25 is encoded by the byte sequence (1e e2 a2 5f)
681        // and the value 0.140541E-3 by the sequence (1e 0a 14 05 41 c3 ff)."
682        //
683        // The initial 1e byte in the examples above is the dictionary operator to trigger
684        // parsing of BCD so it is dropped in the tests here.
685        let bytes = FontData::new(&[0xe2, 0xa2, 0x5f]);
686        assert_eq!(
687            parse_bcd(&mut bytes.cursor()).unwrap(),
688            Fixed::from_f64(-2.25)
689        );
690        let bytes = FontData::new(&[0x0a, 0x14, 0x05, 0x41, 0xc3, 0xff]);
691        assert_eq!(
692            parse_bcd(&mut bytes.cursor()).unwrap(),
693            Fixed::from_f64(0.140541E-3)
694        );
695        // Check that we match FreeType for 375e-4.
696        // Note: we used to parse 0.0375... but the new FT matching code
697        // has less precision
698        let bytes = FontData::new(&[0x37, 0x5c, 0x4f]);
699        assert_eq!(
700            parse_bcd(&mut bytes.cursor()).unwrap(),
701            Fixed::from_f64(0.0370025634765625)
702        );
703    }
704
705    #[test]
706    fn example_top_dict_tokens() {
707        use Operator::*;
708        let top_dict_data = &font_test_data::cff2::EXAMPLE[5..12];
709        let tokens: Vec<_> = tokens(top_dict_data).map(|entry| entry.unwrap()).collect();
710        let expected: &[Token] = &[
711            68.into(),
712            FdArrayOffset.into(),
713            56.into(),
714            CharstringsOffset.into(),
715            16.into(),
716            VariationStoreOffset.into(),
717        ];
718        assert_eq!(&tokens, expected);
719    }
720
721    #[test]
722    fn example_top_dict_entries() {
723        use Entry::*;
724        let top_dict_data = &font_test_data::cff2::EXAMPLE[0x5..=0xB];
725        let entries: Vec<_> = entries(top_dict_data, None)
726            .map(|entry| entry.unwrap())
727            .collect();
728        let expected: &[Entry] = &[
729            FdArrayOffset(68),
730            CharstringsOffset(56),
731            VariationStoreOffset(16),
732        ];
733        assert_eq!(&entries, expected);
734    }
735
736    #[test]
737    fn example_private_dict_entries() {
738        use Entry::*;
739        let private_dict_data = &font_test_data::cff2::EXAMPLE[0x4f..=0xc0];
740        let store =
741            ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
742        let coords = &[F2Dot14::from_f32(0.0)];
743        let blend_state = BlendState::new(store, coords, 0).unwrap();
744        let entries: Vec<_> = entries(private_dict_data, Some(blend_state))
745            .map(|entry| entry.unwrap())
746            .collect();
747        fn make_blues(values: &[f64]) -> Blues {
748            Blues::new(values.iter().copied().map(Fixed::from_f64))
749        }
750        fn make_stem_snaps(values: &[f64]) -> StemSnaps {
751            StemSnaps::new(values.iter().copied().map(Fixed::from_f64))
752        }
753        let expected: &[Entry] = &[
754            BlueValues(make_blues(&[
755                -20.0, 0.0, 472.0, 490.0, 525.0, 540.0, 645.0, 660.0, 670.0, 690.0, 730.0, 750.0,
756            ])),
757            OtherBlues(make_blues(&[-250.0, -240.0])),
758            FamilyBlues(make_blues(&[
759                -20.0, 0.0, 473.0, 491.0, 525.0, 540.0, 644.0, 659.0, 669.0, 689.0, 729.0, 749.0,
760            ])),
761            FamilyOtherBlues(make_blues(&[-249.0, -239.0])),
762            BlueScale(Fixed::from_f64(0.0370025634765625)),
763            BlueFuzz(Fixed::ZERO),
764            StdHw(Fixed::from_f64(55.0)),
765            StdVw(Fixed::from_f64(80.0)),
766            StemSnapH(make_stem_snaps(&[40.0, 55.0])),
767            StemSnapV(make_stem_snaps(&[80.0, 90.0])),
768            SubrsOffset(114),
769        ];
770        assert_eq!(&entries, expected);
771    }
772
773    #[test]
774    fn noto_serif_display_top_dict_entries() {
775        use Entry::*;
776        let top_dict_data = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED)
777            .unwrap()
778            .cff()
779            .unwrap()
780            .top_dicts()
781            .get(0)
782            .unwrap();
783        let entries: Vec<_> = entries(top_dict_data, None)
784            .map(|entry| entry.unwrap())
785            .collect();
786        let expected = &[
787            Version(StringId::new(391)),
788            Notice(StringId::new(392)),
789            Copyright(StringId::new(393)),
790            FullName(StringId::new(394)),
791            FamilyName(StringId::new(395)),
792            FontBbox([-693.0, -470.0, 2797.0, 1048.0].map(Fixed::from_f64)),
793            Charset(517),
794            PrivateDictRange(549..587),
795            CharstringsOffset(521),
796        ];
797        assert_eq!(&entries, expected);
798    }
799
800    // Fuzzer caught add with overflow when constructing private DICT
801    // range.
802    // See <https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=71746>
803    // and <https://oss-fuzz.com/testcase?key=4591358306746368>
804    #[test]
805    fn private_dict_range_avoid_overflow() {
806        // A Private DICT that tries to construct a range from -1..(-1 + -1)
807        // which overflows when converted to usize
808        let private_dict = BeBuffer::new()
809            .push(29u8) // integer operator
810            .push(-1i32) // integer value
811            .push(29u8) // integer operator
812            .push(-1i32) // integer value
813            .push(18u8) // PrivateDICT operator
814            .to_vec();
815        // Just don't panic
816        let _ = entries(&private_dict, None).count();
817    }
818}