1 //! Parsing for PostScript DICTs.
2 
3 use std::ops::Range;
4 
5 use super::{BlendState, Error, Number, Stack, StringId};
6 use crate::{types::Fixed, Cursor};
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)]
13 pub 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 
71 impl Operator {
from_opcode(opcode: u8) -> Option<Self>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 
from_extended_opcode(opcode: u8) -> Option<Self>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)]
149 pub enum Token {
150     Operator(Operator),
151     Operand(Number),
152 }
153 
154 impl From<Operator> for Token {
from(value: Operator) -> Self155     fn from(value: Operator) -> Self {
156         Self::Operator(value)
157     }
158 }
159 
160 impl<T> From<T> for Token
161 where
162     T: Into<Number>,
163 {
from(value: T) -> Self164     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.
tokens(dict_data: &[u8]) -> impl Iterator<Item = Result<Token, Error>> + '_ + Clone174 pub 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 
parse_token(cursor: &mut Cursor) -> Result<Token, Error>185 fn 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)]
204 pub 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.
entries<'a>( dict_data: &'a [u8], mut blend_state: Option<BlendState<'a>>, ) -> impl Iterator<Item = Result<Entry, Error>> + 'a273 pub 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 
parse_entry(op: Operator, stack: &mut Stack) -> Result<Entry, Error>319 fn 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             Entry::PrivateDictRange(start..start + len)
338         }
339         VariationStoreOffset => Entry::VariationStoreOffset(stack.pop_i32()? as usize),
340         Copyright => Entry::Copyright(stack.pop_i32()?.into()),
341         IsFixedPitch => Entry::IsFixedPitch(stack.pop_i32()? != 0),
342         ItalicAngle => Entry::ItalicAngle(stack.pop_fixed()?),
343         UnderlinePosition => Entry::UnderlinePosition(stack.pop_fixed()?),
344         UnderlineThickness => Entry::UnderlineThickness(stack.pop_fixed()?),
345         PaintType => Entry::PaintType(stack.pop_i32()?),
346         CharstringType => Entry::CharstringType(stack.pop_i32()?),
347         FontMatrix => Entry::FontMatrix([
348             stack.get_fixed(0)?,
349             stack.get_fixed(1)?,
350             stack.get_fixed(2)?,
351             stack.get_fixed(3)?,
352             stack.get_fixed(4)?,
353             stack.get_fixed(5)?,
354         ]),
355         StrokeWidth => Entry::StrokeWidth(stack.pop_fixed()?),
356         FdArrayOffset => Entry::FdArrayOffset(stack.pop_i32()? as usize),
357         FdSelectOffset => Entry::FdSelectOffset(stack.pop_i32()? as usize),
358         BlueValues => {
359             stack.apply_delta_prefix_sum();
360             Entry::BlueValues(Blues::new(stack.fixed_values()))
361         }
362         OtherBlues => {
363             stack.apply_delta_prefix_sum();
364             Entry::OtherBlues(Blues::new(stack.fixed_values()))
365         }
366         FamilyBlues => {
367             stack.apply_delta_prefix_sum();
368             Entry::FamilyBlues(Blues::new(stack.fixed_values()))
369         }
370         FamilyOtherBlues => {
371             stack.apply_delta_prefix_sum();
372             Entry::FamilyOtherBlues(Blues::new(stack.fixed_values()))
373         }
374         SubrsOffset => Entry::SubrsOffset(stack.pop_i32()? as usize),
375         VariationStoreIndex => Entry::VariationStoreIndex(stack.pop_i32()? as u16),
376         BlueScale => Entry::BlueScale(stack.pop_fixed()?),
377         BlueShift => Entry::BlueShift(stack.pop_fixed()?),
378         BlueFuzz => Entry::BlueFuzz(stack.pop_fixed()?),
379         LanguageGroup => Entry::LanguageGroup(stack.pop_i32()?),
380         ExpansionFactor => Entry::ExpansionFactor(stack.pop_fixed()?),
381         Encoding => Entry::Encoding(stack.pop_i32()? as usize),
382         Charset => Entry::Charset(stack.pop_i32()? as usize),
383         UniqueId => Entry::UniqueId(stack.pop_i32()?),
384         Xuid => Entry::Xuid,
385         SyntheticBase => Entry::SyntheticBase(stack.pop_i32()?),
386         PostScript => Entry::PostScript(stack.pop_i32()?.into()),
387         BaseFontName => Entry::BaseFontName(stack.pop_i32()?.into()),
388         BaseFontBlend => Entry::BaseFontBlend,
389         Ros => Entry::Ros {
390             registry: stack.get_i32(0)?.into(),
391             ordering: stack.get_i32(1)?.into(),
392             supplement: stack.get_fixed(2)?,
393         },
394         CidFontVersion => Entry::CidFontVersion(stack.pop_fixed()?),
395         CidFontRevision => Entry::CidFontRevision(stack.pop_fixed()?),
396         CidFontType => Entry::CidFontType(stack.pop_i32()?),
397         CidCount => Entry::CidCount(stack.pop_i32()? as u32),
398         UidBase => Entry::UidBase(stack.pop_i32()?),
399         FontName => Entry::FontName(stack.pop_i32()?.into()),
400         StdHw => Entry::StdHw(stack.pop_fixed()?),
401         StdVw => Entry::StdVw(stack.pop_fixed()?),
402         DefaultWidthX => Entry::DefaultWidthX(stack.pop_fixed()?),
403         NominalWidthX => Entry::NominalWidthX(stack.pop_fixed()?),
404         StemSnapH => {
405             stack.apply_delta_prefix_sum();
406             Entry::StemSnapH(StemSnaps::new(stack.fixed_values()))
407         }
408         StemSnapV => {
409             stack.apply_delta_prefix_sum();
410             Entry::StemSnapV(StemSnaps::new(stack.fixed_values()))
411         }
412         ForceBold => Entry::ForceBold(stack.pop_i32()? != 0),
413         InitialRandomSeed => Entry::InitialRandomSeed(stack.pop_i32()?),
414         // Blend is handled at the layer above
415         Blend => unreachable!(),
416     })
417 }
418 
419 /// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psblues.h#L141>
420 const MAX_BLUE_VALUES: usize = 7;
421 
422 /// Operand for the `BlueValues`, `OtherBlues`, `FamilyBlues` and
423 /// `FamilyOtherBlues` operators.
424 ///
425 /// These are used to generate zones when applying hints.
426 #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
427 pub struct Blues {
428     values: [(Fixed, Fixed); MAX_BLUE_VALUES],
429     len: u32,
430 }
431 
432 impl Blues {
new(values: impl Iterator<Item = Fixed>) -> Self433     pub fn new(values: impl Iterator<Item = Fixed>) -> Self {
434         let mut blues = Self::default();
435         let mut stash = Fixed::ZERO;
436         for (i, value) in values.take(MAX_BLUE_VALUES * 2).enumerate() {
437             if (i & 1) == 0 {
438                 stash = value;
439             } else {
440                 blues.values[i / 2] = (stash, value);
441                 blues.len += 1;
442             }
443         }
444         blues
445     }
446 
values(&self) -> &[(Fixed, Fixed)]447     pub fn values(&self) -> &[(Fixed, Fixed)] {
448         &self.values[..self.len as usize]
449     }
450 }
451 
452 /// Summary: older PostScript interpreters accept two values, but newer ones
453 /// accept 12. We'll assume that as maximum.
454 /// <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5049.StemSnap.pdf>
455 const MAX_STEM_SNAPS: usize = 12;
456 
457 /// Operand for the `StemSnapH` and `StemSnapV` operators.
458 ///
459 /// These are used for stem darkening when applying hints.
460 #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
461 pub struct StemSnaps {
462     values: [Fixed; MAX_STEM_SNAPS],
463     len: u32,
464 }
465 
466 impl StemSnaps {
new(values: impl Iterator<Item = Fixed>) -> Self467     fn new(values: impl Iterator<Item = Fixed>) -> Self {
468         let mut snaps = Self::default();
469         for (value, target_value) in values.take(MAX_STEM_SNAPS).zip(&mut snaps.values) {
470             *target_value = value;
471             snaps.len += 1;
472         }
473         snaps
474     }
475 
values(&self) -> &[Fixed]476     pub fn values(&self) -> &[Fixed] {
477         &self.values[..self.len as usize]
478     }
479 }
480 
parse_int(cursor: &mut Cursor, b0: u8) -> Result<i32, Error>481 pub(crate) fn parse_int(cursor: &mut Cursor, b0: u8) -> Result<i32, Error> {
482     // Size   b0 range     Value range              Value calculation
483     //--------------------------------------------------------------------------------
484     // 1      32 to 246    -107 to +107             b0 - 139
485     // 2      247 to 250   +108 to +1131            (b0 - 247) * 256 + b1 + 108
486     // 2      251 to 254   -1131 to -108            -(b0 - 251) * 256 - b1 - 108
487     // 3      28           -32768 to +32767         b1 << 8 | b2
488     // 5      29           -(2^31) to +(2^31 - 1)   b1 << 24 | b2 << 16 | b3 << 8 | b4
489     // <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-3-operand-encoding>
490     Ok(match b0 {
491         32..=246 => b0 as i32 - 139,
492         247..=250 => (b0 as i32 - 247) * 256 + cursor.read::<u8>()? as i32 + 108,
493         251..=254 => -(b0 as i32 - 251) * 256 - cursor.read::<u8>()? as i32 - 108,
494         28 => cursor.read::<i16>()? as i32,
495         29 => cursor.read::<i32>()?,
496         _ => {
497             return Err(Error::InvalidNumber);
498         }
499     })
500 }
501 
502 /// Parse a binary coded decimal number.
parse_bcd(cursor: &mut Cursor) -> Result<Fixed, Error>503 fn parse_bcd(cursor: &mut Cursor) -> Result<Fixed, Error> {
504     // fonttools says:
505     // "Note: 14 decimal digits seems to be the limitation for CFF real numbers
506     // in macOS. However, we use 8 here to match the implementation of AFDKO."
507     // <https://github.com/fonttools/fonttools/blob/84cebca6a1709085b920783400ceb1a147d51842/Lib/fontTools/misc/psCharStrings.py#L269>
508     // So, 32 should be big enough for anybody?
509     const MAX_LEN: usize = 32;
510     let mut buf = [0u8; MAX_LEN];
511     let mut n = 0;
512     let mut push = |byte| {
513         if n < MAX_LEN {
514             buf[n] = byte;
515             n += 1;
516             Ok(())
517         } else {
518             Err(Error::InvalidNumber)
519         }
520     };
521     // Nibble value    Represents
522     //----------------------------------
523     // 0 to 9          0 to 9
524     // a               . (decimal point)
525     // b               E
526     // c               E-
527     // d               <reserved>
528     // e               - (minus)
529     // f               end of number
530     // <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-5-nibble-definitions>
531     'outer: loop {
532         let b = cursor.read::<u8>()?;
533         for nibble in [(b >> 4) & 0xF, b & 0xF] {
534             match nibble {
535                 0x0..=0x9 => push(b'0' + nibble)?,
536                 0xA => push(b'.')?,
537                 0xB => push(b'E')?,
538                 0xC => {
539                     push(b'E')?;
540                     push(b'-')?;
541                 }
542                 0xE => push(b'-')?,
543                 0xF => break 'outer,
544                 _ => return Err(Error::InvalidNumber),
545             }
546         }
547     }
548     std::str::from_utf8(&buf[..n])
549         .map_or(None, |buf| buf.parse::<f64>().ok())
550         .map(Fixed::from_f64)
551         .ok_or(Error::InvalidNumber)
552 }
553 
554 #[cfg(test)]
555 mod tests {
556     use super::*;
557     use crate::{
558         tables::variations::ItemVariationStore, types::F2Dot14, FontData, FontRead, FontRef,
559         TableProvider,
560     };
561 
562     #[test]
int_operands()563     fn int_operands() {
564         // Test the boundary conditions of the ranged int operators
565         let empty = FontData::new(&[]);
566         let min_byte = FontData::new(&[0]);
567         let max_byte = FontData::new(&[255]);
568         // 32..=246 => -107..=107
569         assert_eq!(parse_int(&mut empty.cursor(), 32).unwrap(), -107);
570         assert_eq!(parse_int(&mut empty.cursor(), 246).unwrap(), 107);
571         // 247..=250 => +108 to +1131
572         assert_eq!(parse_int(&mut min_byte.cursor(), 247).unwrap(), 108);
573         assert_eq!(parse_int(&mut max_byte.cursor(), 250).unwrap(), 1131);
574         // 251..=254 => -1131 to -108
575         assert_eq!(parse_int(&mut min_byte.cursor(), 251).unwrap(), -108);
576         assert_eq!(parse_int(&mut max_byte.cursor(), 254).unwrap(), -1131);
577     }
578 
579     #[test]
binary_coded_decimal_operands()580     fn binary_coded_decimal_operands() {
581         // From <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-5-nibble-definitions>:
582         //
583         // "A real number is terminated by one (or two) 0xf nibbles so that it is always padded
584         // to a full byte. Thus, the value -2.25 is encoded by the byte sequence (1e e2 a2 5f)
585         // and the value 0.140541E-3 by the sequence (1e 0a 14 05 41 c3 ff)."
586         //
587         // The initial 1e byte in the examples above is the dictionary operator to trigger
588         // parsing of BCD so it is dropped in the tests here.
589         let bytes = FontData::new(&[0xe2, 0xa2, 0x5f]);
590         assert_eq!(
591             parse_bcd(&mut bytes.cursor()).unwrap(),
592             Fixed::from_f64(-2.25)
593         );
594         let bytes = FontData::new(&[0x0a, 0x14, 0x05, 0x41, 0xc3, 0xff]);
595         assert_eq!(
596             parse_bcd(&mut bytes.cursor()).unwrap(),
597             Fixed::from_f64(0.140541E-3)
598         );
599     }
600 
601     #[test]
example_top_dict_tokens()602     fn example_top_dict_tokens() {
603         use Operator::*;
604         let top_dict_data = &font_test_data::cff2::EXAMPLE[5..12];
605         let tokens: Vec<_> = tokens(top_dict_data).map(|entry| entry.unwrap()).collect();
606         let expected: &[Token] = &[
607             68.into(),
608             FdArrayOffset.into(),
609             56.into(),
610             CharstringsOffset.into(),
611             16.into(),
612             VariationStoreOffset.into(),
613         ];
614         assert_eq!(&tokens, expected);
615     }
616 
617     #[test]
example_top_dict_entries()618     fn example_top_dict_entries() {
619         use Entry::*;
620         let top_dict_data = &font_test_data::cff2::EXAMPLE[0x5..=0xB];
621         let entries: Vec<_> = entries(top_dict_data, None)
622             .map(|entry| entry.unwrap())
623             .collect();
624         let expected: &[Entry] = &[
625             FdArrayOffset(68),
626             CharstringsOffset(56),
627             VariationStoreOffset(16),
628         ];
629         assert_eq!(&entries, expected);
630     }
631 
632     #[test]
example_private_dict_entries()633     fn example_private_dict_entries() {
634         use Entry::*;
635         let private_dict_data = &font_test_data::cff2::EXAMPLE[0x4f..=0xc0];
636         let store =
637             ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
638         let coords = &[F2Dot14::from_f32(0.0)];
639         let blend_state = BlendState::new(store, coords, 0).unwrap();
640         let entries: Vec<_> = entries(private_dict_data, Some(blend_state))
641             .map(|entry| entry.unwrap())
642             .collect();
643         fn make_blues(values: &[f64]) -> Blues {
644             Blues::new(values.iter().copied().map(Fixed::from_f64))
645         }
646         fn make_stem_snaps(values: &[f64]) -> StemSnaps {
647             StemSnaps::new(values.iter().copied().map(Fixed::from_f64))
648         }
649         let expected: &[Entry] = &[
650             BlueValues(make_blues(&[
651                 -20.0, 0.0, 472.0, 490.0, 525.0, 540.0, 645.0, 660.0, 670.0, 690.0, 730.0, 750.0,
652             ])),
653             OtherBlues(make_blues(&[-250.0, -240.0])),
654             FamilyBlues(make_blues(&[
655                 -20.0, 0.0, 473.0, 491.0, 525.0, 540.0, 644.0, 659.0, 669.0, 689.0, 729.0, 749.0,
656             ])),
657             FamilyOtherBlues(make_blues(&[-249.0, -239.0])),
658             BlueScale(Fixed::from_f64(0.037506103515625)),
659             BlueFuzz(Fixed::ZERO),
660             StdHw(Fixed::from_f64(55.0)),
661             StdVw(Fixed::from_f64(80.0)),
662             StemSnapH(make_stem_snaps(&[40.0, 55.0])),
663             StemSnapV(make_stem_snaps(&[80.0, 90.0])),
664             SubrsOffset(114),
665         ];
666         assert_eq!(&entries, expected);
667     }
668 
669     #[test]
noto_serif_display_top_dict_entries()670     fn noto_serif_display_top_dict_entries() {
671         use Entry::*;
672         let top_dict_data = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED)
673             .unwrap()
674             .cff()
675             .unwrap()
676             .top_dicts()
677             .get(0)
678             .unwrap();
679         let entries: Vec<_> = entries(top_dict_data, None)
680             .map(|entry| entry.unwrap())
681             .collect();
682         let expected = &[
683             Version(StringId::new(391)),
684             Notice(StringId::new(392)),
685             Copyright(StringId::new(393)),
686             FullName(StringId::new(394)),
687             FamilyName(StringId::new(395)),
688             FontBbox([-693.0, -470.0, 2797.0, 1048.0].map(Fixed::from_f64)),
689             Charset(517),
690             PrivateDictRange(549..587),
691             CharstringsOffset(521),
692         ];
693         assert_eq!(&entries, expected);
694     }
695 }
696