1mod hint;
4
5use super::{GlyphHMetrics, OutlinePen};
6use hint::{HintParams, HintState, HintingSink};
7use read_fonts::{
8 ps::{
9 cff::{blend::BlendState, dict, fd_select::FdSelect, index::Index},
10 cs::{self, CommandSink, NopFilterSink, TransformSink},
11 error::Error,
12 transform::{self, FontMatrix, ScaledFontMatrix, Transform},
13 },
14 tables::variations::ItemVariationStore,
15 types::{F2Dot14, Fixed, GlyphId},
16 FontData, FontRead, FontRef, ReadError, TableProvider,
17};
18use std::ops::Range;
19
20#[derive(Clone)]
38pub(crate) struct Outlines<'a> {
39 pub(crate) font: FontRef<'a>,
40 pub(crate) glyph_metrics: GlyphHMetrics<'a>,
41 offset_data: FontData<'a>,
42 global_subrs: Index<'a>,
43 top_dict: TopDict<'a>,
44 version: u16,
45 units_per_em: u16,
46}
47
48impl<'a> Outlines<'a> {
49 pub fn new(font: &FontRef<'a>) -> Option<Self> {
54 let units_per_em = font.head().ok()?.units_per_em();
55 Self::from_cff2(font, units_per_em).or_else(|| Self::from_cff(font, units_per_em))
56 }
57
58 pub fn from_cff(font: &FontRef<'a>, units_per_em: u16) -> Option<Self> {
59 let cff1 = font.cff().ok()?;
60 let glyph_metrics = GlyphHMetrics::new(font)?;
61 let top_dict_data = cff1.top_dicts().get(0).ok()?;
67 let top_dict = TopDict::new(cff1.offset_data().as_bytes(), top_dict_data, false).ok()?;
68 Some(Self {
69 font: font.clone(),
70 glyph_metrics,
71 offset_data: cff1.offset_data(),
72 global_subrs: cff1.global_subrs().into(),
73 top_dict,
74 version: 1,
75 units_per_em,
76 })
77 }
78
79 pub fn from_cff2(font: &FontRef<'a>, units_per_em: u16) -> Option<Self> {
80 let cff2 = font.cff2().ok()?;
81 let glyph_metrics = GlyphHMetrics::new(font)?;
82 let table_data = cff2.offset_data().as_bytes();
83 let top_dict = TopDict::new(table_data, cff2.top_dict_data(), true).ok()?;
84 Some(Self {
85 font: font.clone(),
86 glyph_metrics,
87 offset_data: cff2.offset_data(),
88 global_subrs: cff2.global_subrs().into(),
89 top_dict,
90 version: 2,
91 units_per_em,
92 })
93 }
94
95 pub fn is_cff2(&self) -> bool {
96 self.version == 2
97 }
98
99 pub fn units_per_em(&self) -> u16 {
100 self.units_per_em
101 }
102
103 pub fn glyph_count(&self) -> usize {
105 self.top_dict.charstrings.count() as usize
106 }
107
108 pub fn subfont_count(&self) -> u32 {
110 self.top_dict.font_dicts.count().max(1)
112 }
113
114 pub fn subfont_index(&self, glyph_id: GlyphId) -> u32 {
117 self.top_dict
128 .fd_select
129 .as_ref()
130 .and_then(|select| select.font_index(glyph_id))
131 .unwrap_or(0) as u32
132 }
133
134 pub fn subfont(
140 &self,
141 index: u32,
142 size: Option<f32>,
143 coords: &[F2Dot14],
144 ) -> Result<Subfont, Error> {
145 let font_dict = self.parse_font_dict(index)?;
146 let blend_state = self
147 .top_dict
148 .var_store
149 .clone()
150 .map(|store| BlendState::new(store, coords, 0))
151 .transpose()?;
152 let private_dict =
153 PrivateDict::new(self.offset_data, font_dict.private_dict_range, blend_state)?;
154 let upem = self.units_per_em as i32;
155 let mut scale = match size {
156 Some(ppem) if upem > 0 => {
157 Some(Fixed::from_bits((ppem * 64.) as i32) / Fixed::from_bits(upem))
160 }
161 _ => None,
162 };
163 let scale_requested = size.is_some();
164 let font_matrix = if let Some(top_matrix) = self.top_dict.font_matrix {
167 if let Some(sub_matrix) = font_dict.font_matrix {
169 let scaling = if top_matrix.scale > 1 && sub_matrix.scale > 1 {
170 top_matrix.scale.min(sub_matrix.scale)
171 } else {
172 1
173 };
174 let matrix =
176 transform::combine_scaled(&top_matrix.matrix, &sub_matrix.matrix, scaling);
177 let upem = Fixed::from_bits(sub_matrix.scale).mul_div(
178 Fixed::from_bits(top_matrix.scale),
179 Fixed::from_bits(scaling),
180 );
181 Some(
183 ScaledFontMatrix {
184 matrix,
185 scale: upem.to_bits(),
186 }
187 .normalize(),
188 )
189 } else {
190 Some(top_matrix)
192 }
193 } else {
194 font_dict.font_matrix.map(|matrix| matrix.normalize())
196 };
197 let mut font_matrix = if let Some(matrix) = font_matrix {
200 if matrix.scale != upem {
203 let original_scale = scale.unwrap_or(Fixed::from_i32(64));
207 scale = Some(
208 original_scale.mul_div(Fixed::from_bits(upem), Fixed::from_bits(matrix.scale)),
209 );
210 }
211 Some(matrix.matrix)
212 } else {
213 None
214 };
215 if font_matrix == Some(FontMatrix::IDENTITY) {
216 font_matrix = None;
219 }
220 let hint_scale = scale_for_hinting(scale);
221 let hint_state = HintState::new(&private_dict.hint_params, hint_scale);
222 Ok(Subfont {
223 is_cff2: self.is_cff2(),
224 scale,
225 scale_requested,
226 subrs_offset: private_dict.subrs_offset,
227 hint_state,
228 store_index: private_dict.store_index,
229 font_matrix,
230 default_width: private_dict.default_width,
231 nominal_width: private_dict.nominal_width,
232 })
233 }
234
235 pub fn draw(
247 &self,
248 subfont: &Subfont,
249 glyph_id: GlyphId,
250 coords: &[F2Dot14],
251 hint: bool,
252 pen: &mut impl OutlinePen,
253 ) -> Result<Option<f32>, Error> {
254 let cff_data = self.offset_data.as_bytes();
255 let charstrings = self.top_dict.charstrings.clone();
256 let charstring_data = charstrings.get(glyph_id.to_u32() as usize)?;
257 let subrs = subfont.subrs(self)?;
258 let blend_state = subfont.blend_state(self, coords)?;
259 let cs_eval = CharstringEvaluator {
260 cff_data,
261 charstrings,
262 global_subrs: self.global_subrs.clone(),
263 subrs,
264 blend_state,
265 charstring_data,
266 };
267 let apply_hinting = hint && subfont.scale_requested;
269 let mut pen_sink = PenSink::new(pen);
270 let mut simplifying_adapter = NopFilterSink::new(&mut pen_sink);
271 let mut transform = Transform {
272 matrix: FontMatrix::IDENTITY,
273 scale: subfont.scale,
274 };
275 let maybe_width = if let Some(matrix) = subfont.font_matrix {
276 transform.matrix = matrix;
277 if apply_hinting {
278 let mut transform_sink =
279 HintedTransformingSink::new(&mut simplifying_adapter, matrix);
280 let mut hinting_adapter =
281 HintingSink::new(&subfont.hint_state, &mut transform_sink);
282 cs_eval.evaluate(&mut hinting_adapter)
283 } else {
284 let mut transform_sink = TransformSink::from_matrix_scale(
285 &mut simplifying_adapter,
286 matrix,
287 subfont.scale,
288 );
289 cs_eval.evaluate(&mut transform_sink)
290 }
291 } else if apply_hinting {
292 let mut hinting_adapter =
293 HintingSink::new(&subfont.hint_state, &mut simplifying_adapter);
294 cs_eval.evaluate(&mut hinting_adapter)
295 } else {
296 let mut scaling_adapter = TransformSink::from_matrix_scale(
297 &mut simplifying_adapter,
298 FontMatrix::IDENTITY,
299 subfont.scale,
300 );
301 cs_eval.evaluate(&mut scaling_adapter)
302 }?;
303 Ok(maybe_width
304 .map(|w| w + subfont.nominal_width)
307 .or(subfont.default_width)
309 .or_else(|| {
311 Some(Fixed::from_i32(
312 self.glyph_metrics.advance_width(glyph_id, coords),
313 ))
314 })
315 .map(|w| {
316 let w = transform.transform_h_metric(w);
317 if hint {
318 w.round().to_f32()
319 } else {
320 w.to_f32()
321 }
322 })
323 .filter(|w| *w >= 0.0))
329 }
330
331 fn parse_font_dict(&self, subfont_index: u32) -> Result<FontDict, Error> {
332 if self.top_dict.font_dicts.count() != 0 {
333 let font_dict_data = self.top_dict.font_dicts.get(subfont_index as usize)?;
336 FontDict::new(font_dict_data)
337 } else {
338 let range = self.top_dict.private_dict_range.clone();
343 Ok(FontDict {
344 private_dict_range: range.start as usize..range.end as usize,
345 font_matrix: None,
346 })
347 }
348 }
349}
350
351fn scale_for_hinting(scale: Option<Fixed>) -> Fixed {
355 Fixed::from_bits((scale.unwrap_or(Fixed::ONE).to_bits().saturating_add(32)) / 64)
356}
357
358struct CharstringEvaluator<'a> {
359 cff_data: &'a [u8],
360 charstrings: Index<'a>,
361 global_subrs: Index<'a>,
362 subrs: Option<Index<'a>>,
363 blend_state: Option<BlendState<'a>>,
364 charstring_data: &'a [u8],
365}
366
367impl CharstringEvaluator<'_> {
368 fn evaluate(self, sink: &mut impl CommandSink) -> Result<Option<Fixed>, Error> {
369 let subrs = self.subrs.unwrap_or_default();
370 let ctx = (self.cff_data, &self.charstrings, &self.global_subrs, &subrs);
371 cs::evaluate(&ctx, self.blend_state, self.charstring_data, sink)
372 }
373}
374
375#[derive(Clone)]
383pub(crate) struct Subfont {
384 is_cff2: bool,
385 scale: Option<Fixed>,
386 scale_requested: bool,
390 subrs_offset: Option<usize>,
391 pub(crate) hint_state: HintState,
392 store_index: u16,
393 font_matrix: Option<FontMatrix>,
394 default_width: Option<Fixed>,
395 nominal_width: Fixed,
396}
397
398impl Subfont {
399 pub fn subrs<'a>(&self, scaler: &Outlines<'a>) -> Result<Option<Index<'a>>, Error> {
401 if let Some(subrs_offset) = self.subrs_offset {
402 let offset_data = scaler.offset_data.as_bytes();
403 let index_data = offset_data.get(subrs_offset..).unwrap_or_default();
404 Ok(Some(Index::new(index_data, self.is_cff2)?))
405 } else {
406 Ok(None)
407 }
408 }
409
410 pub fn blend_state<'a>(
413 &self,
414 scaler: &Outlines<'a>,
415 coords: &'a [F2Dot14],
416 ) -> Result<Option<BlendState<'a>>, Error> {
417 if let Some(var_store) = scaler.top_dict.var_store.clone() {
418 Ok(Some(BlendState::new(var_store, coords, self.store_index)?))
419 } else {
420 Ok(None)
421 }
422 }
423}
424
425#[derive(Default)]
428struct PrivateDict {
429 hint_params: HintParams,
430 subrs_offset: Option<usize>,
431 store_index: u16,
432 default_width: Option<Fixed>,
433 nominal_width: Fixed,
434}
435
436impl PrivateDict {
437 fn new(
438 data: FontData,
439 range: Range<usize>,
440 blend_state: Option<BlendState<'_>>,
441 ) -> Result<Self, Error> {
442 let private_dict_data = data.read_array(range.clone())?;
443 let mut dict = Self::default();
444 for entry in dict::entries(private_dict_data, blend_state) {
445 use dict::Entry::*;
446 match entry? {
447 DefaultWidthX(width) => dict.default_width = Some(width.floor()),
449 NominalWidthX(width) => dict.nominal_width = width.floor(),
450 BlueValues(values) => dict.hint_params.blues = values,
451 FamilyBlues(values) => dict.hint_params.family_blues = values,
452 OtherBlues(values) => dict.hint_params.other_blues = values,
453 FamilyOtherBlues(values) => dict.hint_params.family_other_blues = values,
454 BlueScale(value) => dict.hint_params.blue_scale = value,
455 BlueShift(value) => dict.hint_params.blue_shift = value,
456 BlueFuzz(value) => dict.hint_params.blue_fuzz = value,
457 LanguageGroup(group) => dict.hint_params.language_group = group,
458 SubrsOffset(offset) => {
460 dict.subrs_offset = Some(
461 range
462 .start
463 .checked_add(offset)
464 .ok_or(ReadError::OutOfBounds)?,
465 )
466 }
467 VariationStoreIndex(index) => dict.store_index = index,
468 _ => {}
469 }
470 }
471 Ok(dict)
472 }
473}
474
475#[derive(Clone, Default)]
477struct FontDict {
478 private_dict_range: Range<usize>,
479 font_matrix: Option<ScaledFontMatrix>,
480}
481
482impl FontDict {
483 fn new(font_dict_data: &[u8]) -> Result<Self, Error> {
484 let mut range = None;
485 let mut font_matrix = None;
486 for entry in dict::entries(font_dict_data, None) {
487 match entry? {
488 dict::Entry::PrivateDictRange(r) => {
489 range = Some(r);
490 }
491 dict::Entry::FontMatrix(matrix) => font_matrix = Some(matrix),
495 _ => {}
496 }
497 }
498 Ok(Self {
499 private_dict_range: range.ok_or(Error::MissingPrivateDict)?,
500 font_matrix,
501 })
502 }
503}
504
505#[derive(Clone, Default)]
508struct TopDict<'a> {
509 charstrings: Index<'a>,
510 font_dicts: Index<'a>,
511 fd_select: Option<FdSelect<'a>>,
512 private_dict_range: Range<u32>,
513 font_matrix: Option<ScaledFontMatrix>,
514 var_store: Option<ItemVariationStore<'a>>,
515}
516
517impl<'a> TopDict<'a> {
518 fn new(table_data: &'a [u8], top_dict_data: &'a [u8], is_cff2: bool) -> Result<Self, Error> {
519 let mut items = TopDict::default();
520 for entry in dict::entries(top_dict_data, None) {
521 match entry? {
522 dict::Entry::CharstringsOffset(offset) => {
523 items.charstrings =
524 Index::new(table_data.get(offset..).unwrap_or_default(), is_cff2)?;
525 }
526 dict::Entry::FdArrayOffset(offset) => {
527 items.font_dicts =
528 Index::new(table_data.get(offset..).unwrap_or_default(), is_cff2)?;
529 }
530 dict::Entry::FdSelectOffset(offset) => {
531 items.fd_select = Some(FdSelect::read(FontData::new(
532 table_data.get(offset..).unwrap_or_default(),
533 ))?);
534 }
535 dict::Entry::PrivateDictRange(range) => {
536 items.private_dict_range = range.start as u32..range.end as u32;
537 }
538 dict::Entry::FontMatrix(matrix) => {
539 items.font_matrix = Some(matrix.normalize());
541 }
542 dict::Entry::VariationStoreOffset(offset) if is_cff2 => {
543 let offset = offset.checked_add(2).ok_or(ReadError::OutOfBounds)?;
547 items.var_store = Some(ItemVariationStore::read(FontData::new(
548 table_data.get(offset..).unwrap_or_default(),
549 ))?);
550 }
551 _ => {}
552 }
553 }
554 Ok(items)
555 }
556}
557
558struct PenSink<'a, P>(&'a mut P);
561
562impl<'a, P> PenSink<'a, P> {
563 fn new(pen: &'a mut P) -> Self {
564 Self(pen)
565 }
566}
567
568impl<P> CommandSink for PenSink<'_, P>
569where
570 P: OutlinePen,
571{
572 fn move_to(&mut self, x: Fixed, y: Fixed) {
573 self.0.move_to(x.to_f32(), y.to_f32());
574 }
575
576 fn line_to(&mut self, x: Fixed, y: Fixed) {
577 self.0.line_to(x.to_f32(), y.to_f32());
578 }
579
580 fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
581 self.0.curve_to(
582 cx0.to_f32(),
583 cy0.to_f32(),
584 cx1.to_f32(),
585 cy1.to_f32(),
586 x.to_f32(),
587 y.to_f32(),
588 );
589 }
590
591 fn close(&mut self) {
592 self.0.close();
593 }
594}
595
596struct HintedTransformingSink<'a, S> {
598 inner: &'a mut S,
599 matrix: FontMatrix,
600}
601
602impl<'a, S> HintedTransformingSink<'a, S> {
603 fn new(sink: &'a mut S, matrix: FontMatrix) -> Self {
604 Self {
605 inner: sink,
606 matrix,
607 }
608 }
609
610 fn transform(&self, x: Fixed, y: Fixed) -> (Fixed, Fixed) {
611 let (x, y) = self.matrix.transform(
614 Fixed::from_bits(x.to_bits() >> 10),
615 Fixed::from_bits(y.to_bits() >> 10),
616 );
617 (
618 Fixed::from_bits(x.to_bits() << 10),
619 Fixed::from_bits(y.to_bits() << 10),
620 )
621 }
622}
623
624impl<S: CommandSink> CommandSink for HintedTransformingSink<'_, S> {
625 fn hstem(&mut self, y: Fixed, dy: Fixed) {
626 self.inner.hstem(y, dy);
627 }
628
629 fn vstem(&mut self, x: Fixed, dx: Fixed) {
630 self.inner.vstem(x, dx);
631 }
632
633 fn hint_mask(&mut self, mask: &[u8]) {
634 self.inner.hint_mask(mask);
635 }
636
637 fn counter_mask(&mut self, mask: &[u8]) {
638 self.inner.counter_mask(mask);
639 }
640
641 fn clear_hints(&mut self) {
642 self.inner.clear_hints();
643 }
644
645 fn move_to(&mut self, x: Fixed, y: Fixed) {
646 let (x, y) = self.transform(x, y);
647 self.inner.move_to(x, y);
648 }
649
650 fn line_to(&mut self, x: Fixed, y: Fixed) {
651 let (x, y) = self.transform(x, y);
652 self.inner.line_to(x, y);
653 }
654
655 fn curve_to(&mut self, cx1: Fixed, cy1: Fixed, cx2: Fixed, cy2: Fixed, x: Fixed, y: Fixed) {
656 let (cx1, cy1) = self.transform(cx1, cy1);
657 let (cx2, cy2) = self.transform(cx2, cy2);
658 let (x, y) = self.transform(x, y);
659 self.inner.curve_to(cx1, cy1, cx2, cy2, x, y);
660 }
661
662 fn close(&mut self) {
663 self.inner.close();
664 }
665
666 fn finish(&mut self) {
667 self.inner.finish();
668 }
669}
670
671#[cfg(test)]
672mod tests {
673 use super::{super::pen::SvgPen, *};
674 use crate::{
675 outline::{HintingInstance, HintingOptions},
676 prelude::{LocationRef, Size},
677 MetadataProvider,
678 };
679 use font_test_data::bebuffer::BeBuffer;
680 use raw::tables::cff2::Cff2;
681 use read_fonts::ps::hinting::Blues;
682 use read_fonts::FontRef;
683
684 #[test]
685 fn read_cff_static() {
686 let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
687 let cff = Outlines::new(&font).unwrap();
688 assert!(!cff.is_cff2());
689 assert!(cff.top_dict.var_store.is_none());
690 assert!(cff.top_dict.font_dicts.count() == 0);
691 assert!(!cff.top_dict.private_dict_range.is_empty());
692 assert!(cff.top_dict.fd_select.is_none());
693 assert_eq!(cff.subfont_count(), 1);
694 assert_eq!(cff.subfont_index(GlyphId::new(1)), 0);
695 assert_eq!(cff.global_subrs.count(), 17);
696 }
697
698 #[test]
699 fn read_cff2_static() {
700 let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
701 let cff = Outlines::new(&font).unwrap();
702 assert!(cff.is_cff2());
703 assert!(cff.top_dict.var_store.is_some());
704 assert!(cff.top_dict.font_dicts.count() != 0);
705 assert!(cff.top_dict.private_dict_range.is_empty());
706 assert!(cff.top_dict.fd_select.is_none());
707 assert_eq!(cff.subfont_count(), 1);
708 assert_eq!(cff.subfont_index(GlyphId::new(1)), 0);
709 assert_eq!(cff.global_subrs.count(), 0);
710 }
711
712 #[test]
713 fn read_example_cff2_table() {
714 let cff2 = Cff2::read(FontData::new(font_test_data::cff2::EXAMPLE)).unwrap();
715 let top_dict =
716 TopDict::new(cff2.offset_data().as_bytes(), cff2.top_dict_data(), true).unwrap();
717 assert!(top_dict.var_store.is_some());
718 assert!(top_dict.font_dicts.count() != 0);
719 assert!(top_dict.private_dict_range.is_empty());
720 assert!(top_dict.fd_select.is_none());
721 assert_eq!(cff2.global_subrs().count(), 0);
722 }
723
724 #[test]
725 fn cff2_variable_outlines_match_freetype() {
726 compare_glyphs(
727 font_test_data::CANTARELL_VF_TRIMMED,
728 font_test_data::CANTARELL_VF_TRIMMED_GLYPHS,
729 );
730 }
731
732 #[test]
733 fn cff_static_outlines_match_freetype() {
734 compare_glyphs(
735 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
736 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED_GLYPHS,
737 );
738 }
739
740 #[test]
741 fn unhinted_ends_with_close() {
742 let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
743 let glyph = font.outline_glyphs().get(GlyphId::new(1)).unwrap();
744 let mut svg = SvgPen::default();
745 glyph.draw(Size::unscaled(), &mut svg).unwrap();
746 assert!(svg.to_string().ends_with('Z'));
747 }
748
749 #[test]
750 fn hinted_ends_with_close() {
751 let font = FontRef::new(font_test_data::CANTARELL_VF_TRIMMED).unwrap();
752 let glyphs = font.outline_glyphs();
753 let hinter = HintingInstance::new(
754 &glyphs,
755 Size::unscaled(),
756 LocationRef::default(),
757 HintingOptions::default(),
758 )
759 .unwrap();
760 let glyph = glyphs.get(GlyphId::new(1)).unwrap();
761 let mut svg = SvgPen::default();
762 glyph.draw(&hinter, &mut svg).unwrap();
763 assert!(svg.to_string().ends_with('Z'));
764 }
765
766 #[test]
768 fn empty_private_dict() {
769 let font = FontRef::new(font_test_data::MATERIAL_ICONS_SUBSET).unwrap();
770 let outlines = super::Outlines::new(&font).unwrap();
771 assert!(outlines.top_dict.private_dict_range.is_empty());
772 assert!(outlines
773 .parse_font_dict(0)
774 .unwrap()
775 .private_dict_range
776 .is_empty());
777 }
778
779 #[test]
782 fn subrs_offset_overflow() {
783 let private_dict = BeBuffer::new()
785 .push(0u32) .push(29u8) .push(-1i32) .push(19u8) .to_vec();
790 assert!(
792 PrivateDict::new(FontData::new(&private_dict), 4..private_dict.len(), None).is_err()
793 );
794 }
795
796 #[test]
800 fn top_dict_ivs_offset_overflow() {
801 let top_dict = BeBuffer::new()
804 .push(29u8) .push(-1i32) .push(24u8) .to_vec();
808 assert!(TopDict::new(&[], &top_dict, true).is_err());
810 }
811
812 #[test]
819 fn proper_scaling_when_factor_equals_fixed_one() {
820 let font = FontRef::new(font_test_data::MATERIAL_ICONS_SUBSET).unwrap();
821 assert_eq!(font.head().unwrap().units_per_em(), 512);
822 let glyphs = font.outline_glyphs();
823 let glyph = glyphs.get(GlyphId::new(1)).unwrap();
824 let mut svg = SvgPen::with_precision(6);
825 glyph
826 .draw((Size::new(8.0), LocationRef::default()), &mut svg)
827 .unwrap();
828 assert!(svg.starts_with("M6.328125,7.000000 L1.671875,7.000000"));
830 }
831
832 fn compare_glyphs(font_data: &[u8], expected_outlines: &str) {
839 use super::super::testing;
840 let font = FontRef::new(font_data).unwrap();
841 let expected_outlines = testing::parse_glyph_outlines(expected_outlines);
842 let outlines = super::Outlines::new(&font).unwrap();
843 let mut path = testing::Path::default();
844 for expected_outline in &expected_outlines {
845 if expected_outline.size == 0.0 && !expected_outline.coords.is_empty() {
846 continue;
847 }
848 let size = (expected_outline.size != 0.0).then_some(expected_outline.size);
849 path.elements.clear();
850 let subfont = outlines
851 .subfont(
852 outlines.subfont_index(expected_outline.glyph_id),
853 size,
854 &expected_outline.coords,
855 )
856 .unwrap();
857 outlines
858 .draw(
859 &subfont,
860 expected_outline.glyph_id,
861 &expected_outline.coords,
862 false,
863 &mut path,
864 )
865 .unwrap();
866 if path.elements != expected_outline.path {
867 panic!(
868 "mismatch in glyph path for id {} (size: {}, coords: {:?}): path: {:?} expected_path: {:?}",
869 expected_outline.glyph_id,
870 expected_outline.size,
871 expected_outline.coords,
872 &path.elements,
873 &expected_outline.path
874 );
875 }
876 }
877 }
878
879 #[test]
881 fn capture_family_other_blues() {
882 let private_dict_data = &font_test_data::cff2::EXAMPLE[0x4f..=0xc0];
883 let store =
884 ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
885 let coords = &[F2Dot14::from_f32(0.0)];
886 let blend_state = BlendState::new(store, coords, 0).unwrap();
887 let private_dict = PrivateDict::new(
888 FontData::new(private_dict_data),
889 0..private_dict_data.len(),
890 Some(blend_state),
891 )
892 .unwrap();
893 assert_eq!(
894 private_dict.hint_params.family_other_blues,
895 Blues::new([-249.0, -239.0].map(Fixed::from_f64).into_iter())
896 )
897 }
898
899 #[test]
900 fn implied_seac() {
901 let font = FontRef::new(font_test_data::CHARSTRING_PATH_OPS).unwrap();
902 let glyphs = font.outline_glyphs();
903 let gid = GlyphId::new(3);
904 assert_eq!(font.glyph_names().get(gid).unwrap(), "Scaron");
905 let glyph = glyphs.get(gid).unwrap();
906 let mut pen = SvgPen::new();
907 glyph
908 .draw((Size::unscaled(), LocationRef::default()), &mut pen)
909 .unwrap();
910 assert_eq!(pen.to_string().chars().filter(|ch| *ch == 'Z').count(), 2);
915 }
916
917 #[test]
918 fn implied_seac_clears_hints() {
919 let font = FontRef::new(font_test_data::CHARSTRING_PATH_OPS).unwrap();
920 let outlines = Outlines::from_cff(&font, 1000).unwrap();
921 let subfont = outlines.subfont(0, Some(16.0), &[]).unwrap();
922 let cff_data = outlines.offset_data.as_bytes();
923 let charstrings = outlines.top_dict.charstrings.clone();
924 let charstring_data = charstrings.get(3).unwrap();
925 let subrs = subfont.subrs(&outlines).unwrap();
926 let blend_state = None;
927 let cs_eval = CharstringEvaluator {
928 cff_data,
929 charstrings,
930 global_subrs: outlines.global_subrs.clone(),
931 subrs,
932 blend_state,
933 charstring_data,
934 };
935 struct ClearHintsCountingSink(u32);
936 impl CommandSink for ClearHintsCountingSink {
937 fn move_to(&mut self, _: Fixed, _: Fixed) {}
938 fn line_to(&mut self, _: Fixed, _: Fixed) {}
939 fn curve_to(&mut self, _: Fixed, _: Fixed, _: Fixed, _: Fixed, _: Fixed, _: Fixed) {}
940 fn close(&mut self) {}
941 fn clear_hints(&mut self) {
942 self.0 += 1;
943 }
944 }
945 let mut sink = ClearHintsCountingSink(0);
946 cs_eval.evaluate(&mut sink).unwrap();
947 assert_eq!(sink.0, 2);
950 }
951
952 const TRANSFORM: FontMatrix = FontMatrix::from_elements([
953 Fixed::ONE,
954 Fixed::ZERO,
955 Fixed::from_bits(10945),
957 Fixed::ONE,
958 Fixed::ZERO,
959 Fixed::ZERO,
960 ]);
961
962 #[test]
963 fn hinted_transform_sink() {
964 let input = [(383i32, 117i32), (450, 20), (555, -34), (683, -34)]
967 .map(|(x, y)| (Fixed::from_bits(x << 10), Fixed::from_bits(y << 10)));
968 let expected = [(403, 117i32), (453, 20), (549, -34), (677, -34)]
969 .map(|(x, y)| (Fixed::from_bits(x << 10), Fixed::from_bits(y << 10)));
970 let mut dummy = ();
971 let sink = HintedTransformingSink::new(&mut dummy, TRANSFORM);
972 let transformed = input.map(|(x, y)| sink.transform(x, y));
973 assert_eq!(transformed, expected);
974 }
975
976 #[test]
978 fn nested_font_matrices() {
979 let font = FontRef::new(font_test_data::MATERIAL_ICONS_SUBSET_MATRIX).unwrap();
981 let outlines = Outlines::from_cff(&font, 512).unwrap();
982 let top_matrix = outlines.top_dict.font_matrix.unwrap();
984 let expected_top_matrix = [65536, 0, 5604, 65536, 0, 0].map(Fixed::from_bits);
985 assert_eq!(top_matrix.matrix.elements(), expected_top_matrix);
986 assert_eq!(top_matrix.scale, 512);
987 let sub_matrix = outlines.parse_font_dict(0).unwrap().font_matrix.unwrap();
989 let expected_sub_matrix = [327680, 0, 0, 327680, 0, 0].map(Fixed::from_bits);
990 assert_eq!(sub_matrix.matrix.elements(), expected_sub_matrix);
991 assert_eq!(sub_matrix.scale, 10);
992 let subfont = outlines.subfont(0, Some(24.0), &[]).unwrap();
994 let expected_combined_matrix = [65536, 0, 5604, 65536, 0, 0].map(Fixed::from_bits);
995 assert_eq!(
996 subfont.font_matrix.unwrap().elements(),
997 expected_combined_matrix
998 );
999 assert_eq!(subfont.scale.unwrap().to_bits(), 98304);
1001 }
1002
1003 #[test]
1007 fn subfont_hint_scale_overflow() {
1008 let _ = scale_for_hinting(Some(Fixed::from_bits(i32::MAX)));
1010 }
1011}