1use std::ops::Range;
4
5use super::{BlendState, Error, Number, Stack, StringId};
6use crate::{types::Fixed, Cursor, ReadError};
7
8#[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 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 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 _ => return None,
103 })
104 }
105
106 fn from_extended_opcode(opcode: u8) -> Option<Self> {
107 use Operator::*;
108 Some(match opcode {
109 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 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#[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
169pub 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 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 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#[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
265pub 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 => unreachable!(),
417 })
418}
419
420const MAX_BLUE_VALUES: usize = 7;
422
423#[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
453const MAX_STEM_SNAPS: usize = 12;
457
458#[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 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
503fn parse_bcd(cursor: &mut Cursor) -> Result<Fixed, Error> {
506 const OVERFLOW: Fixed = Fixed::from_bits(0x7FFFFFFF);
508 const UNDERFLOW: Fixed = Fixed::ZERO;
510 const NUMBER_LIMIT: i32 = 0xCCCCCCC;
513 const INTEGER_LIMIT: i32 = 0x7FFF;
515 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 '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 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 if integer_len < 0 {
621 number /= POWER_TENS[(-integer_len) as usize];
622 fraction_len += integer_len;
623 }
624 if fraction_len == 10 {
626 number /= 10;
627 fraction_len -= 1;
628 }
629 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 let empty = FontData::new(&[]);
662 let min_byte = FontData::new(&[0]);
663 let max_byte = FontData::new(&[255]);
664 assert_eq!(parse_int(&mut empty.cursor(), 32).unwrap(), -107);
666 assert_eq!(parse_int(&mut empty.cursor(), 246).unwrap(), 107);
667 assert_eq!(parse_int(&mut min_byte.cursor(), 247).unwrap(), 108);
669 assert_eq!(parse_int(&mut max_byte.cursor(), 250).unwrap(), 1131);
670 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 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 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 #[test]
805 fn private_dict_range_avoid_overflow() {
806 let private_dict = BeBuffer::new()
809 .push(29u8) .push(-1i32) .push(29u8) .push(-1i32) .push(18u8) .to_vec();
815 let _ = entries(&private_dict, None).count();
817 }
818}