1pub mod autohint;
82mod cff;
83mod glyf;
84mod hint;
85mod hint_reliant;
86mod memory;
87mod metrics;
88mod path;
89mod unscaled;
90mod varc;
91
92#[cfg(test)]
93mod testing;
94
95pub mod error;
96pub mod pen;
97
98pub use autohint::GlyphStyles;
99pub use hint::{
100 Engine, HintingInstance, HintingMode, HintingOptions, LcdLayout, SmoothMode, Target,
101};
102use metrics::GlyphHMetrics;
103use raw::FontRef;
104#[doc(inline)]
105pub use {error::DrawError, pen::OutlinePen};
106
107use self::glyf::{FreeTypeScaler, HarfBuzzScaler};
108use super::{
109 instance::{LocationRef, NormalizedCoord, Size},
110 GLYF_COMPOSITE_RECURSION_LIMIT,
111};
112use core::fmt::Debug;
113use pen::PathStyle;
114use read_fonts::{types::GlyphId, TableProvider};
115
116#[cfg(feature = "libm")]
117#[allow(unused_imports)]
118use core_maths::CoreFloat;
119
120#[derive(Copy, Clone, PartialEq, Eq, Debug)]
122pub enum OutlineGlyphFormat {
123 Glyf,
125 Cff,
127 Cff2,
129 Varc,
131}
132
133#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
135pub enum Hinting {
136 #[default]
138 None,
139 Embedded,
145}
146
147#[derive(Copy, Clone, Default, Debug)]
153pub struct AdjustedMetrics {
154 pub has_overlaps: bool,
157 pub lsb: Option<f32>,
163 pub advance_width: Option<f32>,
176}
177
178pub struct DrawSettings<'a> {
181 instance: DrawInstance<'a>,
182 memory: Option<&'a mut [u8]>,
183 path_style: PathStyle,
184}
185
186impl<'a> DrawSettings<'a> {
187 pub fn unhinted(size: Size, location: impl Into<LocationRef<'a>>) -> Self {
190 Self {
191 instance: DrawInstance::Unhinted(size, location.into()),
192 memory: None,
193 path_style: PathStyle::default(),
194 }
195 }
196
197 pub fn hinted(instance: &'a HintingInstance, is_pedantic: bool) -> Self {
207 Self {
208 instance: DrawInstance::Hinted {
209 instance,
210 is_pedantic,
211 },
212 memory: None,
213 path_style: PathStyle::default(),
214 }
215 }
216
217 pub fn with_memory(mut self, memory: Option<&'a mut [u8]>) -> Self {
225 self.memory = memory;
226 self
227 }
228
229 pub fn with_path_style(mut self, path_style: PathStyle) -> Self {
233 self.path_style = path_style;
234 self
235 }
236}
237
238enum DrawInstance<'a> {
239 Unhinted(Size, LocationRef<'a>),
240 Hinted {
241 instance: &'a HintingInstance,
242 is_pedantic: bool,
243 },
244}
245
246impl<'a, L> From<(Size, L)> for DrawSettings<'a>
247where
248 L: Into<LocationRef<'a>>,
249{
250 fn from(value: (Size, L)) -> Self {
251 DrawSettings::unhinted(value.0, value.1.into())
252 }
253}
254
255impl From<Size> for DrawSettings<'_> {
256 fn from(value: Size) -> Self {
257 DrawSettings::unhinted(value, LocationRef::default())
258 }
259}
260
261impl<'a> From<&'a HintingInstance> for DrawSettings<'a> {
262 fn from(value: &'a HintingInstance) -> Self {
263 DrawSettings::hinted(value, false)
264 }
265}
266
267#[derive(Clone)]
275pub struct OutlineGlyph<'a> {
276 kind: OutlineKind<'a>,
277}
278
279impl<'a> OutlineGlyph<'a> {
280 pub fn format(&self) -> OutlineGlyphFormat {
282 match &self.kind {
283 OutlineKind::Glyf(..) => OutlineGlyphFormat::Glyf,
284 OutlineKind::Cff(cff, ..) => {
285 if cff.is_cff2() {
286 OutlineGlyphFormat::Cff2
287 } else {
288 OutlineGlyphFormat::Cff
289 }
290 }
291 OutlineKind::Varc(..) => OutlineGlyphFormat::Varc,
292 }
293 }
294
295 pub fn glyph_id(&self) -> GlyphId {
297 match &self.kind {
298 OutlineKind::Glyf(_, glyph) => glyph.glyph_id,
299 OutlineKind::Cff(_, gid, _) => *gid,
300 OutlineKind::Varc(_, outline) => outline.glyph_id,
301 }
302 }
303
304 pub fn has_overlaps(&self) -> Option<bool> {
309 match &self.kind {
310 OutlineKind::Glyf(_, outline) => Some(outline.has_overlaps),
311 _ => None,
312 }
313 }
314
315 pub fn has_hinting(&self) -> Option<bool> {
321 match &self.kind {
322 OutlineKind::Glyf(_, outline) => Some(outline.has_hinting),
323 OutlineKind::Varc(..) => Some(false),
324 _ => None,
325 }
326 }
327
328 pub fn draw_memory_size(&self, hinting: Hinting) -> usize {
344 match &self.kind {
345 OutlineKind::Glyf(_, outline) => outline.required_buffer_size(hinting),
346 OutlineKind::Varc(_, outline) => outline.required_buffer_size(),
347 _ => 0,
348 }
349 }
350
351 pub fn draw<'s>(
354 &self,
355 settings: impl Into<DrawSettings<'a>>,
356 pen: &mut impl OutlinePen,
357 ) -> Result<AdjustedMetrics, DrawError> {
358 let settings: DrawSettings<'a> = settings.into();
359 match (settings.instance, settings.path_style) {
360 (DrawInstance::Unhinted(size, location), PathStyle::FreeType) => {
361 self.draw_unhinted(size, location, settings.memory, settings.path_style, pen)
362 }
363 (DrawInstance::Unhinted(size, location), PathStyle::HarfBuzz) => {
364 self.draw_unhinted(size, location, settings.memory, settings.path_style, pen)
365 }
366 (
367 DrawInstance::Hinted {
368 instance: hinting_instance,
369 is_pedantic,
370 },
371 PathStyle::FreeType,
372 ) => {
373 if hinting_instance.is_enabled() {
374 hinting_instance.draw(
375 self,
376 settings.memory,
377 settings.path_style,
378 pen,
379 is_pedantic,
380 )
381 } else {
382 let mut metrics = self.draw_unhinted(
383 hinting_instance.size(),
384 hinting_instance.location(),
385 settings.memory,
386 settings.path_style,
387 pen,
388 )?;
389 if let Some(advance) = metrics.advance_width.as_mut() {
392 *advance = advance.round();
393 }
394 Ok(metrics)
395 }
396 }
397 (DrawInstance::Hinted { .. }, PathStyle::HarfBuzz) => {
398 Err(DrawError::HarfBuzzHintingUnsupported)
399 }
400 }
401 }
402
403 fn draw_unhinted(
404 &self,
405 size: Size,
406 location: impl Into<LocationRef<'a>>,
407 user_memory: Option<&mut [u8]>,
408 path_style: PathStyle,
409 pen: &mut impl OutlinePen,
410 ) -> Result<AdjustedMetrics, DrawError> {
411 let ppem = size.ppem();
412 let coords = location.into().effective_coords();
413 match &self.kind {
414 OutlineKind::Glyf(glyf, outline) => {
415 with_temporary_memory(self, Hinting::None, user_memory, |buf| {
416 let (lsb, advance_width) = match path_style {
417 PathStyle::FreeType => {
418 let scaled_outline =
419 FreeTypeScaler::unhinted(glyf, outline, buf, ppem, coords)?
420 .scale(&outline.glyph, outline.glyph_id)?;
421 scaled_outline.to_path(path_style, pen)?;
422 (
423 scaled_outline.adjusted_lsb().to_f32(),
424 scaled_outline.adjusted_advance_width().to_f32(),
425 )
426 }
427 PathStyle::HarfBuzz => {
428 let scaled_outline =
429 HarfBuzzScaler::unhinted(glyf, outline, buf, ppem, coords)?
430 .scale(&outline.glyph, outline.glyph_id)?;
431 scaled_outline.to_path(path_style, pen)?;
432 (
433 scaled_outline.adjusted_lsb(),
434 scaled_outline.adjusted_advance_width(),
435 )
436 }
437 };
438
439 Ok(AdjustedMetrics {
440 has_overlaps: outline.has_overlaps,
441 lsb: Some(lsb),
442 advance_width: Some(advance_width),
443 })
444 })
445 }
446 OutlineKind::Cff(cff, glyph_id, subfont_ix) => {
447 let subfont = cff.subfont(*subfont_ix, ppem, coords)?;
448 let advance_width = cff.draw(&subfont, *glyph_id, coords, false, pen)?;
449 Ok(AdjustedMetrics {
450 has_overlaps: false,
451 lsb: None,
452 advance_width,
453 })
454 }
455 OutlineKind::Varc(varc, outline) => {
456 with_temporary_memory(self, Hinting::None, user_memory, |buf| {
457 varc.draw(outline, buf, size, coords, path_style, pen)?;
458 Ok(AdjustedMetrics::default())
459 })
460 }
461 }
462 }
463
464 #[allow(dead_code)]
467 fn draw_unscaled(
468 &self,
469 location: impl Into<LocationRef<'a>>,
470 user_memory: Option<&mut [u8]>,
471 sink: &mut impl unscaled::UnscaledOutlineSink,
472 ) -> Result<i32, DrawError> {
473 let coords = location.into().effective_coords();
474 let ppem = None;
475 match &self.kind {
476 OutlineKind::Glyf(glyf, outline) => {
477 with_temporary_memory(self, Hinting::None, user_memory, |buf| {
478 let outline = FreeTypeScaler::unhinted(glyf, outline, buf, ppem, coords)?
479 .scale(&outline.glyph, outline.glyph_id)?;
480 sink.try_reserve(outline.points.len())?;
481 let mut contour_start = 0;
482 for contour_end in outline.contours.iter().map(|contour| *contour as usize) {
483 if contour_end >= contour_start {
484 if let Some(points) = outline.points.get(contour_start..=contour_end) {
485 let flags = &outline.flags[contour_start..=contour_end];
486 sink.extend(points.iter().zip(flags).enumerate().map(
487 |(ix, (point, flags))| {
488 unscaled::UnscaledPoint::from_glyf_point(
489 *point,
490 *flags,
491 ix == 0,
492 )
493 },
494 ))?;
495 }
496 }
497 contour_start = contour_end + 1;
498 }
499 Ok(outline.adjusted_advance_width().to_bits() >> 6)
500 })
501 }
502 OutlineKind::Cff(cff, glyph_id, subfont_ix) => {
503 let subfont = cff.subfont(*subfont_ix, ppem, coords)?;
504 let mut adapter = unscaled::UnscaledPenAdapter::new(sink);
505 cff.draw(&subfont, *glyph_id, coords, false, &mut adapter)?;
506 adapter.finish()?;
507 let advance = cff.glyph_metrics.advance_width(*glyph_id, coords);
508 Ok(advance)
509 }
510 OutlineKind::Varc(varc, outline) => {
511 with_temporary_memory(self, Hinting::None, user_memory, |buf| {
512 let mut adapter = unscaled::UnscaledPenAdapter::new(sink);
513 let advance = varc.draw_unscaled(outline, buf, coords, &mut adapter)?;
514 adapter.finish()?;
515 Ok(advance)
516 })
517 }
518 }
519 }
520
521 pub fn with_scaled_glyf_outline<R>(
523 &self,
524 size: Size,
525 location: impl Into<LocationRef<'a>>,
526 user_memory: Option<&mut [u8]>,
527 mut callback: impl FnMut(&glyf::ScaledOutline<'_, raw::types::F26Dot6>) -> Result<R, DrawError>,
528 ) -> Result<R, DrawError> {
529 let ppem = size.ppem();
530 let coords = location.into().effective_coords();
531
532 match &self.kind {
533 OutlineKind::Glyf(glyf, outline) => {
534 with_temporary_memory(self, Hinting::None, user_memory, |buf| {
535 let scaled = FreeTypeScaler::unhinted(glyf, outline, buf, ppem, coords)?
536 .scale(&outline.glyph, outline.glyph_id)?;
537 callback(&scaled)
538 })
539 }
540 _ => Err(DrawError::NoSources),
541 }
542 }
543
544 pub(crate) fn font(&self) -> &FontRef<'a> {
545 match &self.kind {
546 OutlineKind::Glyf(glyf, ..) => &glyf.font,
547 OutlineKind::Cff(cff, ..) => &cff.font,
548 OutlineKind::Varc(varc, ..) => varc.font(),
549 }
550 }
551
552 fn units_per_em(&self) -> u16 {
553 match &self.kind {
554 OutlineKind::Cff(cff, ..) => cff.units_per_em(),
555 OutlineKind::Glyf(glyf, ..) => glyf.units_per_em(),
556 OutlineKind::Varc(varc, ..) => varc.units_per_em(),
557 }
558 }
559}
560
561#[derive(Clone)]
562enum OutlineKind<'a> {
563 Glyf(glyf::Outlines<'a>, glyf::Outline<'a>),
564 Cff(cff::Outlines<'a>, GlyphId, u32),
566 Varc(varc::Outlines<'a>, varc::Outline),
567}
568
569impl Debug for OutlineKind<'_> {
570 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
571 match self {
572 Self::Glyf(_, outline) => f.debug_tuple("Glyf").field(&outline.glyph_id).finish(),
573 Self::Cff(_, gid, subfont_index) => f
574 .debug_tuple("Cff")
575 .field(gid)
576 .field(subfont_index)
577 .finish(),
578 Self::Varc(_, outline) => f.debug_tuple("Varc").field(&outline.glyph_id).finish(),
579 }
580 }
581}
582
583#[derive(Debug, Clone)]
585pub struct OutlineGlyphCollection<'a> {
586 kind: OutlineCollectionKind<'a>,
587}
588
589impl<'a> OutlineGlyphCollection<'a> {
590 pub fn new(font: &FontRef<'a>) -> Self {
592 let kind = if let Some(varc) = varc::Outlines::new(font) {
593 OutlineCollectionKind::Varc(varc)
594 } else if let Some(glyf) = glyf::Outlines::new(font) {
595 OutlineCollectionKind::Glyf(glyf)
596 } else if let Some(cff) = cff::Outlines::new(font) {
597 OutlineCollectionKind::Cff(cff)
598 } else {
599 OutlineCollectionKind::None
600 };
601 Self { kind }
602 }
603
604 pub fn with_format(font: &FontRef<'a>, format: OutlineGlyphFormat) -> Option<Self> {
610 let kind = match format {
611 OutlineGlyphFormat::Glyf => OutlineCollectionKind::Glyf(glyf::Outlines::new(font)?),
612 OutlineGlyphFormat::Cff => {
613 let upem = font.head().ok()?.units_per_em();
614 OutlineCollectionKind::Cff(cff::Outlines::from_cff(font, upem)?)
615 }
616 OutlineGlyphFormat::Cff2 => {
617 let upem = font.head().ok()?.units_per_em();
618 OutlineCollectionKind::Cff(cff::Outlines::from_cff2(font, upem)?)
619 }
620 OutlineGlyphFormat::Varc => OutlineCollectionKind::Varc(varc::Outlines::new(font)?),
621 };
622 Some(Self { kind })
623 }
624
625 pub fn format(&self) -> Option<OutlineGlyphFormat> {
627 match &self.kind {
628 OutlineCollectionKind::Glyf(..) => Some(OutlineGlyphFormat::Glyf),
629 OutlineCollectionKind::Cff(cff) => cff
630 .is_cff2()
631 .then_some(OutlineGlyphFormat::Cff2)
632 .or(Some(OutlineGlyphFormat::Cff)),
633 OutlineCollectionKind::Varc(..) => Some(OutlineGlyphFormat::Varc),
634 OutlineCollectionKind::None => None,
635 }
636 }
637
638 pub fn get(&self, glyph_id: GlyphId) -> Option<OutlineGlyph<'a>> {
640 match &self.kind {
641 OutlineCollectionKind::None => None,
642 OutlineCollectionKind::Glyf(glyf) => Some(OutlineGlyph {
643 kind: OutlineKind::Glyf(glyf.clone(), glyf.outline(glyph_id).ok()?),
644 }),
645 OutlineCollectionKind::Cff(cff) => Some(OutlineGlyph {
646 kind: OutlineKind::Cff(cff.clone(), glyph_id, cff.subfont_index(glyph_id)),
647 }),
648 OutlineCollectionKind::Varc(varc) => Some(OutlineGlyph {
649 kind: if let Some(outline) = varc.outline(glyph_id).ok()? {
650 OutlineKind::Varc(varc.clone(), outline)
651 } else {
652 varc.fallback_outline_kind(glyph_id)?
653 },
654 }),
655 }
656 }
657
658 pub fn iter(&self) -> impl Iterator<Item = (GlyphId, OutlineGlyph<'a>)> + 'a + Clone {
660 let len = match &self.kind {
661 OutlineCollectionKind::Glyf(glyf) => glyf.glyph_count() as u32,
662 OutlineCollectionKind::Cff(cff) => cff.glyph_count() as u32,
663 OutlineCollectionKind::Varc(varc) => varc.glyph_count(),
664 OutlineCollectionKind::None => 0,
665 };
666 let copy = self.clone();
667 (0..len).filter_map(move |gid| {
668 let gid = GlyphId::from(gid);
669 let glyph = copy.get(gid)?;
670 Some((gid, glyph))
671 })
672 }
673
674 pub fn prefer_interpreter(&self) -> bool {
688 match &self.kind {
689 OutlineCollectionKind::Glyf(glyf) => glyf.prefer_interpreter(),
690 OutlineCollectionKind::Varc(varc) => varc.prefer_interpreter(),
691 _ => true,
692 }
693 }
694
695 pub fn require_interpreter(&self) -> bool {
711 self.font()
712 .map(|font| hint_reliant::require_interpreter(font))
713 .unwrap_or_default()
714 }
715
716 pub fn fractional_size_hinting(&self) -> bool {
721 match &self.kind {
722 OutlineCollectionKind::Glyf(glyf) => glyf.fractional_size_hinting,
723 OutlineCollectionKind::Varc(varc) => varc.fractional_size_hinting(),
724 _ => true,
725 }
726 }
727
728 pub(crate) fn font(&self) -> Option<&FontRef<'a>> {
729 match &self.kind {
730 OutlineCollectionKind::Glyf(glyf) => Some(&glyf.font),
731 OutlineCollectionKind::Cff(cff) => Some(&cff.font),
732 OutlineCollectionKind::Varc(varc) => Some(varc.font()),
733 OutlineCollectionKind::None => None,
734 }
735 }
736}
737
738#[derive(Clone)]
739enum OutlineCollectionKind<'a> {
740 None,
741 Glyf(glyf::Outlines<'a>),
742 Cff(cff::Outlines<'a>),
743 Varc(varc::Outlines<'a>),
744}
745
746impl Debug for OutlineCollectionKind<'_> {
747 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
748 match self {
749 Self::None => write!(f, "None"),
750 Self::Glyf(..) => f.debug_tuple("Glyf").finish(),
751 Self::Cff(..) => f.debug_tuple("Cff").finish(),
752 Self::Varc(..) => f.debug_tuple("Varc").finish(),
753 }
754 }
755}
756
757pub(super) fn with_temporary_memory<R>(
760 outline: &OutlineGlyph<'_>,
761 hinting: Hinting,
762 memory: Option<&mut [u8]>,
763 mut f: impl FnMut(&mut [u8]) -> R,
764) -> R {
765 match memory {
766 Some(buf) => f(buf),
767 None => {
768 let buf_size = outline.draw_memory_size(hinting);
769 memory::with_temporary_memory(buf_size, f)
770 }
771 }
772}
773
774#[cfg(test)]
775mod tests {
776 use super::*;
777 use crate::{instance::Location, outline::pen::SvgPen, MetadataProvider};
778 use kurbo::{Affine, BezPath, PathEl, Point};
779 use read_fonts::{types::GlyphId, FontRef, TableProvider};
780
781 use pretty_assertions::assert_eq;
782
783 const PERIOD: u32 = 0x2E_u32;
784 const COMMA: u32 = 0x2C_u32;
785
786 #[test]
787 fn outline_glyph_formats() {
788 let font_format_pairs = [
789 (font_test_data::VAZIRMATN_VAR, OutlineGlyphFormat::Glyf),
790 (
791 font_test_data::CANTARELL_VF_TRIMMED,
792 OutlineGlyphFormat::Cff2,
793 ),
794 (
795 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
796 OutlineGlyphFormat::Cff,
797 ),
798 (font_test_data::COLRV0V1_VARIABLE, OutlineGlyphFormat::Glyf),
799 ];
800 for (font_data, format) in font_format_pairs {
801 assert_eq!(
802 FontRef::new(font_data).unwrap().outline_glyphs().format(),
803 Some(format)
804 );
805 }
806 }
807
808 #[test]
809 fn vazirmatin_var() {
810 compare_glyphs(
811 font_test_data::VAZIRMATN_VAR,
812 font_test_data::VAZIRMATN_VAR_GLYPHS,
813 );
814 }
815
816 #[test]
817 fn cantarell_vf() {
818 compare_glyphs(
819 font_test_data::CANTARELL_VF_TRIMMED,
820 font_test_data::CANTARELL_VF_TRIMMED_GLYPHS,
821 );
822 }
823
824 #[test]
825 fn noto_serif_display() {
826 compare_glyphs(
827 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
828 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED_GLYPHS,
829 );
830 }
831
832 #[test]
833 fn overlap_flags() {
834 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
835 let outlines = font.outline_glyphs();
836 let glyph_count = font.maxp().unwrap().num_glyphs();
837 let expected_gids_with_overlap = vec![2, 3];
840 assert_eq!(
841 expected_gids_with_overlap,
842 (0..glyph_count)
843 .filter(
844 |gid| outlines.get(GlyphId::from(*gid)).unwrap().has_overlaps() == Some(true)
845 )
846 .collect::<Vec<_>>()
847 );
848 }
849
850 fn compare_glyphs(font_data: &[u8], expected_outlines: &str) {
851 let font = FontRef::new(font_data).unwrap();
852 let expected_outlines = testing::parse_glyph_outlines(expected_outlines);
853 let mut path = testing::Path::default();
854 for expected_outline in &expected_outlines {
855 if expected_outline.size == 0.0 && !expected_outline.coords.is_empty() {
856 continue;
857 }
858 let size = if expected_outline.size != 0.0 {
859 Size::new(expected_outline.size)
860 } else {
861 Size::unscaled()
862 };
863 path.elements.clear();
864 font.outline_glyphs()
865 .get(expected_outline.glyph_id)
866 .unwrap()
867 .draw(
868 DrawSettings::unhinted(size, expected_outline.coords.as_slice()),
869 &mut path,
870 )
871 .unwrap();
872 assert_eq!(path.elements, expected_outline.path, "mismatch in glyph path for id {} (size: {}, coords: {:?}): path: {:?} expected_path: {:?}",
873 expected_outline.glyph_id,
874 expected_outline.size,
875 expected_outline.coords,
876 &path.elements,
877 &expected_outline.path
878 );
879 }
880 }
881
882 #[derive(Copy, Clone, Debug, PartialEq)]
883 enum GlyphPoint {
884 On { x: f32, y: f32 },
885 Off { x: f32, y: f32 },
886 }
887
888 impl GlyphPoint {
889 fn implied_oncurve(&self, other: Self) -> Self {
890 let (x1, y1) = self.xy();
891 let (x2, y2) = other.xy();
892 Self::On {
893 x: (x1 + x2) / 2.0,
894 y: (y1 + y2) / 2.0,
895 }
896 }
897
898 fn xy(&self) -> (f32, f32) {
899 match self {
900 GlyphPoint::On { x, y } | GlyphPoint::Off { x, y } => (*x, *y),
901 }
902 }
903 }
904
905 #[derive(Debug)]
906 struct PointPen {
907 points: Vec<GlyphPoint>,
908 }
909
910 impl PointPen {
911 fn new() -> Self {
912 Self { points: Vec::new() }
913 }
914
915 fn into_points(self) -> Vec<GlyphPoint> {
916 self.points
917 }
918 }
919
920 impl OutlinePen for PointPen {
921 fn move_to(&mut self, x: f32, y: f32) {
922 self.points.push(GlyphPoint::On { x, y });
923 }
924
925 fn line_to(&mut self, x: f32, y: f32) {
926 self.points.push(GlyphPoint::On { x, y });
927 }
928
929 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
930 self.points.push(GlyphPoint::Off { x: cx0, y: cy0 });
931 self.points.push(GlyphPoint::On { x, y });
932 }
933
934 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
935 self.points.push(GlyphPoint::Off { x: cx0, y: cy0 });
936 self.points.push(GlyphPoint::Off { x: cx1, y: cy1 });
937 self.points.push(GlyphPoint::On { x, y });
938 }
939
940 fn close(&mut self) {
941 let np = self.points.len();
948 if np > 2
950 && self.points[0] == self.points[np - 1]
951 && matches!(
952 (self.points[0], self.points[np - 2]),
953 (GlyphPoint::On { .. }, GlyphPoint::Off { .. })
954 )
955 {
956 self.points.pop();
957 }
958 }
959 }
960
961 const STARTING_OFF_CURVE_POINTS: [GlyphPoint; 4] = [
962 GlyphPoint::Off { x: 278.0, y: 710.0 },
963 GlyphPoint::On { x: 278.0, y: 470.0 },
964 GlyphPoint::On { x: 998.0, y: 470.0 },
965 GlyphPoint::On { x: 998.0, y: 710.0 },
966 ];
967
968 const MOSTLY_OFF_CURVE_POINTS: [GlyphPoint; 5] = [
969 GlyphPoint::Off { x: 278.0, y: 710.0 },
970 GlyphPoint::Off { x: 278.0, y: 470.0 },
971 GlyphPoint::On { x: 998.0, y: 470.0 },
972 GlyphPoint::Off { x: 998.0, y: 710.0 },
973 GlyphPoint::Off { x: 750.0, y: 500.0 },
974 ];
975
976 #[derive(Default, Debug)]
980 struct CommandPen {
981 commands: String,
982 }
983
984 impl OutlinePen for CommandPen {
985 fn move_to(&mut self, _x: f32, _y: f32) {
986 self.commands.push('M');
987 }
988
989 fn line_to(&mut self, _x: f32, _y: f32) {
990 self.commands.push('L');
991 }
992
993 fn quad_to(&mut self, _cx0: f32, _cy0: f32, _x: f32, _y: f32) {
994 self.commands.push('Q');
995 }
996
997 fn curve_to(&mut self, _cx0: f32, _cy0: f32, _cx1: f32, _cy1: f32, _x: f32, _y: f32) {
998 self.commands.push('C');
999 }
1000
1001 fn close(&mut self) {
1002 self.commands.push('Z');
1003 }
1004 }
1005
1006 fn draw_to_pen(font: &[u8], codepoint: u32, settings: DrawSettings, pen: &mut impl OutlinePen) {
1007 let font = FontRef::new(font).unwrap();
1008 let gid = font
1009 .cmap()
1010 .unwrap()
1011 .map_codepoint(codepoint)
1012 .unwrap_or_else(|| panic!("No gid for 0x{codepoint:04x}"));
1013 let outlines = font.outline_glyphs();
1014 let outline = outlines.get(gid).unwrap_or_else(|| {
1015 panic!(
1016 "No outline for {gid:?} in collection of {:?}",
1017 outlines.format()
1018 )
1019 });
1020
1021 outline.draw(settings, pen).unwrap();
1022 }
1023
1024 fn draw_commands(font: &[u8], codepoint: u32, settings: DrawSettings) -> String {
1025 let mut pen = CommandPen::default();
1026 draw_to_pen(font, codepoint, settings, &mut pen);
1027 pen.commands
1028 }
1029
1030 fn drawn_points(font: &[u8], codepoint: u32, settings: DrawSettings) -> Vec<GlyphPoint> {
1031 let mut pen = PointPen::new();
1032 draw_to_pen(font, codepoint, settings, &mut pen);
1033 pen.into_points()
1034 }
1035
1036 fn insert_implicit_oncurve(pointstream: &[GlyphPoint]) -> Vec<GlyphPoint> {
1037 let mut expanded_points = Vec::new();
1038
1039 for i in 0..pointstream.len() - 1 {
1040 expanded_points.push(pointstream[i]);
1041 if matches!(
1042 (pointstream[i], pointstream[i + 1]),
1043 (GlyphPoint::Off { .. }, GlyphPoint::Off { .. })
1044 ) {
1045 expanded_points.push(pointstream[i].implied_oncurve(pointstream[i + 1]));
1046 }
1047 }
1048
1049 expanded_points.push(*pointstream.last().unwrap());
1050
1051 expanded_points
1052 }
1053
1054 fn as_on_off_sequence(points: &[GlyphPoint]) -> Vec<&'static str> {
1055 points
1056 .iter()
1057 .map(|p| match p {
1058 GlyphPoint::On { .. } => "On",
1059 GlyphPoint::Off { .. } => "Off",
1060 })
1061 .collect()
1062 }
1063
1064 #[test]
1065 fn always_get_closing_lines() {
1066 let period = draw_commands(
1068 font_test_data::INTERPOLATE_THIS,
1069 PERIOD,
1070 Size::unscaled().into(),
1071 );
1072 let comma = draw_commands(
1073 font_test_data::INTERPOLATE_THIS,
1074 COMMA,
1075 Size::unscaled().into(),
1076 );
1077
1078 assert_eq!(
1079 period, comma,
1080 "Incompatible\nperiod\n{period:#?}\ncomma\n{comma:#?}\n"
1081 );
1082 assert_eq!(
1083 "MLLLZ", period,
1084 "We should get an explicit L for close even when it's a nop"
1085 );
1086 }
1087
1088 #[test]
1089 fn triangle_and_square_retain_compatibility() {
1090 let period = drawn_points(
1092 font_test_data::INTERPOLATE_THIS,
1093 PERIOD,
1094 Size::unscaled().into(),
1095 );
1096 let comma = drawn_points(
1097 font_test_data::INTERPOLATE_THIS,
1098 COMMA,
1099 Size::unscaled().into(),
1100 );
1101
1102 assert_ne!(period, comma);
1103 assert_eq!(
1104 as_on_off_sequence(&period),
1105 as_on_off_sequence(&comma),
1106 "Incompatible\nperiod\n{period:#?}\ncomma\n{comma:#?}\n"
1107 );
1108 assert_eq!(
1109 4,
1110 period.len(),
1111 "we should have the same # of points we started with"
1112 );
1113 }
1114
1115 fn assert_walked_backwards_like_freetype(pointstream: &[GlyphPoint], font: &[u8]) {
1116 assert!(
1117 matches!(pointstream[0], GlyphPoint::Off { .. }),
1118 "Bad testdata, should start off curve"
1119 );
1120
1121 let mut expected_points = pointstream.to_vec();
1123 let last = *expected_points.last().unwrap();
1124 let first_move = if matches!(last, GlyphPoint::Off { .. }) {
1125 expected_points[0].implied_oncurve(last)
1126 } else {
1127 expected_points.pop().unwrap()
1128 };
1129 expected_points.insert(0, first_move);
1130
1131 expected_points = insert_implicit_oncurve(&expected_points);
1132 let actual = drawn_points(font, PERIOD, Size::unscaled().into());
1133 assert_eq!(
1134 expected_points, actual,
1135 "expected\n{expected_points:#?}\nactual\n{actual:#?}"
1136 );
1137 }
1138
1139 fn assert_walked_forwards_like_harfbuzz(pointstream: &[GlyphPoint], font: &[u8]) {
1140 assert!(
1141 matches!(pointstream[0], GlyphPoint::Off { .. }),
1142 "Bad testdata, should start off curve"
1143 );
1144
1145 let mut expected_points = pointstream.to_vec();
1147 let first = expected_points.remove(0);
1148 expected_points.push(first);
1149 if matches!(expected_points[0], GlyphPoint::Off { .. }) {
1150 expected_points.insert(0, first.implied_oncurve(expected_points[0]))
1151 };
1152
1153 expected_points = insert_implicit_oncurve(&expected_points);
1154
1155 let settings: DrawSettings = Size::unscaled().into();
1156 let settings = settings.with_path_style(PathStyle::HarfBuzz);
1157 let actual = drawn_points(font, PERIOD, settings);
1158 assert_eq!(
1159 expected_points, actual,
1160 "expected\n{expected_points:#?}\nactual\n{actual:#?}"
1161 );
1162 }
1163
1164 #[test]
1165 fn starting_off_curve_walk_backwards_like_freetype() {
1166 assert_walked_backwards_like_freetype(
1167 &STARTING_OFF_CURVE_POINTS,
1168 font_test_data::STARTING_OFF_CURVE,
1169 );
1170 }
1171
1172 #[test]
1173 fn mostly_off_curve_walk_backwards_like_freetype() {
1174 assert_walked_backwards_like_freetype(
1175 &MOSTLY_OFF_CURVE_POINTS,
1176 font_test_data::MOSTLY_OFF_CURVE,
1177 );
1178 }
1179
1180 #[test]
1181 fn starting_off_curve_walk_forwards_like_hbdraw() {
1182 assert_walked_forwards_like_harfbuzz(
1183 &STARTING_OFF_CURVE_POINTS,
1184 font_test_data::STARTING_OFF_CURVE,
1185 );
1186 }
1187
1188 #[test]
1189 fn mostly_off_curve_walk_forwards_like_hbdraw() {
1190 assert_walked_forwards_like_harfbuzz(
1191 &MOSTLY_OFF_CURVE_POINTS,
1192 font_test_data::MOSTLY_OFF_CURVE,
1193 );
1194 }
1195
1196 fn icon_loc_off_default(font: &FontRef) -> Location {
1199 font.axes().location(&[
1200 ("wght", 700.0),
1201 ("opsz", 48.0),
1202 ("GRAD", 200.0),
1203 ("FILL", 1.0),
1204 ])
1205 }
1206
1207 fn pt(x: f32, y: f32) -> Point {
1208 (x as f64, y as f64).into()
1209 }
1210
1211 fn svg_commands(elements: &[PathEl]) -> Vec<String> {
1213 elements
1214 .iter()
1215 .map(|e| match e {
1216 PathEl::MoveTo(p) => format!("M{:.2},{:.2}", p.x, p.y),
1217 PathEl::LineTo(p) => format!("L{:.2},{:.2}", p.x, p.y),
1218 PathEl::QuadTo(c0, p) => format!("Q{:.2},{:.2} {:.2},{:.2}", c0.x, c0.y, p.x, p.y),
1219 PathEl::CurveTo(c0, c1, p) => format!(
1220 "C{:.2},{:.2} {:.2},{:.2} {:.2},{:.2}",
1221 c0.x, c0.y, c1.x, c1.y, p.x, p.y
1222 ),
1223 PathEl::ClosePath => "Z".to_string(),
1224 })
1225 .collect()
1226 }
1227
1228 #[derive(Default)]
1230 struct BezPen {
1231 path: BezPath,
1232 }
1233
1234 impl OutlinePen for BezPen {
1235 fn move_to(&mut self, x: f32, y: f32) {
1236 self.path.move_to(pt(x, y));
1237 }
1238
1239 fn line_to(&mut self, x: f32, y: f32) {
1240 self.path.line_to(pt(x, y));
1241 }
1242
1243 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
1244 self.path.quad_to(pt(cx0, cy0), pt(x, y));
1245 }
1246
1247 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
1248 self.path.curve_to(pt(cx0, cy0), pt(cx1, cy1), pt(x, y));
1249 }
1250
1251 fn close(&mut self) {
1252 self.path.close_path();
1253 }
1254 }
1255
1256 fn assert_glyph_path_start_with(
1258 font: &FontRef,
1259 gid: GlyphId,
1260 loc: Location,
1261 path_style: PathStyle,
1262 expected_path_start: &[PathEl],
1263 ) {
1264 let glyph = font
1265 .outline_glyphs()
1266 .get(gid)
1267 .unwrap_or_else(|| panic!("No glyph for {gid}"));
1268
1269 let mut pen = BezPen::default();
1270 glyph
1271 .draw(
1272 DrawSettings::unhinted(Size::unscaled(), &loc).with_path_style(path_style),
1273 &mut pen,
1274 )
1275 .unwrap_or_else(|e| panic!("Unable to draw {gid}: {e}"));
1276 let bez = Affine::FLIP_Y * pen.path; let actual_path_start = &bez.elements()[..expected_path_start.len()];
1278 assert_eq!(
1281 svg_commands(expected_path_start),
1282 svg_commands(actual_path_start)
1283 );
1284 }
1285
1286 const MATERIAL_SYMBOL_GID_MAIL_AT_DEFAULT: GlyphId = GlyphId::new(1);
1287 const MATERIAL_SYMBOL_GID_MAIL_OFF_DEFAULT: GlyphId = GlyphId::new(2);
1288
1289 #[test]
1290 fn draw_icon_freetype_style_at_default() {
1291 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1292 assert_glyph_path_start_with(
1293 &font,
1294 MATERIAL_SYMBOL_GID_MAIL_AT_DEFAULT,
1295 Location::default(),
1296 PathStyle::FreeType,
1297 &[
1298 PathEl::MoveTo((160.0, -160.0).into()),
1299 PathEl::QuadTo((127.0, -160.0).into(), (103.5, -183.5).into()),
1300 PathEl::QuadTo((80.0, -207.0).into(), (80.0, -240.0).into()),
1301 ],
1302 );
1303 }
1304
1305 #[test]
1306 fn draw_icon_harfbuzz_style_at_default() {
1307 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1308 assert_glyph_path_start_with(
1309 &font,
1310 MATERIAL_SYMBOL_GID_MAIL_AT_DEFAULT,
1311 Location::default(),
1312 PathStyle::HarfBuzz,
1313 &[
1314 PathEl::MoveTo((160.0, -160.0).into()),
1315 PathEl::QuadTo((127.0, -160.0).into(), (103.5, -183.5).into()),
1316 PathEl::QuadTo((80.0, -207.0).into(), (80.0, -240.0).into()),
1317 ],
1318 );
1319 }
1320
1321 #[test]
1322 fn draw_icon_freetype_style_off_default() {
1323 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1324 assert_glyph_path_start_with(
1325 &font,
1326 MATERIAL_SYMBOL_GID_MAIL_OFF_DEFAULT,
1327 icon_loc_off_default(&font),
1328 PathStyle::FreeType,
1329 &[
1330 PathEl::MoveTo((150.0, -138.0).into()),
1331 PathEl::QuadTo((113.0, -138.0).into(), (86.0, -165.5).into()),
1332 PathEl::QuadTo((59.0, -193.0).into(), (59.0, -229.0).into()),
1333 ],
1334 );
1335 }
1336
1337 #[test]
1338 fn draw_icon_harfbuzz_style_off_default() {
1339 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1340 assert_glyph_path_start_with(
1341 &font,
1342 MATERIAL_SYMBOL_GID_MAIL_OFF_DEFAULT,
1343 icon_loc_off_default(&font),
1344 PathStyle::HarfBuzz,
1345 &[
1346 PathEl::MoveTo((150.0, -138.0).into()),
1347 PathEl::QuadTo((113.22, -138.0).into(), (86.11, -165.61).into()),
1348 PathEl::QuadTo((59.0, -193.22).into(), (59.0, -229.0).into()),
1349 ],
1350 );
1351 }
1352
1353 const GLYF_COMPONENT_GID_NON_UNIFORM_SCALE: GlyphId = GlyphId::new(3);
1354 const GLYF_COMPONENT_GID_SCALED_COMPONENT_OFFSET: GlyphId = GlyphId::new(7);
1355 const GLYF_COMPONENT_GID_NO_SCALED_COMPONENT_OFFSET: GlyphId = GlyphId::new(8);
1356
1357 #[test]
1358 fn draw_nonuniform_scale_component_freetype() {
1359 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1360 assert_glyph_path_start_with(
1361 &font,
1362 GLYF_COMPONENT_GID_NON_UNIFORM_SCALE,
1363 Location::default(),
1364 PathStyle::FreeType,
1365 &[
1366 PathEl::MoveTo((-138.0, -185.0).into()),
1367 PathEl::LineTo((-32.0, -259.0).into()),
1368 PathEl::LineTo((26.0, -175.0).into()),
1369 PathEl::LineTo((-80.0, -101.0).into()),
1370 PathEl::ClosePath,
1371 ],
1372 );
1373 }
1374
1375 #[test]
1376 fn draw_nonuniform_scale_component_harfbuzz() {
1377 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1378 assert_glyph_path_start_with(
1379 &font,
1380 GLYF_COMPONENT_GID_NON_UNIFORM_SCALE,
1381 Location::default(),
1382 PathStyle::HarfBuzz,
1383 &[
1384 PathEl::MoveTo((-137.8, -184.86).into()),
1385 PathEl::LineTo((-32.15, -258.52).into()),
1386 PathEl::LineTo((25.9, -175.24).into()),
1387 PathEl::LineTo((-79.75, -101.58).into()),
1388 PathEl::ClosePath,
1389 ],
1390 );
1391 }
1392
1393 #[test]
1394 fn draw_scaled_component_offset_freetype() {
1395 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1396 assert_glyph_path_start_with(
1397 &font,
1398 GLYF_COMPONENT_GID_SCALED_COMPONENT_OFFSET,
1399 Location::default(),
1400 PathStyle::FreeType,
1401 &[
1402 PathEl::MoveTo((715.0, -360.0).into()),
1404 ],
1405 );
1406 }
1407
1408 #[test]
1409 fn draw_no_scaled_component_offset_freetype() {
1410 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1411 assert_glyph_path_start_with(
1412 &font,
1413 GLYF_COMPONENT_GID_NO_SCALED_COMPONENT_OFFSET,
1414 Location::default(),
1415 PathStyle::FreeType,
1416 &[PathEl::MoveTo((705.0, -340.0).into())],
1417 );
1418 }
1419
1420 #[test]
1421 fn draw_scaled_component_offset_harfbuzz() {
1422 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1423 assert_glyph_path_start_with(
1424 &font,
1425 GLYF_COMPONENT_GID_SCALED_COMPONENT_OFFSET,
1426 Location::default(),
1427 PathStyle::HarfBuzz,
1428 &[
1429 PathEl::MoveTo((714.97, -360.0).into()),
1431 ],
1432 );
1433 }
1434
1435 #[test]
1436 fn draw_no_scaled_component_offset_harfbuzz() {
1437 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1438 assert_glyph_path_start_with(
1439 &font,
1440 GLYF_COMPONENT_GID_NO_SCALED_COMPONENT_OFFSET,
1441 Location::default(),
1442 PathStyle::HarfBuzz,
1443 &[PathEl::MoveTo((704.97, -340.0).into())],
1444 );
1445 }
1446
1447 #[cfg(feature = "spec_next")]
1448 const CUBIC_GLYPH: GlyphId = GlyphId::new(2);
1449
1450 #[test]
1451 #[cfg(feature = "spec_next")]
1452 fn draw_cubic() {
1453 let font = FontRef::new(font_test_data::CUBIC_GLYF).unwrap();
1454 assert_glyph_path_start_with(
1455 &font,
1456 CUBIC_GLYPH,
1457 Location::default(),
1458 PathStyle::FreeType,
1459 &[
1460 PathEl::MoveTo((278.0, -710.0).into()),
1461 PathEl::LineTo((278.0, -470.0).into()),
1462 PathEl::CurveTo(
1463 (300.0, -500.0).into(),
1464 (800.0, -500.0).into(),
1465 (998.0, -470.0).into(),
1466 ),
1467 PathEl::LineTo((998.0, -710.0).into()),
1468 ],
1469 );
1470 }
1471
1472 #[test]
1476 fn tthint_with_subset() {
1477 let font = FontRef::new(font_test_data::TTHINT_SUBSET).unwrap();
1478 let glyphs = font.outline_glyphs();
1479 let hinting = HintingInstance::new(
1480 &glyphs,
1481 Size::new(16.0),
1482 LocationRef::default(),
1483 HintingOptions::default(),
1484 )
1485 .unwrap();
1486 let glyph = glyphs.get(GlyphId::new(1)).unwrap();
1487 glyph
1489 .draw(DrawSettings::hinted(&hinting, true), &mut BezPen::default())
1490 .unwrap();
1491 }
1492
1493 #[test]
1494 fn empty_glyph_advance_unhinted() {
1495 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
1496 let outlines = font.outline_glyphs();
1497 let coords = [NormalizedCoord::from_f32(0.5)];
1498 let gid = font.charmap().map(' ').unwrap();
1499 let outline = outlines.get(gid).unwrap();
1500 let advance = outline
1501 .draw(
1502 (Size::new(24.0), LocationRef::new(&coords)),
1503 &mut super::pen::NullPen,
1504 )
1505 .unwrap()
1506 .advance_width
1507 .unwrap();
1508 assert_eq!(advance, 10.796875);
1509 }
1510
1511 #[test]
1512 fn empty_glyph_advance_hinted() {
1513 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
1514 let outlines = font.outline_glyphs();
1515 let coords = [NormalizedCoord::from_f32(0.5)];
1516 let hinter = HintingInstance::new(
1517 &outlines,
1518 Size::new(24.0),
1519 LocationRef::new(&coords),
1520 HintingOptions::default(),
1521 )
1522 .unwrap();
1523 let gid = font.charmap().map(' ').unwrap();
1524 let outline = outlines.get(gid).unwrap();
1525 let advance = outline
1526 .draw(&hinter, &mut super::pen::NullPen)
1527 .unwrap()
1528 .advance_width
1529 .unwrap();
1530 assert_eq!(advance, 11.0);
1531 }
1532
1533 #[test]
1536 fn fractional_size_hinting_matters() {
1537 let font = FontRef::from_index(font_test_data::TINOS_SUBSET, 0).unwrap();
1538 let mut outlines = font.outline_glyphs();
1539 let instance = HintingInstance::new(
1540 &outlines,
1541 Size::new(24.8),
1542 LocationRef::default(),
1543 HintingOptions::default(),
1544 )
1545 .unwrap();
1546 let gid = GlyphId::new(2);
1547 let outline_with_fractional = {
1548 let OutlineCollectionKind::Glyf(glyf) = &mut outlines.kind else {
1549 panic!("this is definitely a TrueType font");
1550 };
1551 glyf.fractional_size_hinting = true;
1552 let mut pen = SvgPen::new();
1553 let outline = outlines.get(gid).unwrap();
1554 outline.draw(&instance, &mut pen).unwrap();
1555 pen.to_string()
1556 };
1557 let outline_without_fractional = {
1558 let OutlineCollectionKind::Glyf(glyf) = &mut outlines.kind else {
1559 panic!("this is definitely a TrueType font");
1560 };
1561 glyf.fractional_size_hinting = false;
1562 let mut pen = SvgPen::new();
1563 let outline = outlines.get(gid).unwrap();
1564 outline.draw(&instance, &mut pen).unwrap();
1565 pen.to_string()
1566 };
1567 assert_ne!(outline_with_fractional, outline_without_fractional);
1568 }
1569
1570 #[test]
1571 fn cff2_advance_widths() {
1572 check_cff_hinted_and_unhinted_advance_widths(font_test_data::CANTARELL_VF_TRIMMED, true);
1573 }
1574
1575 #[test]
1576 fn cff_advance_widths() {
1577 check_cff_hinted_and_unhinted_advance_widths(
1578 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
1579 false,
1580 );
1581 }
1582
1583 #[track_caller]
1584 fn check_cff_hinted_and_unhinted_advance_widths(font_data: &[u8], is_cff2: bool) {
1585 let font = FontRef::new(font_data).unwrap();
1586 let outlines = font.outline_glyphs();
1587 let OutlineCollectionKind::Cff(cff_outlines) = &outlines.kind else {
1588 panic!("this should be a CFF outline collection");
1589 };
1590 assert_eq!(cff_outlines.is_cff2(), is_cff2);
1591 let size = Size::new(16.0);
1592 let hinter = HintingInstance::new(
1593 &outlines,
1594 size,
1595 LocationRef::default(),
1596 HintingOptions::default(),
1597 )
1598 .unwrap();
1599 let num_glyphs = font.maxp().unwrap().num_glyphs() as u32;
1600 assert!(num_glyphs > 0);
1601 for gid in 0..num_glyphs {
1602 let outline = outlines.get(gid.into()).unwrap();
1603 let hinted = outline
1604 .draw(&hinter, &mut super::pen::NullPen)
1605 .unwrap()
1606 .advance_width
1607 .unwrap();
1608 let unhinted = outline
1609 .draw(
1610 DrawSettings::unhinted(size, LocationRef::default()),
1611 &mut super::pen::NullPen,
1612 )
1613 .unwrap()
1614 .advance_width
1615 .unwrap();
1616 assert_eq!(hinted.round(), hinted);
1617 assert!(
1618 (hinted - unhinted).abs() <= 0.5,
1619 "advance widths should be close, got hinted={hinted}, unhinted={unhinted} for gid {gid}"
1620 );
1621 }
1622 }
1623}