1use crate::{
4 model::pen::OutlinePen,
5 ps::{
6 cff::{
7 blend::BlendState, charset::Charset, dict, encoding::Encoding as RawEncoding,
8 fd_select::FdSelect, index::Index,
9 },
10 cs::{self, CommandSink, NopFilterSink, TransformSink},
11 encoding::PredefinedEncoding,
12 error::Error,
13 hinting::HintingParams,
14 string::Sid,
15 transform::{self, ScaledFontMatrix, Transform},
16 },
17 tables::{cff, cff2, variations::ItemVariationStore},
18 FontData, FontRead, ReadError,
19};
20use core::ops::Range;
21use types::{BoundingBox, F2Dot14, Fixed, GlyphId};
22
23#[derive(Clone)]
28pub struct CffFontRef<'a> {
29 data: &'a [u8],
30 is_cff2: bool,
31 upem: i32,
32 global_subrs: Index<'a>,
33 top_dict: TopDict<'a>,
34 top_dict_index: u16,
35}
36
37impl<'a> CffFontRef<'a> {
38 pub fn new(data: &'a [u8], top_dict_index: u32, upem: Option<i32>) -> Result<Self, Error> {
46 let version: u8 = FontData::new(data).read_at(0)?;
47 match version {
48 1 => Self::new_cff(data, top_dict_index, upem),
49 2 => Self::new_cff2(data, upem),
50 _ => Err(Error::InvalidFontFormat),
51 }
52 }
53
54 pub fn new_cff(data: &'a [u8], top_dict_index: u32, upem: Option<i32>) -> Result<Self, Error> {
59 let cff = cff::Cff::read(data.into())?;
60 let top_dict_data = cff.top_dicts().get(top_dict_index as usize)?;
61 let top_dict_index: u16 = top_dict_index
62 .try_into()
63 .map_err(|_| ReadError::OutOfBounds)?;
64 Self::new_impl(
65 data,
66 false,
67 upem,
68 top_dict_data,
69 top_dict_index,
70 cff.strings().into(),
71 cff.global_subrs().into(),
72 )
73 }
74
75 pub fn new_cff2(data: &'a [u8], upem: Option<i32>) -> Result<Self, Error> {
80 let cff = cff2::Cff2::read(data.into())?;
81 Self::new_impl(
82 data,
83 true,
84 upem,
85 cff.top_dict_data(),
86 0,
87 Index::Empty,
88 cff.global_subrs().into(),
89 )
90 }
91
92 fn new_impl(
93 data: &'a [u8],
94 is_cff2: bool,
95 upem: Option<i32>,
96 top_dict_data: &'a [u8],
97 top_dict_index: u16,
98 strings: Index<'a>,
99 global_subrs: Index<'a>,
100 ) -> Result<Self, Error> {
101 let top_dict = TopDict::new(data, top_dict_data, strings, is_cff2)?;
102 let top_upem = top_dict.matrix.map(|mat| mat.scale).unwrap_or(1000);
103 Ok(Self {
104 data,
105 is_cff2,
106 upem: upem.unwrap_or(top_upem),
107 global_subrs,
108 top_dict,
109 top_dict_index,
110 })
111 }
112
113 pub fn data(&self) -> &'a [u8] {
115 self.data
116 }
117
118 pub fn version(&self) -> u16 {
120 if self.is_cff2 {
121 2
122 } else {
123 1
124 }
125 }
126
127 pub fn metadata(&self) -> Option<Metadata<'a>> {
129 Metadata::new(self.data, self.top_dict_index)
130 }
131
132 pub fn is_cid(&self) -> bool {
134 matches!(&self.top_dict.kind, CffFontKind::Cid { .. })
135 }
136
137 pub fn global_subrs(&self) -> &Index<'a> {
139 &self.global_subrs
140 }
141
142 pub fn num_glyphs(&self) -> u32 {
144 self.top_dict.charstrings.count()
145 }
146
147 pub fn charstrings(&self) -> &Index<'a> {
149 &self.top_dict.charstrings
150 }
151
152 pub fn strings(&self) -> Option<&Index<'a>> {
154 match &self.top_dict.kind {
155 CffFontKind::Sid { strings, .. } => Some(strings),
156 _ => None,
157 }
158 }
159
160 pub fn string(&self, sid: Sid) -> Option<&'a [u8]> {
162 match sid.resolve_standard() {
163 Ok(s) => Some(s),
164 Err(idx) => self.strings()?.get(idx).ok(),
165 }
166 }
167
168 pub fn charset(&self) -> Option<Charset<'a>> {
173 Charset::new(
174 self.data.into(),
175 self.top_dict.charset_offset.get()?,
176 self.top_dict.charstrings.count(),
177 )
178 .ok()
179 }
180
181 pub fn encoding(&self) -> Option<Encoding<'a>> {
183 let charset = self.charset()?;
184 let encoding = RawEncoding::new(self.data, self.top_dict.encoding_offset.get()?).ok()?;
185 Some(Encoding { encoding, charset })
186 }
187
188 pub fn matrix(&self) -> Option<&ScaledFontMatrix> {
190 self.top_dict.matrix.as_ref()
191 }
192
193 pub fn upem(&self) -> i32 {
195 self.upem
196 }
197
198 pub fn var_store(&self) -> Option<&ItemVariationStore<'a>> {
202 self.top_dict.var_store.as_ref()
203 }
204
205 pub fn num_subfonts(&self) -> u16 {
207 match &self.top_dict.kind {
208 CffFontKind::Sid { .. } => 1,
209 CffFontKind::Cid { fd_array, .. } | CffFontKind::Cff2 { fd_array, .. } => {
210 fd_array.count() as u16
211 }
212 }
213 }
214
215 pub fn subfont_index(&self, gid: GlyphId) -> Option<u16> {
217 match &self.top_dict.kind {
218 CffFontKind::Sid { .. } => Some(0),
219 CffFontKind::Cid { fd_select, .. } | CffFontKind::Cff2 { fd_select, .. } => fd_select
220 .as_ref()
221 .map_or(Some(0), |fds| fds.font_index(gid)),
222 }
223 }
224
225 pub fn subfont(&self, index: u16, coords: &[F2Dot14]) -> Result<Subfont, Error> {
228 let blend = self.blend_state(0, coords);
229 match &self.top_dict.kind {
230 CffFontKind::Sid { private_dict, .. } => Subfont::new(
231 self.data,
232 private_dict.start as usize..private_dict.end as usize,
233 blend,
234 None,
235 ),
236 CffFontKind::Cid { fd_array, .. } | CffFontKind::Cff2 { fd_array, .. } => {
237 let font_dict = FontDict::new(fd_array.get(index as usize)?)?;
238 Subfont::new(
239 self.data,
240 font_dict.private_dict_range,
241 blend,
242 font_dict.matrix,
243 )
244 }
245 }
246 }
247
248 pub fn subfont_hinted(
251 &self,
252 index: u16,
253 coords: &[F2Dot14],
254 ) -> Result<(Subfont, HintingParams), Error> {
255 let blend = self.blend_state(0, coords);
256 match &self.top_dict.kind {
257 CffFontKind::Sid { private_dict, .. } => Subfont::new_hinted(
258 self.data,
259 private_dict.start as usize..private_dict.end as usize,
260 blend,
261 None,
262 ),
263 CffFontKind::Cid { fd_array, .. } | CffFontKind::Cff2 { fd_array, .. } => {
264 let font_dict = FontDict::new(fd_array.get(index as usize)?)?;
265 Subfont::new_hinted(
266 self.data,
267 font_dict.private_dict_range,
268 blend,
269 font_dict.matrix,
270 )
271 }
272 }
273 }
274
275 pub fn transform(&self, subfont: &Subfont, ppem: Option<f32>) -> Transform {
278 let mut scale = ppem.map(|ppem| Transform::compute_scale(ppem, self.upem));
279 let scaled_matrix = if let Some(top_matrix) = self.top_dict.matrix {
282 if let Some(sub_matrix) = subfont.matrix {
284 let scaling = if top_matrix.scale > 1 && sub_matrix.scale > 1 {
285 top_matrix.scale.min(sub_matrix.scale)
286 } else {
287 1
288 };
289 let matrix =
291 transform::combine_scaled(&top_matrix.matrix, &sub_matrix.matrix, scaling);
292 let scaled_upem = Fixed::from_bits(sub_matrix.scale).mul_div(
293 Fixed::from_bits(top_matrix.scale),
294 Fixed::from_bits(scaling),
295 );
296 Some(
298 ScaledFontMatrix {
299 matrix,
300 scale: scaled_upem.to_bits(),
301 }
302 .normalize(),
303 )
304 } else {
305 Some(top_matrix)
307 }
308 } else {
309 subfont.matrix.map(|matrix| matrix.normalize())
311 };
312 if let Some(matrix) = scaled_matrix.as_ref() {
315 if matrix.scale != self.upem {
318 let original_scale = scale.unwrap_or(Fixed::from_i32(64));
322 scale = Some(
323 original_scale
324 .mul_div(Fixed::from_bits(self.upem), Fixed::from_bits(matrix.scale)),
325 );
326 }
327 }
328 Transform {
329 matrix: scaled_matrix
330 .map(|scaled_mat| scaled_mat.matrix)
331 .unwrap_or_default(),
332 scale,
333 }
334 }
335
336 pub fn evaluate_charstring(
342 &self,
343 subfont: &Subfont,
344 gid: GlyphId,
345 coords: &[F2Dot14],
346 sink: &mut impl CommandSink,
347 ) -> Result<Option<Fixed>, Error> {
348 let charstrings = self.top_dict.charstrings.clone();
349 let blend = self.blend_state(subfont.vs_index, coords);
350 let subrs = if subfont.subrs_offset != 0 {
351 let data = self
352 .data
353 .get(subfont.subrs_offset as usize..)
354 .ok_or(ReadError::OutOfBounds)?;
355 Index::new(data, self.is_cff2)?
356 } else {
357 Index::Empty
358 };
359 let charstring_data = charstrings.get(gid.to_u32() as usize)?;
360 let ctx = (self.data, &charstrings, &self.global_subrs, &subrs);
361 if let Some(width) = cs::evaluate(&ctx, blend, charstring_data, sink)? {
362 Ok(Some(width + subfont.nominal_width))
363 } else {
364 Ok(subfont.default_width)
365 }
366 }
367
368 pub fn draw(
373 &self,
374 subfont: &Subfont,
375 gid: GlyphId,
376 coords: &[F2Dot14],
377 ppem: Option<f32>,
378 pen: &mut impl OutlinePen,
379 ) -> Result<Option<f32>, Error> {
380 let mut nop_filter = NopFilterSink::new(pen);
381 let transform = self.transform(subfont, ppem);
382 let mut transformer = TransformSink::new(&mut nop_filter, transform);
383 let width = self.evaluate_charstring(subfont, gid, coords, &mut transformer)?;
384 Ok(width.map(|w| transform.transform_h_metric(w).to_f32().max(0.0)))
385 }
386
387 fn blend_state(&self, vs_index: u16, coords: &'a [F2Dot14]) -> Option<BlendState<'a>> {
390 self.top_dict
391 .var_store
392 .as_ref()
393 .and_then(|store| BlendState::new(store.clone(), coords, vs_index).ok())
394 }
395}
396
397#[derive(Clone)]
399pub struct Encoding<'a> {
400 encoding: RawEncoding<'a>,
401 charset: Charset<'a>,
402}
403
404impl<'a> Encoding<'a> {
405 pub fn predefined(&self) -> Option<PredefinedEncoding> {
408 if let RawEncoding::Predefined(pre) = &self.encoding {
409 Some(*pre)
410 } else {
411 None
412 }
413 }
414
415 pub fn charset(&self) -> &Charset<'a> {
417 &self.charset
418 }
419
420 pub fn map(&self, code: u8) -> Option<GlyphId> {
422 self.encoding.map(&self.charset, code)
423 }
424}
425
426#[derive(Clone)]
428enum CffFontKind<'a> {
429 Sid {
431 strings: Index<'a>,
433 private_dict: Range<u32>,
435 },
436 Cid {
438 fd_select: Option<FdSelect<'a>>,
440 fd_array: Index<'a>,
442 },
443 Cff2 {
445 fd_select: Option<FdSelect<'a>>,
447 fd_array: Index<'a>,
449 },
450}
451
452#[derive(Copy, Clone, Default, Debug)]
464pub struct Subfont {
465 subrs_offset: u32,
466 default_width: Option<Fixed>,
467 nominal_width: Fixed,
468 matrix: Option<ScaledFontMatrix>,
469 vs_index: u16,
470}
471
472impl Subfont {
473 fn new(
474 data: &[u8],
475 range: Range<usize>,
476 blend: Option<BlendState>,
477 matrix: Option<ScaledFontMatrix>,
478 ) -> Result<Self, Error> {
479 let mut subfont = Self {
480 matrix,
481 ..Default::default()
482 };
483 let data = data.get(range.clone()).ok_or(ReadError::OutOfBounds)?;
484 for entry in dict::entries(data, blend).filter_map(|e| e.ok()) {
485 match entry {
486 dict::Entry::SubrsOffset(offset) => {
487 subfont.subrs_offset = range
488 .start
489 .checked_add(offset)
490 .ok_or(ReadError::OutOfBounds)?
491 as u32;
492 }
493 dict::Entry::VariationStoreIndex(index) => subfont.vs_index = index,
494 dict::Entry::DefaultWidthX(width) => subfont.default_width = Some(width.floor()),
496 dict::Entry::NominalWidthX(width) => subfont.nominal_width = width.floor(),
497 _ => {}
498 }
499 }
500 Ok(subfont)
501 }
502
503 fn new_hinted(
504 data: &[u8],
505 range: Range<usize>,
506 blend: Option<BlendState>,
507 matrix: Option<ScaledFontMatrix>,
508 ) -> Result<(Self, HintingParams), Error> {
509 let mut subfont = Self {
510 matrix,
511 ..Default::default()
512 };
513 let mut params = HintingParams::default();
514 let data = data.get(range.clone()).ok_or(ReadError::OutOfBounds)?;
515 for entry in dict::entries(data, blend).filter_map(|e| e.ok()) {
516 match entry {
517 dict::Entry::SubrsOffset(offset) => {
518 subfont.subrs_offset = range
519 .start
520 .checked_add(offset)
521 .ok_or(ReadError::OutOfBounds)?
522 as u32;
523 }
524 dict::Entry::VariationStoreIndex(index) => subfont.vs_index = index,
525 dict::Entry::DefaultWidthX(width) => subfont.default_width = Some(width.floor()),
527 dict::Entry::NominalWidthX(width) => subfont.nominal_width = width.floor(),
528 dict::Entry::BlueValues(values) => params.blues = values,
529 dict::Entry::FamilyBlues(values) => params.family_blues = values,
530 dict::Entry::OtherBlues(values) => params.other_blues = values,
531 dict::Entry::FamilyOtherBlues(values) => params.family_other_blues = values,
532 dict::Entry::BlueScale(value) => params.blue_scale = value,
533 dict::Entry::BlueShift(value) => params.blue_shift = value,
534 dict::Entry::BlueFuzz(value) => params.blue_fuzz = value,
535 dict::Entry::LanguageGroup(group) => params.language_group = group,
536 _ => {}
537 }
538 }
539 Ok((subfont, params))
540 }
541
542 pub fn subrs_offset(&self) -> u32 {
545 self.subrs_offset
546 }
547
548 pub fn default_width(&self) -> Option<Fixed> {
552 self.default_width
553 }
554
555 pub fn nominal_width(&self) -> Fixed {
560 self.nominal_width
561 }
562
563 pub fn matrix(&self) -> Option<&ScaledFontMatrix> {
565 self.matrix.as_ref()
566 }
567
568 pub fn vs_index(&self) -> u16 {
570 self.vs_index
571 }
572}
573
574#[derive(Copy, Clone)]
576struct MaybeOffset(u32);
577
578impl MaybeOffset {
579 fn get(self) -> Option<usize> {
580 (self.0 != u32::MAX).then_some(self.0 as usize)
581 }
582}
583
584impl Default for MaybeOffset {
585 fn default() -> Self {
586 Self(u32::MAX)
587 }
588}
589
590#[derive(Clone)]
591struct TopDict<'a> {
592 charstrings: Index<'a>,
593 charset_offset: MaybeOffset,
594 encoding_offset: MaybeOffset,
595 matrix: Option<ScaledFontMatrix>,
596 var_store: Option<ItemVariationStore<'a>>,
597 kind: CffFontKind<'a>,
598}
599
600impl<'a> TopDict<'a> {
601 fn new(
602 cff_data: &'a [u8],
603 top_dict_data: &[u8],
604 strings: Index<'a>,
605 is_cff2: bool,
606 ) -> Result<Self, Error> {
607 let mut has_ros = false;
608 let mut charstrings = None;
609 let [mut charset_offset, mut encoding_offset] = if is_cff2 {
610 [MaybeOffset::default(); 2]
613 } else {
614 [MaybeOffset(0); 2]
617 };
618 let mut fd_array = None;
619 let mut fd_select = None;
620 let mut private_dict_range = 0..0;
621 let mut matrix = None;
622 let mut var_store = None;
623 for entry in dict::entries(top_dict_data, None).filter_map(|e| e.ok()) {
624 match entry {
625 dict::Entry::Ros { .. } => has_ros = true,
626 dict::Entry::CharstringsOffset(offset) => {
627 charstrings = cff_data
628 .get(offset..)
629 .and_then(|data| Index::new(data, is_cff2).ok());
630 }
631 dict::Entry::Charset(offset) => charset_offset = MaybeOffset(offset as u32),
632 dict::Entry::Encoding(offset) => encoding_offset = MaybeOffset(offset as u32),
633 dict::Entry::FdArrayOffset(offset) => {
634 fd_array = cff_data
635 .get(offset..)
636 .and_then(|data| Index::new(data, is_cff2).ok());
637 }
638 dict::Entry::FdSelectOffset(offset) => {
639 fd_select = cff_data
640 .get(offset..)
641 .and_then(|data| FdSelect::read(data.into()).ok());
642 }
643 dict::Entry::PrivateDictRange(range) => {
644 let _ = cff_data.get(range.clone()).ok_or(ReadError::OutOfBounds)?;
646 private_dict_range = range;
647 }
648 dict::Entry::FontMatrix(font_matrix) => {
649 matrix = Some(font_matrix.normalize());
652 }
653 dict::Entry::VariationStoreOffset(offset) if is_cff2 => {
654 let offset = offset.checked_add(2).ok_or(ReadError::OutOfBounds)?;
658 var_store = Some(ItemVariationStore::read(
659 cff_data.get(offset..).unwrap_or_default().into(),
660 )?);
661 }
662 _ => {}
663 }
664 }
665 let charstrings = charstrings.ok_or(Error::MissingCharstrings)?;
666 let kind = if let Some(fd_array) = fd_array {
667 if is_cff2 {
668 CffFontKind::Cff2 {
669 fd_array,
670 fd_select,
671 }
672 } else {
673 CffFontKind::Cid {
674 fd_array,
675 fd_select,
676 }
677 }
678 } else {
679 if has_ros || is_cff2 {
680 return Err(Error::MissingFdArray);
682 }
683 CffFontKind::Sid {
684 strings,
685 private_dict: private_dict_range.start as u32..private_dict_range.end as u32,
686 }
687 };
688 Ok(Self {
689 charset_offset,
690 encoding_offset,
691 charstrings,
692 matrix,
693 kind,
694 var_store,
695 })
696 }
697}
698
699#[derive(Default)]
700struct FontDict {
701 private_dict_range: Range<usize>,
702 matrix: Option<ScaledFontMatrix>,
703}
704
705impl FontDict {
706 fn new(dict_data: &[u8]) -> Result<Self, Error> {
707 let mut range = None;
708 let mut matrix = None;
709 for entry in dict::entries(dict_data, None) {
710 match entry? {
711 dict::Entry::PrivateDictRange(dict_range) => {
712 range = Some(dict_range);
713 }
714 dict::Entry::FontMatrix(font_matrix) => {
715 matrix = Some(font_matrix);
716 }
717 _ => {}
718 }
719 }
720 Ok(Self {
721 private_dict_range: range.ok_or(Error::MissingPrivateDict)?,
722 matrix,
723 })
724 }
725}
726
727#[derive(Clone, Debug)]
732pub struct Metadata<'a> {
733 name: Option<&'a str>,
734 full_name: Option<&'a str>,
735 family_name: Option<&'a str>,
736 weight: Option<&'a str>,
737 bbox: BoundingBox<Fixed>,
738 italic_angle: Fixed,
739 is_fixed_pitch: bool,
740 underline_position: Fixed,
741 underline_thickness: Fixed,
742}
743
744impl<'a> Metadata<'a> {
745 fn new(data: &'a [u8], top_dict_index: u16) -> Option<Self> {
746 let cff = cff::Cff::read(FontData::new(data)).ok()?;
747 let strings = cff.strings();
748 let get_str = |sid: Sid| {
749 let bytes = match sid.resolve_standard() {
750 Ok(bytes) => bytes,
751 Err(idx) => strings.get(idx).ok()?,
752 };
753 core::str::from_utf8(bytes).ok()
754 };
755 let top_dict_data = cff.top_dicts().get(top_dict_index as usize).ok()?;
756 let name = cff
757 .name(top_dict_index as usize)
758 .and_then(|bytes| core::str::from_utf8(bytes).ok());
759 let mut meta = Metadata {
760 name,
761 ..Default::default()
762 };
763 for entry in dict::entries(top_dict_data, None).filter_map(|e| e.ok()) {
764 match entry {
765 dict::Entry::FullName(sid) => meta.full_name = get_str(sid),
766 dict::Entry::FamilyName(sid) => meta.family_name = get_str(sid),
767 dict::Entry::Weight(sid) => meta.weight = get_str(sid),
768 dict::Entry::FontBbox([x_min, y_min, x_max, y_max]) => {
769 meta.bbox = BoundingBox {
770 x_min,
771 x_max,
772 y_min,
773 y_max,
774 }
775 }
776 dict::Entry::ItalicAngle(angle) => meta.italic_angle = angle,
777 dict::Entry::IsFixedPitch(fixed_pitch) => meta.is_fixed_pitch = fixed_pitch,
778 dict::Entry::UnderlinePosition(pos) => meta.underline_position = pos,
779 dict::Entry::UnderlineThickness(size) => meta.underline_thickness = size,
780 _ => {}
781 }
782 }
783 Some(meta)
784 }
785
786 pub fn name(&self) -> Option<&'a str> {
788 self.name
789 }
790
791 pub fn full_name(&self) -> Option<&'a str> {
793 self.full_name
794 }
795
796 pub fn family_name(&self) -> Option<&'a str> {
798 self.family_name
799 }
800
801 pub fn weight(&self) -> Option<&'a str> {
803 self.weight
804 }
805
806 pub fn italic_angle(&self) -> Fixed {
808 self.italic_angle
809 }
810
811 pub fn is_fixed_pitch(&self) -> bool {
813 self.is_fixed_pitch
814 }
815
816 pub fn underline_position(&self) -> Fixed {
818 self.underline_position
819 }
820
821 pub fn underline_thickness(&self) -> Fixed {
823 self.underline_thickness
824 }
825
826 pub fn bbox(&self) -> BoundingBox<Fixed> {
828 self.bbox
829 }
830}
831
832impl Default for Metadata<'_> {
833 fn default() -> Self {
834 Self {
835 name: None,
836 full_name: None,
837 family_name: None,
838 weight: None,
839 bbox: BoundingBox::default(),
840 italic_angle: Fixed::ZERO,
841 is_fixed_pitch: false,
842 underline_position: Fixed::from_i32(-100),
843 underline_thickness: Fixed::from_i32(50),
844 }
845 }
846}
847
848#[cfg(test)]
849mod tests {
850 use super::*;
851 use crate::{
852 ps::{hinting::Blues, transform::FontMatrix},
853 FontData, FontRef, TableProvider,
854 };
855 use cs::test_helpers::*;
856 use font_test_data::bebuffer::BeBuffer;
857
858 #[test]
859 fn read_cff_static() {
860 let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
861 let cff =
862 CffFontRef::new_cff(font.cff().unwrap().offset_data().as_bytes(), 0, None).unwrap();
863 assert_eq!(cff.version(), 1);
864 assert!(cff.var_store().is_none());
865 let CffFontKind::Sid { private_dict, .. } = &cff.top_dict.kind else {
866 panic!("this is an SID font");
867 };
868 assert!(!private_dict.is_empty());
869 assert_eq!(cff.num_glyphs(), 5);
870 assert_eq!(cff.num_subfonts(), 1);
871 assert_eq!(cff.subfont_index(GlyphId::new(1)), Some(0));
872 assert_eq!(cff.global_subrs.count(), 17);
873 }
874
875 #[test]
876 fn read_cff_metadata() {
877 let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
878 let cff = CffFontRef::new(font.cff().unwrap().offset_data().as_bytes(), 0, None).unwrap();
879 let meta = cff.metadata().unwrap();
880 assert_eq!(meta.name(), Some("NotoSerifDisplay-Regular"));
881 assert_eq!(meta.full_name(), Some("Noto Serif Display Regular"));
882 assert_eq!(meta.family_name(), Some("Noto Serif Display"));
883 assert_eq!(meta.weight(), None);
884 assert_eq!(
885 meta.bbox(),
886 BoundingBox {
887 x_min: Fixed::from_i32(-693),
888 y_min: Fixed::from_i32(-470),
889 x_max: Fixed::from_i32(2797),
890 y_max: Fixed::from_i32(1048)
891 }
892 );
893 assert_eq!(meta.italic_angle(), Fixed::ZERO);
894 assert!(!meta.is_fixed_pitch());
895 assert_eq!(meta.underline_position(), Fixed::from_i32(-100));
896 assert_eq!(meta.underline_thickness(), Fixed::from_i32(50));
897 let font = FontRef::new(font_test_data::MATERIAL_ICONS_SUBSET).unwrap();
898 let cff = CffFontRef::new(font.cff().unwrap().offset_data().as_bytes(), 0, None).unwrap();
899 let meta = cff.metadata().unwrap();
900 assert_eq!(meta.name(), Some("GoogleMaterialIcons-Regular"));
901 assert_eq!(meta.full_name(), Some("GoogleMaterialIcons-Regular"));
902 assert_eq!(meta.family_name(), None);
903 assert_eq!(meta.weight(), None);
904 assert_eq!(
905 meta.bbox(),
906 BoundingBox {
907 x_min: Fixed::from_i32(-1),
908 y_min: Fixed::ZERO,
909 x_max: Fixed::from_i32(513),
910 y_max: Fixed::from_i32(512)
911 }
912 );
913 assert_eq!(meta.italic_angle(), Fixed::ZERO);
914 assert!(!meta.is_fixed_pitch());
915 assert_eq!(meta.underline_position(), Fixed::from_i32(-100));
916 assert_eq!(meta.underline_thickness(), Fixed::from_i32(50));
917 }
918
919 #[test]
920 fn read_cff2_static() {
921 let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
922 let cff =
923 CffFontRef::new_cff2(font.cff2().unwrap().offset_data().as_bytes(), None).unwrap();
924 assert_eq!(cff.version(), 2);
925 assert!(cff.var_store().is_some());
926 let CffFontKind::Cff2 { fd_array, .. } = &cff.top_dict.kind else {
927 panic!("this is a CFF2 font");
928 };
929 assert_eq!(fd_array.count(), 1);
930 assert_eq!(cff.num_glyphs(), 6);
931 assert_eq!(cff.num_subfonts(), 1);
932 assert_eq!(cff.subfont_index(GlyphId::new(1)), Some(0));
933 assert_eq!(cff.global_subrs.count(), 0);
934 }
935
936 #[test]
937 fn read_example_cff2_table() {
938 let cff = CffFontRef::new_cff2(font_test_data::cff2::EXAMPLE, None).unwrap();
939 assert_eq!(cff.version(), 2);
940 assert!(cff.var_store().is_some());
941 let CffFontKind::Cff2 { fd_array, .. } = &cff.top_dict.kind else {
942 panic!("this is a CFF2 font");
943 };
944 assert_eq!(fd_array.count(), 1);
945 assert_eq!(cff.num_glyphs(), 2);
946 assert_eq!(cff.num_subfonts(), 1);
947 assert_eq!(cff.subfont_index(GlyphId::new(1)), Some(0));
948 assert_eq!(cff.global_subrs.count(), 0);
949 }
950
951 #[test]
952 fn charset() {
953 let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
954 let cff =
955 CffFontRef::new_cff(font.cff().unwrap().offset_data().as_bytes(), 0, None).unwrap();
956 let charset = cff.charset().unwrap();
957 let glyph_names = charset
958 .iter()
959 .map(|(gid, sid)| {
960 (
961 gid.to_u32(),
962 std::str::from_utf8(sid.resolve_standard().unwrap()).unwrap(),
963 )
964 })
965 .collect::<Vec<_>>();
966 let expected = [(0, ".notdef"), (1, "i"), (2, "j"), (3, "k"), (4, "l")];
967 assert_eq!(glyph_names, expected)
968 }
969
970 #[test]
974 fn top_dict_ivs_offset_overflow() {
975 let top_dict = BeBuffer::new()
978 .push(29u8) .push(-1i32) .push(24u8) .to_vec();
982 assert!(TopDict::new(&[], &top_dict, Index::Empty, true).is_err());
984 }
985
986 #[test]
989 fn subrs_offset_overflow() {
990 let private_dict = BeBuffer::new()
992 .push(0u32) .push(29u8) .push(-1i32) .push(19u8) .to_vec();
997 assert!(Subfont::new(&private_dict, 4..private_dict.len(), None, None).is_err());
999 }
1000
1001 #[test]
1003 fn empty_private_dict() {
1004 let font = FontRef::new(font_test_data::MATERIAL_ICONS_SUBSET).unwrap();
1005 let cff =
1006 CffFontRef::new_cff(font.cff().unwrap().offset_data().as_bytes(), 0, None).unwrap();
1007 let CffFontKind::Sid { private_dict, .. } = &cff.top_dict.kind else {
1008 panic!("this is an SID font");
1009 };
1010 assert!(private_dict.is_empty());
1011 }
1012
1013 #[test]
1015 fn capture_family_other_blues() {
1016 let private_dict_data = &font_test_data::cff2::EXAMPLE[0x4f..=0xc0];
1017 let store =
1018 ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
1019 let coords = &[F2Dot14::from_f32(0.0)];
1020 let blend_state = BlendState::new(store, coords, 0).unwrap();
1021 let (_subfont, hint_params) = Subfont::new_hinted(
1022 private_dict_data,
1023 0..private_dict_data.len(),
1024 Some(blend_state),
1025 None,
1026 )
1027 .unwrap();
1028 assert_eq!(
1029 hint_params.family_other_blues,
1030 Blues::new([-249.0, -239.0].map(Fixed::from_f64).into_iter())
1031 )
1032 }
1033
1034 #[test]
1035 fn subfont_cff() {
1036 let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
1037 let cff =
1038 CffFontRef::new_cff(font.cff().unwrap().offset_data().as_bytes(), 0, None).unwrap();
1039 let subfont = cff.subfont(0, &[]).unwrap();
1040 assert_eq!(subfont.default_width, None);
1041 assert_eq!(subfont.nominal_width, Fixed::from_i32(598));
1042 assert_eq!(subfont.vs_index, 0);
1043 assert_eq!(subfont.matrix, None);
1044 }
1045
1046 fn make_blues<const N: usize>(values: [i32; N]) -> Blues {
1047 Blues::new(values.map(Fixed::from_i32).into_iter())
1048 }
1049
1050 #[test]
1051 fn hinted_subfont_cff() {
1052 let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
1053 let cff =
1054 CffFontRef::new_cff(font.cff().unwrap().offset_data().as_bytes(), 0, None).unwrap();
1055 let (subfont, hinting) = cff.subfont_hinted(0, &[]).unwrap();
1056 assert_eq!(subfont.default_width, None);
1057 assert_eq!(subfont.nominal_width, Fixed::from_i32(598));
1058 assert_eq!(subfont.vs_index, 0);
1059 assert_eq!(subfont.matrix, None);
1060 let expected_hinting = HintingParams {
1061 blues: make_blues([-15, 0, 536, 547, 571, 582, 714, 726, 760, 772]),
1062 family_blues: Blues::default(),
1063 other_blues: make_blues([-255, -240]),
1064 family_other_blues: Blues::default(),
1065 blue_scale: Fixed::from_f64(0.0500030517578125),
1066 blue_shift: Fixed::from_f64(7.0),
1067 blue_fuzz: Fixed::ZERO,
1068 language_group: 0,
1069 };
1070 assert_eq!(hinting, expected_hinting);
1071 }
1072
1073 #[test]
1074 fn subfont_cff2() {
1075 let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
1076 let cff =
1077 CffFontRef::new_cff2(font.cff2().unwrap().offset_data().as_bytes(), None).unwrap();
1078 let subfont = cff.subfont(0, &[]).unwrap();
1079 assert_eq!(subfont.default_width, None);
1080 assert_eq!(subfont.nominal_width, Fixed::ZERO);
1081 assert_eq!(subfont.vs_index, 0);
1082 assert_eq!(subfont.matrix, None);
1083 }
1084
1085 #[test]
1086 fn hinted_subfont_cff2() {
1087 let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
1088 let cff =
1089 CffFontRef::new_cff2(font.cff2().unwrap().offset_data().as_bytes(), None).unwrap();
1090 let (subfont, hinting) = cff.subfont_hinted(0, &[]).unwrap();
1091 assert_eq!(subfont.default_width, None);
1092 assert_eq!(subfont.nominal_width, Fixed::ZERO);
1093 assert_eq!(subfont.vs_index, 0);
1094 assert_eq!(subfont.matrix, None);
1095 let expected_hinting = HintingParams {
1096 blues: make_blues([-10, 0, 482, 492, 694, 704, 739, 749]),
1097 family_blues: Blues::default(),
1098 other_blues: make_blues([-227, -217]),
1099 family_other_blues: Blues::default(),
1100 blue_scale: Fixed::from_f64(0.0625),
1101 blue_shift: Fixed::from_f64(7.0),
1102 blue_fuzz: Fixed::ONE,
1103 language_group: 0,
1104 };
1105 assert_eq!(hinting, expected_hinting);
1106 }
1107
1108 #[test]
1109 fn subfont_matrix() {
1110 let font = FontRef::new(font_test_data::MATERIAL_ICONS_SUBSET_MATRIX).unwrap();
1111 let cff =
1112 CffFontRef::new_cff(font.cff().unwrap().offset_data().as_bytes(), 0, None).unwrap();
1113 let subfont = cff.subfont(0, &[]).unwrap();
1114 assert_eq!(subfont.default_width, None);
1115 assert_eq!(subfont.nominal_width, Fixed::ZERO);
1116 assert_eq!(subfont.vs_index, 0);
1117 let expected_matrix = FontMatrix::from_elements([
1118 Fixed::from_i32(5),
1119 Fixed::ZERO,
1120 Fixed::ZERO,
1121 Fixed::from_i32(5),
1122 Fixed::ZERO,
1123 Fixed::ZERO,
1124 ]);
1125 let expected_scale = 10;
1126 assert_eq!(
1127 subfont.matrix,
1128 Some(ScaledFontMatrix {
1129 matrix: expected_matrix,
1130 scale: expected_scale
1131 })
1132 );
1133 }
1134
1135 #[test]
1136 fn eval_charstring_cff() {
1137 let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
1138 let cff =
1139 CffFontRef::new_cff(font.cff().unwrap().offset_data().as_bytes(), 0, None).unwrap();
1140 let mut sink = CharstringCommandCounter::default();
1141 let subfont = cff.subfont(0, &[]).unwrap();
1142 cff.evaluate_charstring(&subfont, GlyphId::new(2), &[], &mut sink)
1143 .unwrap();
1144 assert_eq!(sink.0, 18);
1147 }
1148
1149 #[test]
1150 fn eval_charstring_cff2() {
1151 let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
1152 let cff =
1153 CffFontRef::new_cff2(font.cff2().unwrap().offset_data().as_bytes(), None).unwrap();
1154 let mut sink = CharstringCommandCounter::default();
1155 let subfont = cff.subfont(0, &[]).unwrap();
1156 cff.evaluate_charstring(&subfont, GlyphId::new(2), &[], &mut sink)
1157 .unwrap();
1158 assert_eq!(sink.0, 11);
1161 }
1162
1163 #[test]
1164 fn select_version() {
1165 assert_eq!(
1167 CffFontRef::new(
1168 FontRef::new(font_test_data::CANTARELL_VF_TRIMMED)
1169 .unwrap()
1170 .cff2()
1171 .unwrap()
1172 .offset_data()
1173 .as_bytes(),
1174 0,
1175 None
1176 )
1177 .unwrap()
1178 .version(),
1179 2
1180 );
1181 assert_eq!(
1183 CffFontRef::new(
1184 FontRef::new(font_test_data::MATERIAL_ICONS_SUBSET_MATRIX)
1185 .unwrap()
1186 .cff()
1187 .unwrap()
1188 .offset_data()
1189 .as_bytes(),
1190 0,
1191 None
1192 )
1193 .unwrap()
1194 .version(),
1195 1
1196 );
1197 assert!(CffFontRef::new(&[0, 1, 4, 5], 0, None).is_err());
1199 assert!(CffFontRef::new(&[1, 1, 4, 5], 0, None).is_err());
1201 assert!(CffFontRef::new(&[2, 1, 4, 5], 0, None).is_err());
1203 }
1204
1205 #[test]
1206 fn transform() {
1207 let font = FontRef::new(font_test_data::MATERIAL_ICONS_SUBSET_MATRIX).unwrap();
1209 let cff =
1211 CffFontRef::new(font.cff().unwrap().offset_data().as_bytes(), 0, Some(512)).unwrap();
1212 let subfont = cff.subfont(0, &[]).unwrap();
1213 let expected_matrix = [65536, 0, 5604, 65536, 0, 0].map(Fixed::from_bits);
1215 let transform = cff.transform(&subfont, None);
1217 assert_eq!(transform.matrix.elements(), expected_matrix);
1218 assert_eq!(transform.scale, Some(Fixed::from_bits(32 << 16)));
1219 let transform = cff.transform(&subfont, Some(16.0));
1221 assert_eq!(transform.matrix.elements(), expected_matrix);
1222 assert_eq!(transform.scale, Some(Fixed::from_bits(1 << 16)));
1223 }
1224
1225 #[test]
1226 fn cff_encoding() {
1227 let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
1228 let cff = CffFontRef::new(font.cff().unwrap().offset_data().as_bytes(), 0, None).unwrap();
1229 let encoding = cff.encoding().unwrap();
1230 assert_eq!(encoding.predefined(), Some(PredefinedEncoding::Standard));
1231 }
1232
1233 #[test]
1234 fn cff2_lacks_encoding() {
1235 let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
1236 let cff = CffFontRef::new(font.cff2().unwrap().offset_data().as_bytes(), 0, None).unwrap();
1237 assert!(cff.encoding().is_none());
1238 }
1239}