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.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 raw::model::pen::NullPen;
780 use read_fonts::{types::GlyphId, FontRef, TableProvider};
781
782 use pretty_assertions::assert_eq;
783
784 const PERIOD: u32 = 0x2E_u32;
785 const COMMA: u32 = 0x2C_u32;
786
787 #[test]
788 fn outline_glyph_formats() {
789 let font_format_pairs = [
790 (font_test_data::VAZIRMATN_VAR, OutlineGlyphFormat::Glyf),
791 (
792 font_test_data::CANTARELL_VF_TRIMMED,
793 OutlineGlyphFormat::Cff2,
794 ),
795 (
796 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
797 OutlineGlyphFormat::Cff,
798 ),
799 (font_test_data::COLRV0V1_VARIABLE, OutlineGlyphFormat::Glyf),
800 ];
801 for (font_data, format) in font_format_pairs {
802 assert_eq!(
803 FontRef::new(font_data).unwrap().outline_glyphs().format(),
804 Some(format)
805 );
806 }
807 }
808
809 #[test]
810 fn vazirmatin_var() {
811 compare_glyphs(
812 font_test_data::VAZIRMATN_VAR,
813 font_test_data::VAZIRMATN_VAR_GLYPHS,
814 );
815 }
816
817 #[test]
818 fn cantarell_vf() {
819 compare_glyphs(
820 font_test_data::CANTARELL_VF_TRIMMED,
821 font_test_data::CANTARELL_VF_TRIMMED_GLYPHS,
822 );
823 }
824
825 #[test]
826 fn noto_serif_display() {
827 compare_glyphs(
828 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
829 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED_GLYPHS,
830 );
831 }
832
833 #[test]
834 fn overlap_flags() {
835 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
836 let outlines = font.outline_glyphs();
837 let glyph_count = font.maxp().unwrap().num_glyphs();
838 let expected_gids_with_overlap = vec![2, 3];
841 assert_eq!(
842 expected_gids_with_overlap,
843 (0..glyph_count)
844 .filter(
845 |gid| outlines.get(GlyphId::from(*gid)).unwrap().has_overlaps() == Some(true)
846 )
847 .collect::<Vec<_>>()
848 );
849 }
850
851 fn compare_glyphs(font_data: &[u8], expected_outlines: &str) {
852 let font = FontRef::new(font_data).unwrap();
853 let expected_outlines = testing::parse_glyph_outlines(expected_outlines);
854 let mut path = testing::Path::default();
855 for expected_outline in &expected_outlines {
856 if expected_outline.size == 0.0 && !expected_outline.coords.is_empty() {
857 continue;
858 }
859 let size = if expected_outline.size != 0.0 {
860 Size::new(expected_outline.size)
861 } else {
862 Size::unscaled()
863 };
864 path.elements.clear();
865 font.outline_glyphs()
866 .get(expected_outline.glyph_id)
867 .unwrap()
868 .draw(
869 DrawSettings::unhinted(size, expected_outline.coords.as_slice()),
870 &mut path,
871 )
872 .unwrap();
873 assert_eq!(path.elements, expected_outline.path, "mismatch in glyph path for id {} (size: {}, coords: {:?}): path: {:?} expected_path: {:?}",
874 expected_outline.glyph_id,
875 expected_outline.size,
876 expected_outline.coords,
877 &path.elements,
878 &expected_outline.path
879 );
880 }
881 }
882
883 #[derive(Copy, Clone, Debug, PartialEq)]
884 enum GlyphPoint {
885 On { x: f32, y: f32 },
886 Off { x: f32, y: f32 },
887 }
888
889 impl GlyphPoint {
890 fn implied_oncurve(&self, other: Self) -> Self {
891 let (x1, y1) = self.xy();
892 let (x2, y2) = other.xy();
893 Self::On {
894 x: (x1 + x2) / 2.0,
895 y: (y1 + y2) / 2.0,
896 }
897 }
898
899 fn xy(&self) -> (f32, f32) {
900 match self {
901 GlyphPoint::On { x, y } | GlyphPoint::Off { x, y } => (*x, *y),
902 }
903 }
904 }
905
906 #[derive(Debug)]
907 struct PointPen {
908 points: Vec<GlyphPoint>,
909 }
910
911 impl PointPen {
912 fn new() -> Self {
913 Self { points: Vec::new() }
914 }
915
916 fn into_points(self) -> Vec<GlyphPoint> {
917 self.points
918 }
919 }
920
921 impl OutlinePen for PointPen {
922 fn move_to(&mut self, x: f32, y: f32) {
923 self.points.push(GlyphPoint::On { x, y });
924 }
925
926 fn line_to(&mut self, x: f32, y: f32) {
927 self.points.push(GlyphPoint::On { x, y });
928 }
929
930 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
931 self.points.push(GlyphPoint::Off { x: cx0, y: cy0 });
932 self.points.push(GlyphPoint::On { x, y });
933 }
934
935 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
936 self.points.push(GlyphPoint::Off { x: cx0, y: cy0 });
937 self.points.push(GlyphPoint::Off { x: cx1, y: cy1 });
938 self.points.push(GlyphPoint::On { x, y });
939 }
940
941 fn close(&mut self) {
942 let np = self.points.len();
949 if np > 2
951 && self.points[0] == self.points[np - 1]
952 && matches!(
953 (self.points[0], self.points[np - 2]),
954 (GlyphPoint::On { .. }, GlyphPoint::Off { .. })
955 )
956 {
957 self.points.pop();
958 }
959 }
960 }
961
962 const STARTING_OFF_CURVE_POINTS: [GlyphPoint; 4] = [
963 GlyphPoint::Off { x: 278.0, y: 710.0 },
964 GlyphPoint::On { x: 278.0, y: 470.0 },
965 GlyphPoint::On { x: 998.0, y: 470.0 },
966 GlyphPoint::On { x: 998.0, y: 710.0 },
967 ];
968
969 const MOSTLY_OFF_CURVE_POINTS: [GlyphPoint; 5] = [
970 GlyphPoint::Off { x: 278.0, y: 710.0 },
971 GlyphPoint::Off { x: 278.0, y: 470.0 },
972 GlyphPoint::On { x: 998.0, y: 470.0 },
973 GlyphPoint::Off { x: 998.0, y: 710.0 },
974 GlyphPoint::Off { x: 750.0, y: 500.0 },
975 ];
976
977 #[derive(Default, Debug)]
981 struct CommandPen {
982 commands: String,
983 }
984
985 impl OutlinePen for CommandPen {
986 fn move_to(&mut self, _x: f32, _y: f32) {
987 self.commands.push('M');
988 }
989
990 fn line_to(&mut self, _x: f32, _y: f32) {
991 self.commands.push('L');
992 }
993
994 fn quad_to(&mut self, _cx0: f32, _cy0: f32, _x: f32, _y: f32) {
995 self.commands.push('Q');
996 }
997
998 fn curve_to(&mut self, _cx0: f32, _cy0: f32, _cx1: f32, _cy1: f32, _x: f32, _y: f32) {
999 self.commands.push('C');
1000 }
1001
1002 fn close(&mut self) {
1003 self.commands.push('Z');
1004 }
1005 }
1006
1007 fn draw_to_pen(font: &[u8], codepoint: u32, settings: DrawSettings, pen: &mut impl OutlinePen) {
1008 let font = FontRef::new(font).unwrap();
1009 let gid = font
1010 .cmap()
1011 .unwrap()
1012 .map_codepoint(codepoint)
1013 .unwrap_or_else(|| panic!("No gid for 0x{codepoint:04x}"));
1014 let outlines = font.outline_glyphs();
1015 let outline = outlines.get(gid).unwrap_or_else(|| {
1016 panic!(
1017 "No outline for {gid:?} in collection of {:?}",
1018 outlines.format()
1019 )
1020 });
1021
1022 outline.draw(settings, pen).unwrap();
1023 }
1024
1025 fn draw_commands(font: &[u8], codepoint: u32, settings: DrawSettings) -> String {
1026 let mut pen = CommandPen::default();
1027 draw_to_pen(font, codepoint, settings, &mut pen);
1028 pen.commands
1029 }
1030
1031 fn drawn_points(font: &[u8], codepoint: u32, settings: DrawSettings) -> Vec<GlyphPoint> {
1032 let mut pen = PointPen::new();
1033 draw_to_pen(font, codepoint, settings, &mut pen);
1034 pen.into_points()
1035 }
1036
1037 fn insert_implicit_oncurve(pointstream: &[GlyphPoint]) -> Vec<GlyphPoint> {
1038 let mut expanded_points = Vec::new();
1039
1040 for i in 0..pointstream.len() - 1 {
1041 expanded_points.push(pointstream[i]);
1042 if matches!(
1043 (pointstream[i], pointstream[i + 1]),
1044 (GlyphPoint::Off { .. }, GlyphPoint::Off { .. })
1045 ) {
1046 expanded_points.push(pointstream[i].implied_oncurve(pointstream[i + 1]));
1047 }
1048 }
1049
1050 expanded_points.push(*pointstream.last().unwrap());
1051
1052 expanded_points
1053 }
1054
1055 fn as_on_off_sequence(points: &[GlyphPoint]) -> Vec<&'static str> {
1056 points
1057 .iter()
1058 .map(|p| match p {
1059 GlyphPoint::On { .. } => "On",
1060 GlyphPoint::Off { .. } => "Off",
1061 })
1062 .collect()
1063 }
1064
1065 #[test]
1066 fn always_get_closing_lines() {
1067 let period = draw_commands(
1069 font_test_data::INTERPOLATE_THIS,
1070 PERIOD,
1071 Size::unscaled().into(),
1072 );
1073 let comma = draw_commands(
1074 font_test_data::INTERPOLATE_THIS,
1075 COMMA,
1076 Size::unscaled().into(),
1077 );
1078
1079 assert_eq!(
1080 period, comma,
1081 "Incompatible\nperiod\n{period:#?}\ncomma\n{comma:#?}\n"
1082 );
1083 assert_eq!(
1084 "MLLLZ", period,
1085 "We should get an explicit L for close even when it's a nop"
1086 );
1087 }
1088
1089 #[test]
1090 fn triangle_and_square_retain_compatibility() {
1091 let period = drawn_points(
1093 font_test_data::INTERPOLATE_THIS,
1094 PERIOD,
1095 Size::unscaled().into(),
1096 );
1097 let comma = drawn_points(
1098 font_test_data::INTERPOLATE_THIS,
1099 COMMA,
1100 Size::unscaled().into(),
1101 );
1102
1103 assert_ne!(period, comma);
1104 assert_eq!(
1105 as_on_off_sequence(&period),
1106 as_on_off_sequence(&comma),
1107 "Incompatible\nperiod\n{period:#?}\ncomma\n{comma:#?}\n"
1108 );
1109 assert_eq!(
1110 4,
1111 period.len(),
1112 "we should have the same # of points we started with"
1113 );
1114 }
1115
1116 fn assert_walked_backwards_like_freetype(pointstream: &[GlyphPoint], font: &[u8]) {
1117 assert!(
1118 matches!(pointstream[0], GlyphPoint::Off { .. }),
1119 "Bad testdata, should start off curve"
1120 );
1121
1122 let mut expected_points = pointstream.to_vec();
1124 let last = *expected_points.last().unwrap();
1125 let first_move = if matches!(last, GlyphPoint::Off { .. }) {
1126 expected_points[0].implied_oncurve(last)
1127 } else {
1128 expected_points.pop().unwrap()
1129 };
1130 expected_points.insert(0, first_move);
1131
1132 expected_points = insert_implicit_oncurve(&expected_points);
1133 let actual = drawn_points(font, PERIOD, Size::unscaled().into());
1134 assert_eq!(
1135 expected_points, actual,
1136 "expected\n{expected_points:#?}\nactual\n{actual:#?}"
1137 );
1138 }
1139
1140 fn assert_walked_forwards_like_harfbuzz(pointstream: &[GlyphPoint], font: &[u8]) {
1141 assert!(
1142 matches!(pointstream[0], GlyphPoint::Off { .. }),
1143 "Bad testdata, should start off curve"
1144 );
1145
1146 let mut expected_points = pointstream.to_vec();
1148 let first = expected_points.remove(0);
1149 expected_points.push(first);
1150 if matches!(expected_points[0], GlyphPoint::Off { .. }) {
1151 expected_points.insert(0, first.implied_oncurve(expected_points[0]))
1152 };
1153
1154 expected_points = insert_implicit_oncurve(&expected_points);
1155
1156 let settings: DrawSettings = Size::unscaled().into();
1157 let settings = settings.with_path_style(PathStyle::HarfBuzz);
1158 let actual = drawn_points(font, PERIOD, settings);
1159 assert_eq!(
1160 expected_points, actual,
1161 "expected\n{expected_points:#?}\nactual\n{actual:#?}"
1162 );
1163 }
1164
1165 #[test]
1166 fn starting_off_curve_walk_backwards_like_freetype() {
1167 assert_walked_backwards_like_freetype(
1168 &STARTING_OFF_CURVE_POINTS,
1169 font_test_data::STARTING_OFF_CURVE,
1170 );
1171 }
1172
1173 #[test]
1174 fn mostly_off_curve_walk_backwards_like_freetype() {
1175 assert_walked_backwards_like_freetype(
1176 &MOSTLY_OFF_CURVE_POINTS,
1177 font_test_data::MOSTLY_OFF_CURVE,
1178 );
1179 }
1180
1181 #[test]
1182 fn starting_off_curve_walk_forwards_like_hbdraw() {
1183 assert_walked_forwards_like_harfbuzz(
1184 &STARTING_OFF_CURVE_POINTS,
1185 font_test_data::STARTING_OFF_CURVE,
1186 );
1187 }
1188
1189 #[test]
1190 fn mostly_off_curve_walk_forwards_like_hbdraw() {
1191 assert_walked_forwards_like_harfbuzz(
1192 &MOSTLY_OFF_CURVE_POINTS,
1193 font_test_data::MOSTLY_OFF_CURVE,
1194 );
1195 }
1196
1197 fn icon_loc_off_default(font: &FontRef) -> Location {
1200 font.axes().location(&[
1201 ("wght", 700.0),
1202 ("opsz", 48.0),
1203 ("GRAD", 200.0),
1204 ("FILL", 1.0),
1205 ])
1206 }
1207
1208 fn pt(x: f32, y: f32) -> Point {
1209 (x as f64, y as f64).into()
1210 }
1211
1212 fn svg_commands(elements: &[PathEl]) -> Vec<String> {
1214 elements
1215 .iter()
1216 .map(|e| match e {
1217 PathEl::MoveTo(p) => format!("M{:.2},{:.2}", p.x, p.y),
1218 PathEl::LineTo(p) => format!("L{:.2},{:.2}", p.x, p.y),
1219 PathEl::QuadTo(c0, p) => format!("Q{:.2},{:.2} {:.2},{:.2}", c0.x, c0.y, p.x, p.y),
1220 PathEl::CurveTo(c0, c1, p) => format!(
1221 "C{:.2},{:.2} {:.2},{:.2} {:.2},{:.2}",
1222 c0.x, c0.y, c1.x, c1.y, p.x, p.y
1223 ),
1224 PathEl::ClosePath => "Z".to_string(),
1225 })
1226 .collect()
1227 }
1228
1229 #[derive(Default)]
1231 struct BezPen {
1232 path: BezPath,
1233 }
1234
1235 impl OutlinePen for BezPen {
1236 fn move_to(&mut self, x: f32, y: f32) {
1237 self.path.move_to(pt(x, y));
1238 }
1239
1240 fn line_to(&mut self, x: f32, y: f32) {
1241 self.path.line_to(pt(x, y));
1242 }
1243
1244 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
1245 self.path.quad_to(pt(cx0, cy0), pt(x, y));
1246 }
1247
1248 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
1249 self.path.curve_to(pt(cx0, cy0), pt(cx1, cy1), pt(x, y));
1250 }
1251
1252 fn close(&mut self) {
1253 self.path.close_path();
1254 }
1255 }
1256
1257 fn assert_glyph_path_start_with(
1259 font: &FontRef,
1260 gid: GlyphId,
1261 loc: Location,
1262 path_style: PathStyle,
1263 expected_path_start: &[PathEl],
1264 ) {
1265 let glyph = font
1266 .outline_glyphs()
1267 .get(gid)
1268 .unwrap_or_else(|| panic!("No glyph for {gid}"));
1269
1270 let mut pen = BezPen::default();
1271 glyph
1272 .draw(
1273 DrawSettings::unhinted(Size::unscaled(), &loc).with_path_style(path_style),
1274 &mut pen,
1275 )
1276 .unwrap_or_else(|e| panic!("Unable to draw {gid}: {e}"));
1277 let bez = Affine::FLIP_Y * pen.path; let actual_path_start = &bez.elements()[..expected_path_start.len()];
1279 assert_eq!(
1282 svg_commands(expected_path_start),
1283 svg_commands(actual_path_start)
1284 );
1285 }
1286
1287 const MATERIAL_SYMBOL_GID_MAIL_AT_DEFAULT: GlyphId = GlyphId::new(1);
1288 const MATERIAL_SYMBOL_GID_MAIL_OFF_DEFAULT: GlyphId = GlyphId::new(2);
1289
1290 #[test]
1291 fn draw_icon_freetype_style_at_default() {
1292 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1293 assert_glyph_path_start_with(
1294 &font,
1295 MATERIAL_SYMBOL_GID_MAIL_AT_DEFAULT,
1296 Location::default(),
1297 PathStyle::FreeType,
1298 &[
1299 PathEl::MoveTo((160.0, -160.0).into()),
1300 PathEl::QuadTo((127.0, -160.0).into(), (103.5, -183.5).into()),
1301 PathEl::QuadTo((80.0, -207.0).into(), (80.0, -240.0).into()),
1302 ],
1303 );
1304 }
1305
1306 #[test]
1307 fn draw_icon_harfbuzz_style_at_default() {
1308 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1309 assert_glyph_path_start_with(
1310 &font,
1311 MATERIAL_SYMBOL_GID_MAIL_AT_DEFAULT,
1312 Location::default(),
1313 PathStyle::HarfBuzz,
1314 &[
1315 PathEl::MoveTo((160.0, -160.0).into()),
1316 PathEl::QuadTo((127.0, -160.0).into(), (103.5, -183.5).into()),
1317 PathEl::QuadTo((80.0, -207.0).into(), (80.0, -240.0).into()),
1318 ],
1319 );
1320 }
1321
1322 #[test]
1323 fn draw_icon_freetype_style_off_default() {
1324 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1325 assert_glyph_path_start_with(
1326 &font,
1327 MATERIAL_SYMBOL_GID_MAIL_OFF_DEFAULT,
1328 icon_loc_off_default(&font),
1329 PathStyle::FreeType,
1330 &[
1331 PathEl::MoveTo((150.0, -138.0).into()),
1332 PathEl::QuadTo((113.0, -138.0).into(), (86.0, -165.5).into()),
1333 PathEl::QuadTo((59.0, -193.0).into(), (59.0, -229.0).into()),
1334 ],
1335 );
1336 }
1337
1338 #[test]
1339 fn draw_icon_harfbuzz_style_off_default() {
1340 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1341 assert_glyph_path_start_with(
1342 &font,
1343 MATERIAL_SYMBOL_GID_MAIL_OFF_DEFAULT,
1344 icon_loc_off_default(&font),
1345 PathStyle::HarfBuzz,
1346 &[
1347 PathEl::MoveTo((150.0, -138.0).into()),
1348 PathEl::QuadTo((113.22, -138.0).into(), (86.11, -165.61).into()),
1349 PathEl::QuadTo((59.0, -193.22).into(), (59.0, -229.0).into()),
1350 ],
1351 );
1352 }
1353
1354 const GLYF_COMPONENT_GID_NON_UNIFORM_SCALE: GlyphId = GlyphId::new(3);
1355 const GLYF_COMPONENT_GID_SCALED_COMPONENT_OFFSET: GlyphId = GlyphId::new(7);
1356 const GLYF_COMPONENT_GID_NO_SCALED_COMPONENT_OFFSET: GlyphId = GlyphId::new(8);
1357
1358 #[test]
1359 fn draw_nonuniform_scale_component_freetype() {
1360 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1361 assert_glyph_path_start_with(
1362 &font,
1363 GLYF_COMPONENT_GID_NON_UNIFORM_SCALE,
1364 Location::default(),
1365 PathStyle::FreeType,
1366 &[
1367 PathEl::MoveTo((-138.0, -185.0).into()),
1368 PathEl::LineTo((-32.0, -259.0).into()),
1369 PathEl::LineTo((26.0, -175.0).into()),
1370 PathEl::LineTo((-80.0, -101.0).into()),
1371 PathEl::ClosePath,
1372 ],
1373 );
1374 }
1375
1376 #[test]
1377 fn draw_nonuniform_scale_component_harfbuzz() {
1378 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1379 assert_glyph_path_start_with(
1380 &font,
1381 GLYF_COMPONENT_GID_NON_UNIFORM_SCALE,
1382 Location::default(),
1383 PathStyle::HarfBuzz,
1384 &[
1385 PathEl::MoveTo((-137.8, -184.86).into()),
1386 PathEl::LineTo((-32.15, -258.52).into()),
1387 PathEl::LineTo((25.9, -175.24).into()),
1388 PathEl::LineTo((-79.75, -101.58).into()),
1389 PathEl::ClosePath,
1390 ],
1391 );
1392 }
1393
1394 #[test]
1395 fn draw_scaled_component_offset_freetype() {
1396 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1397 assert_glyph_path_start_with(
1398 &font,
1399 GLYF_COMPONENT_GID_SCALED_COMPONENT_OFFSET,
1400 Location::default(),
1401 PathStyle::FreeType,
1402 &[
1403 PathEl::MoveTo((715.0, -360.0).into()),
1405 ],
1406 );
1407 }
1408
1409 #[test]
1410 fn draw_no_scaled_component_offset_freetype() {
1411 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1412 assert_glyph_path_start_with(
1413 &font,
1414 GLYF_COMPONENT_GID_NO_SCALED_COMPONENT_OFFSET,
1415 Location::default(),
1416 PathStyle::FreeType,
1417 &[PathEl::MoveTo((705.0, -340.0).into())],
1418 );
1419 }
1420
1421 #[test]
1422 fn draw_scaled_component_offset_harfbuzz() {
1423 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1424 assert_glyph_path_start_with(
1425 &font,
1426 GLYF_COMPONENT_GID_SCALED_COMPONENT_OFFSET,
1427 Location::default(),
1428 PathStyle::HarfBuzz,
1429 &[
1430 PathEl::MoveTo((714.97, -360.0).into()),
1432 ],
1433 );
1434 }
1435
1436 #[test]
1437 fn draw_no_scaled_component_offset_harfbuzz() {
1438 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1439 assert_glyph_path_start_with(
1440 &font,
1441 GLYF_COMPONENT_GID_NO_SCALED_COMPONENT_OFFSET,
1442 Location::default(),
1443 PathStyle::HarfBuzz,
1444 &[PathEl::MoveTo((704.97, -340.0).into())],
1445 );
1446 }
1447
1448 #[cfg(feature = "spec_next")]
1449 const CUBIC_GLYPH: GlyphId = GlyphId::new(2);
1450
1451 #[test]
1452 #[cfg(feature = "spec_next")]
1453 fn draw_cubic() {
1454 let font = FontRef::new(font_test_data::CUBIC_GLYF).unwrap();
1455 assert_glyph_path_start_with(
1456 &font,
1457 CUBIC_GLYPH,
1458 Location::default(),
1459 PathStyle::FreeType,
1460 &[
1461 PathEl::MoveTo((278.0, -710.0).into()),
1462 PathEl::LineTo((278.0, -470.0).into()),
1463 PathEl::CurveTo(
1464 (300.0, -500.0).into(),
1465 (800.0, -500.0).into(),
1466 (998.0, -470.0).into(),
1467 ),
1468 PathEl::LineTo((998.0, -710.0).into()),
1469 ],
1470 );
1471 }
1472
1473 #[test]
1477 fn tthint_with_subset() {
1478 let font = FontRef::new(font_test_data::TTHINT_SUBSET).unwrap();
1479 let glyphs = font.outline_glyphs();
1480 let hinting = HintingInstance::new(
1481 &glyphs,
1482 Size::new(16.0),
1483 LocationRef::default(),
1484 HintingOptions::default(),
1485 )
1486 .unwrap();
1487 let glyph = glyphs.get(GlyphId::new(1)).unwrap();
1488 glyph
1490 .draw(DrawSettings::hinted(&hinting, true), &mut BezPen::default())
1491 .unwrap();
1492 }
1493
1494 #[test]
1495 fn empty_glyph_advance_unhinted() {
1496 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
1497 let outlines = font.outline_glyphs();
1498 let coords = [NormalizedCoord::from_f32(0.5)];
1499 let gid = font.charmap().map(' ').unwrap();
1500 let outline = outlines.get(gid).unwrap();
1501 let advance = outline
1502 .draw(
1503 (Size::new(24.0), LocationRef::new(&coords)),
1504 &mut super::pen::NullPen,
1505 )
1506 .unwrap()
1507 .advance_width
1508 .unwrap();
1509 assert_eq!(advance, 10.796875);
1510 }
1511
1512 #[test]
1513 fn empty_glyph_advance_hinted() {
1514 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
1515 let outlines = font.outline_glyphs();
1516 let coords = [NormalizedCoord::from_f32(0.5)];
1517 let hinter = HintingInstance::new(
1518 &outlines,
1519 Size::new(24.0),
1520 LocationRef::new(&coords),
1521 HintingOptions::default(),
1522 )
1523 .unwrap();
1524 let gid = font.charmap().map(' ').unwrap();
1525 let outline = outlines.get(gid).unwrap();
1526 let advance = outline
1527 .draw(&hinter, &mut super::pen::NullPen)
1528 .unwrap()
1529 .advance_width
1530 .unwrap();
1531 assert_eq!(advance, 11.0);
1532 }
1533
1534 #[test]
1537 fn fractional_size_hinting_matters() {
1538 let font = FontRef::from_index(font_test_data::TINOS_SUBSET, 0).unwrap();
1539 let mut outlines = font.outline_glyphs();
1540 let instance = HintingInstance::new(
1541 &outlines,
1542 Size::new(24.8),
1543 LocationRef::default(),
1544 HintingOptions::default(),
1545 )
1546 .unwrap();
1547 let gid = GlyphId::new(2);
1548 let outline_with_fractional = {
1549 let OutlineCollectionKind::Glyf(glyf) = &mut outlines.kind else {
1550 panic!("this is definitely a TrueType font");
1551 };
1552 glyf.fractional_size_hinting = true;
1553 let mut pen = SvgPen::new();
1554 let outline = outlines.get(gid).unwrap();
1555 outline.draw(&instance, &mut pen).unwrap();
1556 pen.to_string()
1557 };
1558 let outline_without_fractional = {
1559 let OutlineCollectionKind::Glyf(glyf) = &mut outlines.kind else {
1560 panic!("this is definitely a TrueType font");
1561 };
1562 glyf.fractional_size_hinting = false;
1563 let mut pen = SvgPen::new();
1564 let outline = outlines.get(gid).unwrap();
1565 outline.draw(&instance, &mut pen).unwrap();
1566 pen.to_string()
1567 };
1568 assert_ne!(outline_with_fractional, outline_without_fractional);
1569 }
1570
1571 #[test]
1572 fn cff2_advance_widths() {
1573 check_cff_hinted_and_unhinted_advance_widths(font_test_data::CANTARELL_VF_TRIMMED, true);
1574 }
1575
1576 #[test]
1577 fn cff_advance_widths() {
1578 check_cff_hinted_and_unhinted_advance_widths(
1579 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
1580 false,
1581 );
1582 }
1583
1584 #[track_caller]
1585 fn check_cff_hinted_and_unhinted_advance_widths(font_data: &[u8], is_cff2: bool) {
1586 let font = FontRef::new(font_data).unwrap();
1587 let outlines = font.outline_glyphs();
1588 let OutlineCollectionKind::Cff(cff_outlines) = &outlines.kind else {
1589 panic!("this should be a CFF outline collection");
1590 };
1591 assert_eq!(cff_outlines.is_cff2(), is_cff2);
1592 let size = Size::new(16.0);
1593 let hinter = HintingInstance::new(
1594 &outlines,
1595 size,
1596 LocationRef::default(),
1597 HintingOptions::default(),
1598 )
1599 .unwrap();
1600 let num_glyphs = font.maxp().unwrap().num_glyphs() as u32;
1601 assert!(num_glyphs > 0);
1602 for gid in 0..num_glyphs {
1603 let outline = outlines.get(gid.into()).unwrap();
1604 let hinted = outline
1605 .draw(&hinter, &mut super::pen::NullPen)
1606 .unwrap()
1607 .advance_width
1608 .unwrap();
1609 let unhinted = outline
1610 .draw(
1611 DrawSettings::unhinted(size, LocationRef::default()),
1612 &mut super::pen::NullPen,
1613 )
1614 .unwrap()
1615 .advance_width
1616 .unwrap();
1617 assert_eq!(hinted.round(), hinted);
1618 assert!(
1619 (hinted - unhinted).abs() <= 0.5,
1620 "advance widths should be close, got hinted={hinted}, unhinted={unhinted} for gid {gid}"
1621 );
1622 }
1623 }
1624
1625 #[test]
1626 fn autohint_completes_with_long_blues() {
1627 static FONT_DATA: [u8; 352] = [
1630 0, 1, 0, 0, 0, 7, 0, 64, 0, 2, 0, 48, 99, 109, 97, 112, 0, 12, 6, 4, 0, 0, 0, 124, 0,
1631 0, 0, 44, 103, 108, 121, 102, 51, 203, 248, 103, 0, 0, 0, 168, 0, 0, 0, 40, 104, 101,
1632 97, 100, 95, 16, 64, 222, 0, 0, 0, 208, 0, 0, 0, 54, 104, 104, 101, 97, 0, 1, 0, 1, 0,
1633 0, 1, 8, 0, 0, 0, 36, 104, 109, 116, 120, 0, 0, 0, 0, 0, 0, 1, 44, 0, 0, 0, 6, 108,
1634 111, 99, 97, 0, 0, 0, 40, 0, 0, 1, 52, 0, 0, 0, 12, 109, 97, 120, 112, 0, 4, 0, 5, 0,
1635 0, 1, 64, 0, 0, 0, 32, 0, 0, 0, 1, 0, 3, 0, 1, 0, 0, 0, 12, 0, 4, 0, 32, 0, 0, 0, 4, 0,
1636 4, 0, 1, 0, 0, 5, 209, 255, 255, 0, 0, 5, 209, 255, 255, 250, 48, 0, 1, 0, 0, 0, 0, 0,
1637 1, 0, 0, 254, 212, 0, 100, 1, 244, 0, 4, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 100, 0, 0,
1638 255, 206, 0, 0, 254, 212, 0, 0, 1, 44, 1, 244, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1639 95, 15, 60, 245, 0, 0, 3, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1640 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1641 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
1642 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 1, 0, 0, 0, 2, 0, 5, 0, 1, 0, 0, 0, 0,
1643 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1644 ];
1645 let font = FontRef::new(&FONT_DATA).unwrap();
1646 let outlines = font.outline_glyphs();
1647 let hinter = HintingInstance::new(
1648 &outlines,
1649 Size::new(16.0),
1650 LocationRef::default(),
1651 Engine::Auto(None),
1652 )
1653 .unwrap();
1654 let gid = font.charmap().map('\u{05D1}').unwrap();
1655 let outline = outlines.get(gid).unwrap();
1656 let (tx, rx) = std::sync::mpsc::channel();
1657 std::thread::spawn(move || {
1658 outline.draw(&hinter, &mut NullPen).unwrap();
1659 let _ = tx.send(());
1660 });
1661 let budget = std::time::Duration::from_secs(5);
1662 match rx.recv_timeout(budget) {
1663 Ok(_) => {}
1664 Err(_) => {
1665 panic!("Autohinting did not complete within {budget:?}");
1666 }
1667 }
1668 }
1669}