1use super::{
4 blend::BlendState,
5 stack::{Number, Stack},
6};
7use crate::model::Error;
8use crate::{
9 ps::{
10 hinting::{Blues, StemSnaps},
11 num::{self, BcdComponents},
12 string::Sid,
13 transform::ScaledFontMatrix,
14 },
15 types::Fixed,
16 Cursor,
17};
18use std::ops::Range;
19
20#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
25pub enum Operator {
26 Version,
27 Notice,
28 FullName,
29 FamilyName,
30 Weight,
31 FontBbox,
32 CharstringsOffset,
33 PrivateDictRange,
34 VariationStoreOffset,
35 Copyright,
36 IsFixedPitch,
37 ItalicAngle,
38 UnderlinePosition,
39 UnderlineThickness,
40 PaintType,
41 CharstringType,
42 FontMatrix,
43 StrokeWidth,
44 FdArrayOffset,
45 FdSelectOffset,
46 BlueValues,
47 OtherBlues,
48 FamilyBlues,
49 FamilyOtherBlues,
50 SubrsOffset,
51 VariationStoreIndex,
52 BlueScale,
53 BlueShift,
54 BlueFuzz,
55 LanguageGroup,
56 ExpansionFactor,
57 Encoding,
58 Charset,
59 UniqueId,
60 Xuid,
61 SyntheticBase,
62 PostScript,
63 BaseFontName,
64 BaseFontBlend,
65 Ros,
66 CidFontVersion,
67 CidFontRevision,
68 CidFontType,
69 CidCount,
70 UidBase,
71 FontName,
72 StdHw,
73 StdVw,
74 DefaultWidthX,
75 NominalWidthX,
76 Blend,
77 StemSnapH,
78 StemSnapV,
79 ForceBold,
80 InitialRandomSeed,
81}
82
83impl Operator {
84 fn from_opcode(opcode: u8) -> Option<Self> {
85 use Operator::*;
86 Some(match opcode {
87 0 => Version,
89 1 => Notice,
90 2 => FullName,
91 3 => FamilyName,
92 4 => Weight,
93 5 => FontBbox,
94 13 => UniqueId,
95 14 => Xuid,
96 15 => Charset,
97 16 => Encoding,
98 17 => CharstringsOffset,
99 18 => PrivateDictRange,
100 24 => VariationStoreOffset,
101 6 => BlueValues,
103 7 => OtherBlues,
104 8 => FamilyBlues,
105 9 => FamilyOtherBlues,
106 10 => StdHw,
107 11 => StdVw,
108 19 => SubrsOffset,
109 20 => DefaultWidthX,
110 21 => NominalWidthX,
111 22 => VariationStoreIndex,
112 23 => Blend,
113 _ => return None,
115 })
116 }
117
118 fn from_extended_opcode(opcode: u8) -> Option<Self> {
119 use Operator::*;
120 Some(match opcode {
121 0 => Copyright,
123 1 => IsFixedPitch,
124 2 => ItalicAngle,
125 3 => UnderlinePosition,
126 4 => UnderlineThickness,
127 5 => PaintType,
128 6 => CharstringType,
129 7 => FontMatrix,
130 8 => StrokeWidth,
131 20 => SyntheticBase,
132 21 => PostScript,
133 22 => BaseFontName,
134 23 => BaseFontBlend,
135 30 => Ros,
136 31 => CidFontVersion,
137 32 => CidFontRevision,
138 33 => CidFontType,
139 34 => CidCount,
140 35 => UidBase,
141 36 => FdArrayOffset,
142 37 => FdSelectOffset,
143 38 => FontName,
144 9 => BlueScale,
146 10 => BlueShift,
147 11 => BlueFuzz,
148 12 => StemSnapH,
149 13 => StemSnapV,
150 14 => ForceBold,
151 17 => LanguageGroup,
152 18 => ExpansionFactor,
153 19 => InitialRandomSeed,
154 _ => return None,
155 })
156 }
157}
158
159#[derive(Copy, Clone, PartialEq, Eq, Debug)]
161pub enum Token {
162 Operator(Operator),
164 InvalidOperator(u8),
172 InvalidTwoByteOperator(u8),
177 Operand(Number, Option<BcdComponents>),
181}
182
183impl From<Operator> for Token {
184 fn from(value: Operator) -> Self {
185 Self::Operator(value)
186 }
187}
188
189impl<T> From<T> for Token
190where
191 T: Into<Number>,
192{
193 fn from(value: T) -> Self {
194 Self::Operand(value.into(), None)
195 }
196}
197
198pub fn tokens(dict_data: &[u8]) -> impl Iterator<Item = Result<Token, Error>> + '_ + Clone {
204 let mut cursor = crate::FontData::new(dict_data).cursor();
205 std::iter::from_fn(move || {
206 if cursor.remaining_bytes() == 0 {
207 None
208 } else {
209 Some(parse_token(&mut cursor))
210 }
211 })
212}
213
214fn parse_token(cursor: &mut Cursor) -> Result<Token, Error> {
215 const ESCAPE: u8 = 12;
217 let b0 = cursor.read::<u8>()?;
218 Ok(if b0 == ESCAPE {
219 let b1 = cursor.read::<u8>()?;
220 match Operator::from_extended_opcode(b1) {
221 Some(op) => Token::Operator(op),
222 None => Token::InvalidTwoByteOperator(b1),
223 }
224 } else {
225 match b0 {
227 28 | 29 | 32..=254 => Token::Operand(num::parse_int(cursor, b0)?.into(), None),
228 30 => {
229 let components = BcdComponents::parse(cursor)?;
230 Token::Operand(components.value(false).into(), Some(components))
231 }
232 _ => match Operator::from_opcode(b0) {
233 Some(op) => Token::Operator(op),
234 None => Token::InvalidOperator(b0),
235 },
236 }
237 })
238}
239
240#[derive(Clone, PartialEq, Eq, Debug)]
242pub enum Entry {
243 Version(Sid),
244 Notice(Sid),
245 FullName(Sid),
246 FamilyName(Sid),
247 Weight(Sid),
248 FontBbox([Fixed; 4]),
249 CharstringsOffset(usize),
250 PrivateDictRange(Range<usize>),
251 VariationStoreOffset(usize),
252 Copyright(Sid),
253 IsFixedPitch(bool),
254 ItalicAngle(Fixed),
255 UnderlinePosition(Fixed),
256 UnderlineThickness(Fixed),
257 PaintType(i32),
258 CharstringType(i32),
259 FontMatrix(ScaledFontMatrix),
260 StrokeWidth(Fixed),
261 FdArrayOffset(usize),
262 FdSelectOffset(usize),
263 BlueValues(Blues),
264 OtherBlues(Blues),
265 FamilyBlues(Blues),
266 FamilyOtherBlues(Blues),
267 SubrsOffset(usize),
268 VariationStoreIndex(u16),
269 BlueScale(Fixed),
270 BlueShift(Fixed),
271 BlueFuzz(Fixed),
272 LanguageGroup(i32),
273 ExpansionFactor(Fixed),
274 Encoding(usize),
275 Charset(usize),
276 UniqueId(i32),
277 Xuid,
278 SyntheticBase(i32),
279 PostScript(Sid),
280 BaseFontName(Sid),
281 BaseFontBlend,
282 Ros {
283 registry: Sid,
284 ordering: Sid,
285 supplement: Fixed,
286 },
287 CidFontVersion(Fixed),
288 CidFontRevision(Fixed),
289 CidFontType(i32),
290 CidCount(u32),
291 UidBase(i32),
292 FontName(Sid),
293 StdHw(Fixed),
294 StdVw(Fixed),
295 DefaultWidthX(Fixed),
296 NominalWidthX(Fixed),
297 StemSnapH(StemSnaps),
298 StemSnapV(StemSnaps),
299 ForceBold(bool),
300 InitialRandomSeed(i32),
301}
302
303pub fn entries<'a>(
312 dict_data: &'a [u8],
313 blend_state: Option<BlendState<'a>>,
314) -> impl Iterator<Item = Result<Entry, Error>> + 'a {
315 entries_impl(dict_data, blend_state, false)
316}
317
318pub(crate) fn unhinted_private_entries<'a>(
321 dict_data: &'a [u8],
322 blend_state: Option<BlendState<'a>>,
323) -> impl Iterator<Item = Result<Entry, Error>> + 'a {
324 entries_impl(dict_data, blend_state, true)
325}
326
327fn entries_impl<'a>(
328 dict_data: &'a [u8],
329 mut blend_state: Option<BlendState<'a>>,
330 unhinted_private: bool,
331) -> impl Iterator<Item = Result<Entry, Error>> + 'a {
332 let mut stack = Stack::new();
333 let mut last_bcd_components = None;
334 let mut cursor = crate::FontData::new(dict_data).cursor();
335 let mut cursor_pos = 0;
336 std::iter::from_fn(move || loop {
337 if cursor.remaining_bytes() == 0 {
338 return None;
339 }
340 const ESCAPE: u8 = 12;
344 let b0 = match cursor.read::<u8>() {
345 Ok(b0) => b0,
346 Err(e) => return Some(Err(e.into())),
347 };
348 let op = match b0 {
349 28 | 29 | 32..=254 => {
351 last_bcd_components = None;
352 match num::parse_int(&mut cursor, b0).and_then(|number| stack.push(number)) {
353 Ok(_) => continue,
354 Err(e) => return Some(Err(e)),
355 }
356 }
357 30 => {
358 let components = match BcdComponents::parse(&mut cursor) {
359 Ok(components) => components,
360 Err(e) => return Some(Err(e)),
361 };
362 let result = stack.push(components.value(false));
363 last_bcd_components = Some(components);
364 match result {
365 Ok(_) => continue,
366 Err(e) => return Some(Err(e)),
367 }
368 }
369 _ => {
370 let op = if b0 == ESCAPE {
371 match cursor.read::<u8>() {
372 Ok(b1) => Operator::from_extended_opcode(b1),
373 Err(e) => return Some(Err(e.into())),
374 }
375 } else {
376 Operator::from_opcode(b0)
377 };
378 match op {
379 Some(op) => op,
380 None => {
381 stack.clear();
388 continue;
389 }
390 }
391 }
392 };
393 if op == Operator::Blend || op == Operator::VariationStoreIndex {
394 let state = match blend_state.as_mut() {
395 Some(state) => state,
396 None => return Some(Err(Error::MissingResource)),
397 };
398 if op == Operator::VariationStoreIndex {
399 match stack
400 .get_i32(0)
401 .and_then(|ix| state.set_store_index(ix as u16))
402 {
403 Ok(_) => {}
404 Err(e) => return Some(Err(e)),
405 }
406 }
407 if op == Operator::Blend {
408 match stack.apply_blend(state) {
409 Ok(_) => continue,
410 Err(e) => return Some(Err(e)),
411 }
412 }
413 }
414 let keep = !unhinted_private
415 || matches!(
416 op,
417 Operator::SubrsOffset
418 | Operator::VariationStoreIndex
419 | Operator::DefaultWidthX
420 | Operator::NominalWidthX
421 );
422 if op == Operator::BlueScale {
423 if let Some(bcd_components) = last_bcd_components.take() {
428 stack.pop_fixed().ok()?;
432 stack.push(bcd_components.value(true)).ok()?;
433 }
434 }
435 if op == Operator::FontMatrix {
436 stack.clear();
441 last_bcd_components = None;
442 let mut cursor = crate::FontData::new(dict_data).cursor();
444 cursor.advance_by(cursor_pos);
445 if let Some(matrix) = ScaledFontMatrix::parse(&mut cursor) {
446 if keep {
447 return Some(Ok(Entry::FontMatrix(matrix)));
448 }
449 }
450 continue;
451 }
452 last_bcd_components = None;
453 let entry = keep.then(|| parse_entry(op, &mut stack));
454 stack.clear();
455 cursor_pos = cursor.position().unwrap_or_default();
456 if let Some(entry) = entry {
457 return Some(entry);
458 }
459 })
460}
461
462fn parse_entry(op: Operator, stack: &mut Stack) -> Result<Entry, Error> {
463 use Operator::*;
464 Ok(match op {
465 Version => Entry::Version(stack.pop_i32()?.into()),
466 Notice => Entry::Notice(stack.pop_i32()?.into()),
467 FullName => Entry::FullName(stack.pop_i32()?.into()),
468 FamilyName => Entry::FamilyName(stack.pop_i32()?.into()),
469 Weight => Entry::Weight(stack.pop_i32()?.into()),
470 FontBbox => Entry::FontBbox([
471 stack.get_fixed(0)?,
472 stack.get_fixed(1)?,
473 stack.get_fixed(2)?,
474 stack.get_fixed(3)?,
475 ]),
476 CharstringsOffset => Entry::CharstringsOffset(stack.pop_i32()? as usize),
477 PrivateDictRange => {
478 let len = stack.get_i32(0)? as usize;
479 let start = stack.get_i32(1)? as usize;
480 let end = start.checked_add(len).ok_or(Error::Malformed)?;
481 Entry::PrivateDictRange(start..end)
482 }
483 VariationStoreOffset => Entry::VariationStoreOffset(stack.pop_i32()? as usize),
484 Copyright => Entry::Copyright(stack.pop_i32()?.into()),
485 IsFixedPitch => Entry::IsFixedPitch(stack.pop_i32()? != 0),
486 ItalicAngle => Entry::ItalicAngle(stack.pop_fixed()?),
487 UnderlinePosition => Entry::UnderlinePosition(stack.pop_fixed()?),
488 UnderlineThickness => Entry::UnderlineThickness(stack.pop_fixed()?),
489 PaintType => Entry::PaintType(stack.pop_i32()?),
490 CharstringType => Entry::CharstringType(stack.pop_i32()?),
491 FontMatrix => unreachable!(),
492 StrokeWidth => Entry::StrokeWidth(stack.pop_fixed()?),
493 FdArrayOffset => Entry::FdArrayOffset(stack.pop_i32()? as usize),
494 FdSelectOffset => Entry::FdSelectOffset(stack.pop_i32()? as usize),
495 BlueValues => {
496 stack.apply_delta_prefix_sum();
497 Entry::BlueValues(Blues::new(stack.fixed_values()))
498 }
499 OtherBlues => {
500 stack.apply_delta_prefix_sum();
501 Entry::OtherBlues(Blues::new(stack.fixed_values()))
502 }
503 FamilyBlues => {
504 stack.apply_delta_prefix_sum();
505 Entry::FamilyBlues(Blues::new(stack.fixed_values()))
506 }
507 FamilyOtherBlues => {
508 stack.apply_delta_prefix_sum();
509 Entry::FamilyOtherBlues(Blues::new(stack.fixed_values()))
510 }
511 SubrsOffset => Entry::SubrsOffset(stack.pop_i32()? as usize),
512 VariationStoreIndex => Entry::VariationStoreIndex(stack.pop_i32()? as u16),
513 BlueScale => Entry::BlueScale(stack.pop_fixed()?),
514 BlueShift => Entry::BlueShift(stack.pop_fixed()?),
515 BlueFuzz => Entry::BlueFuzz(stack.pop_fixed()?),
516 LanguageGroup => Entry::LanguageGroup(stack.pop_i32()?),
517 ExpansionFactor => Entry::ExpansionFactor(stack.pop_fixed()?),
518 Encoding => Entry::Encoding(stack.pop_i32()? as usize),
519 Charset => Entry::Charset(stack.pop_i32()? as usize),
520 UniqueId => Entry::UniqueId(stack.pop_i32()?),
521 Xuid => Entry::Xuid,
522 SyntheticBase => Entry::SyntheticBase(stack.pop_i32()?),
523 PostScript => Entry::PostScript(stack.pop_i32()?.into()),
524 BaseFontName => Entry::BaseFontName(stack.pop_i32()?.into()),
525 BaseFontBlend => Entry::BaseFontBlend,
526 Ros => Entry::Ros {
527 registry: stack.get_i32(0)?.into(),
528 ordering: stack.get_i32(1)?.into(),
529 supplement: stack.get_fixed(2)?,
530 },
531 CidFontVersion => Entry::CidFontVersion(stack.pop_fixed()?),
532 CidFontRevision => Entry::CidFontRevision(stack.pop_fixed()?),
533 CidFontType => Entry::CidFontType(stack.pop_i32()?),
534 CidCount => Entry::CidCount(stack.pop_i32()? as u32),
535 UidBase => Entry::UidBase(stack.pop_i32()?),
536 FontName => Entry::FontName(stack.pop_i32()?.into()),
537 StdHw => Entry::StdHw(stack.pop_fixed()?),
538 StdVw => Entry::StdVw(stack.pop_fixed()?),
539 DefaultWidthX => Entry::DefaultWidthX(stack.pop_fixed()?),
540 NominalWidthX => Entry::NominalWidthX(stack.pop_fixed()?),
541 StemSnapH => {
542 stack.apply_delta_prefix_sum();
543 Entry::StemSnapH(StemSnaps::new(stack.fixed_values()))
544 }
545 StemSnapV => {
546 stack.apply_delta_prefix_sum();
547 Entry::StemSnapV(StemSnaps::new(stack.fixed_values()))
548 }
549 ForceBold => Entry::ForceBold(stack.pop_i32()? != 0),
550 InitialRandomSeed => Entry::InitialRandomSeed(stack.pop_i32()?),
551 Blend => unreachable!(),
553 })
554}
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559 use crate::{
560 tables::variations::ItemVariationStore, types::F2Dot14, FontData, FontRead, FontRef,
561 TableProvider,
562 };
563 use font_test_data::bebuffer::BeBuffer;
564
565 #[test]
572 fn unknown_operators_are_tokens() {
573 let dict = [139u8, 25, 139, 12, 77, 139, 17];
576 let tokens: Vec<_> = tokens(&dict).map(|tok| tok.unwrap()).collect();
577 assert_eq!(
578 &tokens,
579 &[
580 0.into(),
581 Token::InvalidOperator(25),
582 0.into(),
583 Token::InvalidTwoByteOperator(77),
584 0.into(),
585 Operator::CharstringsOffset.into(),
586 ]
587 );
588 }
589
590 #[test]
591 fn example_top_dict_tokens() {
592 use Operator::*;
593 let top_dict_data = &font_test_data::cff2::EXAMPLE[5..12];
594 let tokens: Vec<_> = tokens(top_dict_data).map(|entry| entry.unwrap()).collect();
595 let expected: &[Token] = &[
596 68.into(),
597 FdArrayOffset.into(),
598 56.into(),
599 CharstringsOffset.into(),
600 16.into(),
601 VariationStoreOffset.into(),
602 ];
603 assert_eq!(&tokens, expected);
604 }
605
606 #[test]
607 fn example_top_dict_entries() {
608 use Entry::*;
609 let top_dict_data = &font_test_data::cff2::EXAMPLE[0x5..=0xB];
610 let entries: Vec<_> = entries(top_dict_data, None)
611 .map(|entry| entry.unwrap())
612 .collect();
613 let expected: &[Entry] = &[
614 FdArrayOffset(68),
615 CharstringsOffset(56),
616 VariationStoreOffset(16),
617 ];
618 assert_eq!(&entries, expected);
619 }
620
621 #[test]
622 fn unhinted_private_entries_preserve_blends() {
623 let data = &font_test_data::cff2::EXAMPLE[0x4f..=0xc0];
624 let store =
625 ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
626 for coord in [-0.75, 0.0, 0.25, 0.75] {
627 let coords = [F2Dot14::from_f32(coord)];
628 let expected = entries(
629 data,
630 Some(BlendState::new(store.clone(), &coords, 0).unwrap()),
631 )
632 .filter_map(Result::ok)
633 .filter(|entry| {
634 matches!(
635 entry,
636 Entry::SubrsOffset(_)
637 | Entry::VariationStoreIndex(_)
638 | Entry::DefaultWidthX(_)
639 | Entry::NominalWidthX(_)
640 )
641 })
642 .collect::<Vec<_>>();
643 let actual = unhinted_private_entries(
644 data,
645 Some(BlendState::new(store.clone(), &coords, 0).unwrap()),
646 )
647 .filter_map(Result::ok)
648 .collect::<Vec<_>>();
649 assert_eq!(actual, expected);
650 }
651 }
652
653 #[test]
654 fn unhinted_private_entries_preserve_stack_and_special_operators() {
655 let data = [
656 142, 143, 6, 10, 30, 10, 0, 31, 12, 9, 28, 2, 88, 20, 30, 10, 0, 31, 139, 139, 30, 10, 0, 31, 139, 139, 12, 7, 140, 255, 159, 21, 149, 19, ];
665 let actual = unhinted_private_entries(&data, None)
666 .map(Result::unwrap)
667 .collect::<Vec<_>>();
668 assert_eq!(
669 actual,
670 [
671 Entry::DefaultWidthX(Fixed::from_i32(600)),
672 Entry::NominalWidthX(Fixed::from_i32(20)),
673 Entry::SubrsOffset(10),
674 ]
675 );
676 }
677
678 #[test]
679 fn example_private_dict_entries() {
680 use Entry::*;
681 let private_dict_data = &font_test_data::cff2::EXAMPLE[0x4f..=0xc0];
682 let store =
683 ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
684 let coords = &[F2Dot14::from_f32(0.0)];
685 let blend_state = BlendState::new(store, coords, 0).unwrap();
686 let entries: Vec<_> = entries(private_dict_data, Some(blend_state))
687 .map(|entry| entry.unwrap())
688 .collect();
689 fn make_blues(values: &[f64]) -> Blues {
690 Blues::new(values.iter().copied().map(Fixed::from_f64))
691 }
692 fn make_stem_snaps(values: &[f64]) -> StemSnaps {
693 StemSnaps::new(values.iter().copied().map(Fixed::from_f64))
694 }
695 let expected: &[Entry] = &[
696 BlueValues(make_blues(&[
697 -20.0, 0.0, 472.0, 490.0, 525.0, 540.0, 645.0, 660.0, 670.0, 690.0, 730.0, 750.0,
698 ])),
699 OtherBlues(make_blues(&[-250.0, -240.0])),
700 FamilyBlues(make_blues(&[
701 -20.0, 0.0, 473.0, 491.0, 525.0, 540.0, 644.0, 659.0, 669.0, 689.0, 729.0, 749.0,
702 ])),
703 FamilyOtherBlues(make_blues(&[-249.0, -239.0])),
704 BlueScale(Fixed::from_f64(0.037506103515625)),
705 BlueFuzz(Fixed::ZERO),
706 StdHw(Fixed::from_f64(55.0)),
707 StdVw(Fixed::from_f64(80.0)),
708 StemSnapH(make_stem_snaps(&[40.0, 55.0])),
709 StemSnapV(make_stem_snaps(&[80.0, 90.0])),
710 SubrsOffset(114),
711 ];
712 assert_eq!(&entries, expected);
713 }
714
715 #[test]
716 fn noto_serif_display_top_dict_entries() {
717 use Entry::*;
718 let top_dict_data = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED)
719 .unwrap()
720 .cff()
721 .unwrap()
722 .top_dicts()
723 .get(0)
724 .unwrap();
725 let entries: Vec<_> = entries(top_dict_data, None)
726 .map(|entry| entry.unwrap())
727 .collect();
728 let expected = &[
729 Version(Sid::new(391)),
730 Notice(Sid::new(392)),
731 Copyright(Sid::new(393)),
732 FullName(Sid::new(394)),
733 FamilyName(Sid::new(395)),
734 FontBbox([-693.0, -470.0, 2797.0, 1048.0].map(Fixed::from_f64)),
735 Charset(517),
736 PrivateDictRange(549..587),
737 CharstringsOffset(521),
738 ];
739 assert_eq!(&entries, expected);
740 }
741
742 #[test]
747 fn private_dict_range_avoid_overflow() {
748 let private_dict = BeBuffer::new()
751 .push(29u8) .push(-1i32) .push(29u8) .push(-1i32) .push(18u8) .to_vec();
757 let _ = entries(&private_dict, None).count();
759 }
760
761 #[test]
762 fn read_font_matrix() {
763 let dict_data = [
764 30u8, 10, 0, 31, 139, 30, 10, 0, 1, 103, 255, 30, 10, 0, 31, 139, 139, 12, 7,
765 ];
766 let Entry::FontMatrix(matrix) = entries(&dict_data, None).next().unwrap().unwrap() else {
767 panic!("This was totally a font matrix");
768 };
769 assert_eq!(
772 matrix.matrix.elements(),
773 [
774 Fixed::ONE,
775 Fixed::ZERO,
776 Fixed::from_f64(0.167007446289062),
777 Fixed::ONE,
778 Fixed::ZERO,
779 Fixed::ZERO,
780 ]
781 );
782 }
783
784 #[test]
785 fn parse_degenerate_font_matrix() {
786 let dict_data = [
787 30u8, 0x0F, 30, 0x0F, 30, 0x0F, 30, 0x0F, 30, 0x0F, 30, 0x0F, 12, 7,
788 ];
789 assert!(entries(&dict_data, None).next().is_none());
791 }
792}