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