1mod autohint;
82mod cff;
83mod glyf;
84mod hint;
85mod hint_reliant;
86mod memory;
87mod metrics;
88mod path;
89mod unscaled;
90
91#[cfg(test)]
92mod testing;
93
94pub mod error;
95pub mod pen;
96
97pub use autohint::GlyphStyles;
98pub use hint::{
99 Engine, HintingInstance, HintingMode, HintingOptions, LcdLayout, SmoothMode, Target,
100};
101use metrics::GlyphHMetrics;
102use raw::FontRef;
103#[doc(inline)]
104pub use {error::DrawError, pen::OutlinePen};
105
106use self::glyf::{FreeTypeScaler, HarfBuzzScaler};
107use super::{
108 instance::{LocationRef, NormalizedCoord, Size},
109 GLYF_COMPOSITE_RECURSION_LIMIT,
110};
111use core::fmt::Debug;
112use pen::PathStyle;
113use read_fonts::{types::GlyphId, TableProvider};
114
115#[cfg(feature = "libm")]
116#[allow(unused_imports)]
117use core_maths::CoreFloat;
118
119#[derive(Copy, Clone, PartialEq, Eq, Debug)]
121pub enum OutlineGlyphFormat {
122 Glyf,
124 Cff,
126 Cff2,
128}
129
130#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
132pub enum Hinting {
133 #[default]
135 None,
136 Embedded,
142}
143
144#[derive(Copy, Clone, Default, Debug)]
150pub struct AdjustedMetrics {
151 pub has_overlaps: bool,
154 pub lsb: Option<f32>,
160 pub advance_width: Option<f32>,
166}
167
168pub struct DrawSettings<'a> {
171 instance: DrawInstance<'a>,
172 memory: Option<&'a mut [u8]>,
173 path_style: PathStyle,
174}
175
176impl<'a> DrawSettings<'a> {
177 pub fn unhinted(size: Size, location: impl Into<LocationRef<'a>>) -> Self {
180 Self {
181 instance: DrawInstance::Unhinted(size, location.into()),
182 memory: None,
183 path_style: PathStyle::default(),
184 }
185 }
186
187 pub fn hinted(instance: &'a HintingInstance, is_pedantic: bool) -> Self {
197 Self {
198 instance: DrawInstance::Hinted {
199 instance,
200 is_pedantic,
201 },
202 memory: None,
203 path_style: PathStyle::default(),
204 }
205 }
206
207 pub fn with_memory(mut self, memory: Option<&'a mut [u8]>) -> Self {
215 self.memory = memory;
216 self
217 }
218
219 pub fn with_path_style(mut self, path_style: PathStyle) -> Self {
223 self.path_style = path_style;
224 self
225 }
226}
227
228enum DrawInstance<'a> {
229 Unhinted(Size, LocationRef<'a>),
230 Hinted {
231 instance: &'a HintingInstance,
232 is_pedantic: bool,
233 },
234}
235
236impl<'a, L> From<(Size, L)> for DrawSettings<'a>
237where
238 L: Into<LocationRef<'a>>,
239{
240 fn from(value: (Size, L)) -> Self {
241 DrawSettings::unhinted(value.0, value.1.into())
242 }
243}
244
245impl From<Size> for DrawSettings<'_> {
246 fn from(value: Size) -> Self {
247 DrawSettings::unhinted(value, LocationRef::default())
248 }
249}
250
251impl<'a> From<&'a HintingInstance> for DrawSettings<'a> {
252 fn from(value: &'a HintingInstance) -> Self {
253 DrawSettings::hinted(value, false)
254 }
255}
256
257#[derive(Clone)]
265pub struct OutlineGlyph<'a> {
266 kind: OutlineKind<'a>,
267}
268
269impl<'a> OutlineGlyph<'a> {
270 pub fn format(&self) -> OutlineGlyphFormat {
272 match &self.kind {
273 OutlineKind::Glyf(..) => OutlineGlyphFormat::Glyf,
274 OutlineKind::Cff(cff, ..) => {
275 if cff.is_cff2() {
276 OutlineGlyphFormat::Cff2
277 } else {
278 OutlineGlyphFormat::Cff
279 }
280 }
281 }
282 }
283
284 pub fn glyph_id(&self) -> GlyphId {
286 match &self.kind {
287 OutlineKind::Glyf(_, glyph) => glyph.glyph_id,
288 OutlineKind::Cff(_, gid, _) => *gid,
289 }
290 }
291
292 pub fn has_overlaps(&self) -> Option<bool> {
297 match &self.kind {
298 OutlineKind::Glyf(_, outline) => Some(outline.has_overlaps),
299 _ => None,
300 }
301 }
302
303 pub fn has_hinting(&self) -> Option<bool> {
309 match &self.kind {
310 OutlineKind::Glyf(_, outline) => Some(outline.has_hinting),
311 _ => None,
312 }
313 }
314
315 pub fn draw_memory_size(&self, hinting: Hinting) -> usize {
331 match &self.kind {
332 OutlineKind::Glyf(_, outline) => outline.required_buffer_size(hinting),
333 _ => 0,
334 }
335 }
336
337 pub fn draw<'s>(
340 &self,
341 settings: impl Into<DrawSettings<'a>>,
342 pen: &mut impl OutlinePen,
343 ) -> Result<AdjustedMetrics, DrawError> {
344 let settings: DrawSettings<'a> = settings.into();
345 match (settings.instance, settings.path_style) {
346 (DrawInstance::Unhinted(size, location), PathStyle::FreeType) => {
347 self.draw_unhinted(size, location, settings.memory, settings.path_style, pen)
348 }
349 (DrawInstance::Unhinted(size, location), PathStyle::HarfBuzz) => {
350 self.draw_unhinted(size, location, settings.memory, settings.path_style, pen)
351 }
352 (
353 DrawInstance::Hinted {
354 instance: hinting_instance,
355 is_pedantic,
356 },
357 PathStyle::FreeType,
358 ) => {
359 if hinting_instance.is_enabled() {
360 hinting_instance.draw(
361 self,
362 settings.memory,
363 settings.path_style,
364 pen,
365 is_pedantic,
366 )
367 } else {
368 let mut metrics = self.draw_unhinted(
369 hinting_instance.size(),
370 hinting_instance.location(),
371 settings.memory,
372 settings.path_style,
373 pen,
374 )?;
375 if let Some(advance) = metrics.advance_width.as_mut() {
378 *advance = advance.round();
379 }
380 Ok(metrics)
381 }
382 }
383 (DrawInstance::Hinted { .. }, PathStyle::HarfBuzz) => {
384 Err(DrawError::HarfBuzzHintingUnsupported)
385 }
386 }
387 }
388
389 fn draw_unhinted(
390 &self,
391 size: Size,
392 location: impl Into<LocationRef<'a>>,
393 user_memory: Option<&mut [u8]>,
394 path_style: PathStyle,
395 pen: &mut impl OutlinePen,
396 ) -> Result<AdjustedMetrics, DrawError> {
397 let ppem = size.ppem();
398 let coords = location.into().effective_coords();
399 match &self.kind {
400 OutlineKind::Glyf(glyf, outline) => {
401 with_glyf_memory(outline, Hinting::None, user_memory, |buf| {
402 let (lsb, advance_width) = match path_style {
403 PathStyle::FreeType => {
404 let scaled_outline =
405 FreeTypeScaler::unhinted(glyf, outline, buf, ppem, coords)?
406 .scale(&outline.glyph, outline.glyph_id)?;
407 scaled_outline.to_path(path_style, pen)?;
408 (
409 scaled_outline.adjusted_lsb().to_f32(),
410 scaled_outline.adjusted_advance_width().to_f32(),
411 )
412 }
413 PathStyle::HarfBuzz => {
414 let scaled_outline =
415 HarfBuzzScaler::unhinted(glyf, outline, buf, ppem, coords)?
416 .scale(&outline.glyph, outline.glyph_id)?;
417 scaled_outline.to_path(path_style, pen)?;
418 (
419 scaled_outline.adjusted_lsb(),
420 scaled_outline.adjusted_advance_width(),
421 )
422 }
423 };
424
425 Ok(AdjustedMetrics {
426 has_overlaps: outline.has_overlaps,
427 lsb: Some(lsb),
428 advance_width: Some(advance_width),
429 })
430 })
431 }
432 OutlineKind::Cff(cff, glyph_id, subfont_ix) => {
433 let subfont = cff.subfont(*subfont_ix, ppem, coords)?;
434 cff.draw(&subfont, *glyph_id, coords, false, pen)?;
435 Ok(AdjustedMetrics::default())
436 }
437 }
438 }
439
440 #[allow(dead_code)]
443 fn draw_unscaled(
444 &self,
445 location: impl Into<LocationRef<'a>>,
446 user_memory: Option<&mut [u8]>,
447 sink: &mut impl unscaled::UnscaledOutlineSink,
448 ) -> Result<i32, DrawError> {
449 let coords = location.into().effective_coords();
450 let ppem = None;
451 match &self.kind {
452 OutlineKind::Glyf(glyf, outline) => {
453 with_glyf_memory(outline, Hinting::None, user_memory, |buf| {
454 let outline = FreeTypeScaler::unhinted(glyf, outline, buf, ppem, coords)?
455 .scale(&outline.glyph, outline.glyph_id)?;
456 sink.try_reserve(outline.points.len())?;
457 let mut contour_start = 0;
458 for contour_end in outline.contours.iter().map(|contour| *contour as usize) {
459 if contour_end >= contour_start {
460 if let Some(points) = outline.points.get(contour_start..=contour_end) {
461 let flags = &outline.flags[contour_start..=contour_end];
462 sink.extend(points.iter().zip(flags).enumerate().map(
463 |(ix, (point, flags))| {
464 unscaled::UnscaledPoint::from_glyf_point(
465 *point,
466 *flags,
467 ix == 0,
468 )
469 },
470 ))?;
471 }
472 }
473 contour_start = contour_end + 1;
474 }
475 Ok(outline.adjusted_advance_width().to_bits() >> 6)
476 })
477 }
478 OutlineKind::Cff(cff, glyph_id, subfont_ix) => {
479 let subfont = cff.subfont(*subfont_ix, ppem, coords)?;
480 let mut adapter = unscaled::UnscaledPenAdapter::new(sink);
481 cff.draw(&subfont, *glyph_id, coords, false, &mut adapter)?;
482 adapter.finish()?;
483 let advance = cff.glyph_metrics.advance_width(*glyph_id, coords);
484 Ok(advance)
485 }
486 }
487 }
488
489 pub(crate) fn font(&self) -> &FontRef<'a> {
490 match &self.kind {
491 OutlineKind::Glyf(glyf, ..) => &glyf.font,
492 OutlineKind::Cff(cff, ..) => &cff.font,
493 }
494 }
495
496 fn units_per_em(&self) -> u16 {
497 match &self.kind {
498 OutlineKind::Cff(cff, ..) => cff.units_per_em(),
499 OutlineKind::Glyf(glyf, ..) => glyf.units_per_em(),
500 }
501 }
502}
503
504#[derive(Clone)]
505enum OutlineKind<'a> {
506 Glyf(glyf::Outlines<'a>, glyf::Outline<'a>),
507 Cff(cff::Outlines<'a>, GlyphId, u32),
509}
510
511impl Debug for OutlineKind<'_> {
512 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
513 match self {
514 Self::Glyf(_, outline) => f.debug_tuple("Glyf").field(&outline.glyph_id).finish(),
515 Self::Cff(_, gid, subfont_index) => f
516 .debug_tuple("Cff")
517 .field(gid)
518 .field(subfont_index)
519 .finish(),
520 }
521 }
522}
523
524#[derive(Debug, Clone)]
526pub struct OutlineGlyphCollection<'a> {
527 kind: OutlineCollectionKind<'a>,
528}
529
530impl<'a> OutlineGlyphCollection<'a> {
531 pub fn new(font: &FontRef<'a>) -> Self {
533 let kind = if let Some(glyf) = glyf::Outlines::new(font) {
534 OutlineCollectionKind::Glyf(glyf)
535 } else if let Some(cff) = cff::Outlines::new(font) {
536 OutlineCollectionKind::Cff(cff)
537 } else {
538 OutlineCollectionKind::None
539 };
540 Self { kind }
541 }
542
543 pub fn with_format(font: &FontRef<'a>, format: OutlineGlyphFormat) -> Option<Self> {
549 let kind = match format {
550 OutlineGlyphFormat::Glyf => OutlineCollectionKind::Glyf(glyf::Outlines::new(font)?),
551 OutlineGlyphFormat::Cff => {
552 let upem = font.head().ok()?.units_per_em();
553 OutlineCollectionKind::Cff(cff::Outlines::from_cff(font, upem)?)
554 }
555 OutlineGlyphFormat::Cff2 => {
556 let upem = font.head().ok()?.units_per_em();
557 OutlineCollectionKind::Cff(cff::Outlines::from_cff2(font, upem)?)
558 }
559 };
560 Some(Self { kind })
561 }
562
563 pub fn format(&self) -> Option<OutlineGlyphFormat> {
565 match &self.kind {
566 OutlineCollectionKind::Glyf(..) => Some(OutlineGlyphFormat::Glyf),
567 OutlineCollectionKind::Cff(cff) => cff
568 .is_cff2()
569 .then_some(OutlineGlyphFormat::Cff2)
570 .or(Some(OutlineGlyphFormat::Cff)),
571 _ => None,
572 }
573 }
574
575 pub fn get(&self, glyph_id: GlyphId) -> Option<OutlineGlyph<'a>> {
577 match &self.kind {
578 OutlineCollectionKind::None => None,
579 OutlineCollectionKind::Glyf(glyf) => Some(OutlineGlyph {
580 kind: OutlineKind::Glyf(glyf.clone(), glyf.outline(glyph_id).ok()?),
581 }),
582 OutlineCollectionKind::Cff(cff) => Some(OutlineGlyph {
583 kind: OutlineKind::Cff(cff.clone(), glyph_id, cff.subfont_index(glyph_id)),
584 }),
585 }
586 }
587
588 pub fn iter(&self) -> impl Iterator<Item = (GlyphId, OutlineGlyph<'a>)> + 'a + Clone {
590 let len = match &self.kind {
591 OutlineCollectionKind::Glyf(glyf) => glyf.glyph_count(),
592 OutlineCollectionKind::Cff(cff) => cff.glyph_count(),
593 _ => 0,
594 } as u16;
595 let copy = self.clone();
596 (0..len).filter_map(move |gid| {
597 let gid = GlyphId::from(gid);
598 let glyph = copy.get(gid)?;
599 Some((gid, glyph))
600 })
601 }
602
603 pub fn prefer_interpreter(&self) -> bool {
617 match &self.kind {
618 OutlineCollectionKind::Glyf(glyf) => glyf.prefer_interpreter(),
619 _ => true,
620 }
621 }
622
623 pub fn require_interpreter(&self) -> bool {
639 self.font()
640 .map(|font| hint_reliant::require_interpreter(font))
641 .unwrap_or_default()
642 }
643
644 pub(crate) fn font(&self) -> Option<&FontRef<'a>> {
645 match &self.kind {
646 OutlineCollectionKind::Glyf(glyf) => Some(&glyf.font),
647 OutlineCollectionKind::Cff(cff) => Some(&cff.font),
648 _ => None,
649 }
650 }
651}
652
653#[derive(Clone)]
654enum OutlineCollectionKind<'a> {
655 None,
656 Glyf(glyf::Outlines<'a>),
657 Cff(cff::Outlines<'a>),
658}
659
660impl Debug for OutlineCollectionKind<'_> {
661 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
662 match self {
663 Self::None => write!(f, "None"),
664 Self::Glyf(..) => f.debug_tuple("Glyf").finish(),
665 Self::Cff(..) => f.debug_tuple("Cff").finish(),
666 }
667 }
668}
669
670pub(super) fn with_glyf_memory<R>(
673 outline: &glyf::Outline,
674 hinting: Hinting,
675 memory: Option<&mut [u8]>,
676 mut f: impl FnMut(&mut [u8]) -> R,
677) -> R {
678 match memory {
679 Some(buf) => f(buf),
680 None => {
681 let buf_size = outline.required_buffer_size(hinting);
682 memory::with_temporary_memory(buf_size, f)
683 }
684 }
685}
686
687#[cfg(test)]
688mod tests {
689 use super::*;
690 use crate::{instance::Location, MetadataProvider};
691 use kurbo::{Affine, BezPath, PathEl, Point};
692 use read_fonts::{types::GlyphId, FontRef, TableProvider};
693
694 use pretty_assertions::assert_eq;
695
696 const PERIOD: u32 = 0x2E_u32;
697 const COMMA: u32 = 0x2C_u32;
698
699 #[test]
700 fn outline_glyph_formats() {
701 let font_format_pairs = [
702 (font_test_data::VAZIRMATN_VAR, OutlineGlyphFormat::Glyf),
703 (
704 font_test_data::CANTARELL_VF_TRIMMED,
705 OutlineGlyphFormat::Cff2,
706 ),
707 (
708 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
709 OutlineGlyphFormat::Cff,
710 ),
711 (font_test_data::COLRV0V1_VARIABLE, OutlineGlyphFormat::Glyf),
712 ];
713 for (font_data, format) in font_format_pairs {
714 assert_eq!(
715 FontRef::new(font_data).unwrap().outline_glyphs().format(),
716 Some(format)
717 );
718 }
719 }
720
721 #[test]
722 fn vazirmatin_var() {
723 compare_glyphs(
724 font_test_data::VAZIRMATN_VAR,
725 font_test_data::VAZIRMATN_VAR_GLYPHS,
726 );
727 }
728
729 #[test]
730 fn cantarell_vf() {
731 compare_glyphs(
732 font_test_data::CANTARELL_VF_TRIMMED,
733 font_test_data::CANTARELL_VF_TRIMMED_GLYPHS,
734 );
735 }
736
737 #[test]
738 fn noto_serif_display() {
739 compare_glyphs(
740 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
741 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED_GLYPHS,
742 );
743 }
744
745 #[test]
746 fn overlap_flags() {
747 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
748 let outlines = font.outline_glyphs();
749 let glyph_count = font.maxp().unwrap().num_glyphs();
750 let expected_gids_with_overlap = vec![2, 3];
753 assert_eq!(
754 expected_gids_with_overlap,
755 (0..glyph_count)
756 .filter(
757 |gid| outlines.get(GlyphId::from(*gid)).unwrap().has_overlaps() == Some(true)
758 )
759 .collect::<Vec<_>>()
760 );
761 }
762
763 fn compare_glyphs(font_data: &[u8], expected_outlines: &str) {
764 let font = FontRef::new(font_data).unwrap();
765 let expected_outlines = testing::parse_glyph_outlines(expected_outlines);
766 let mut path = testing::Path::default();
767 for expected_outline in &expected_outlines {
768 if expected_outline.size == 0.0 && !expected_outline.coords.is_empty() {
769 continue;
770 }
771 let size = if expected_outline.size != 0.0 {
772 Size::new(expected_outline.size)
773 } else {
774 Size::unscaled()
775 };
776 path.elements.clear();
777 font.outline_glyphs()
778 .get(expected_outline.glyph_id)
779 .unwrap()
780 .draw(
781 DrawSettings::unhinted(size, expected_outline.coords.as_slice()),
782 &mut path,
783 )
784 .unwrap();
785 assert_eq!(path.elements, expected_outline.path, "mismatch in glyph path for id {} (size: {}, coords: {:?}): path: {:?} expected_path: {:?}",
786 expected_outline.glyph_id,
787 expected_outline.size,
788 expected_outline.coords,
789 &path.elements,
790 &expected_outline.path
791 );
792 }
793 }
794
795 #[derive(Copy, Clone, Debug, PartialEq)]
796 enum GlyphPoint {
797 On { x: f32, y: f32 },
798 Off { x: f32, y: f32 },
799 }
800
801 impl GlyphPoint {
802 fn implied_oncurve(&self, other: Self) -> Self {
803 let (x1, y1) = self.xy();
804 let (x2, y2) = other.xy();
805 Self::On {
806 x: (x1 + x2) / 2.0,
807 y: (y1 + y2) / 2.0,
808 }
809 }
810
811 fn xy(&self) -> (f32, f32) {
812 match self {
813 GlyphPoint::On { x, y } | GlyphPoint::Off { x, y } => (*x, *y),
814 }
815 }
816 }
817
818 #[derive(Debug)]
819 struct PointPen {
820 points: Vec<GlyphPoint>,
821 }
822
823 impl PointPen {
824 fn new() -> Self {
825 Self { points: Vec::new() }
826 }
827
828 fn into_points(self) -> Vec<GlyphPoint> {
829 self.points
830 }
831 }
832
833 impl OutlinePen for PointPen {
834 fn move_to(&mut self, x: f32, y: f32) {
835 self.points.push(GlyphPoint::On { x, y });
836 }
837
838 fn line_to(&mut self, x: f32, y: f32) {
839 self.points.push(GlyphPoint::On { x, y });
840 }
841
842 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
843 self.points.push(GlyphPoint::Off { x: cx0, y: cy0 });
844 self.points.push(GlyphPoint::On { x, y });
845 }
846
847 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
848 self.points.push(GlyphPoint::Off { x: cx0, y: cy0 });
849 self.points.push(GlyphPoint::Off { x: cx1, y: cy1 });
850 self.points.push(GlyphPoint::On { x, y });
851 }
852
853 fn close(&mut self) {
854 let np = self.points.len();
861 if np > 2
863 && self.points[0] == self.points[np - 1]
864 && matches!(
865 (self.points[0], self.points[np - 2]),
866 (GlyphPoint::On { .. }, GlyphPoint::Off { .. })
867 )
868 {
869 self.points.pop();
870 }
871 }
872 }
873
874 const STARTING_OFF_CURVE_POINTS: [GlyphPoint; 4] = [
875 GlyphPoint::Off { x: 278.0, y: 710.0 },
876 GlyphPoint::On { x: 278.0, y: 470.0 },
877 GlyphPoint::On { x: 998.0, y: 470.0 },
878 GlyphPoint::On { x: 998.0, y: 710.0 },
879 ];
880
881 const MOSTLY_OFF_CURVE_POINTS: [GlyphPoint; 5] = [
882 GlyphPoint::Off { x: 278.0, y: 710.0 },
883 GlyphPoint::Off { x: 278.0, y: 470.0 },
884 GlyphPoint::On { x: 998.0, y: 470.0 },
885 GlyphPoint::Off { x: 998.0, y: 710.0 },
886 GlyphPoint::Off { x: 750.0, y: 500.0 },
887 ];
888
889 #[derive(Default, Debug)]
893 struct CommandPen {
894 commands: String,
895 }
896
897 impl OutlinePen for CommandPen {
898 fn move_to(&mut self, _x: f32, _y: f32) {
899 self.commands.push('M');
900 }
901
902 fn line_to(&mut self, _x: f32, _y: f32) {
903 self.commands.push('L');
904 }
905
906 fn quad_to(&mut self, _cx0: f32, _cy0: f32, _x: f32, _y: f32) {
907 self.commands.push('Q');
908 }
909
910 fn curve_to(&mut self, _cx0: f32, _cy0: f32, _cx1: f32, _cy1: f32, _x: f32, _y: f32) {
911 self.commands.push('C');
912 }
913
914 fn close(&mut self) {
915 self.commands.push('Z');
916 }
917 }
918
919 fn draw_to_pen(font: &[u8], codepoint: u32, settings: DrawSettings, pen: &mut impl OutlinePen) {
920 let font = FontRef::new(font).unwrap();
921 let gid = font
922 .cmap()
923 .unwrap()
924 .map_codepoint(codepoint)
925 .unwrap_or_else(|| panic!("No gid for 0x{codepoint:04x}"));
926 let outlines = font.outline_glyphs();
927 let outline = outlines.get(gid).unwrap_or_else(|| {
928 panic!(
929 "No outline for {gid:?} in collection of {:?}",
930 outlines.format()
931 )
932 });
933
934 outline.draw(settings, pen).unwrap();
935 }
936
937 fn draw_commands(font: &[u8], codepoint: u32, settings: DrawSettings) -> String {
938 let mut pen = CommandPen::default();
939 draw_to_pen(font, codepoint, settings, &mut pen);
940 pen.commands
941 }
942
943 fn drawn_points(font: &[u8], codepoint: u32, settings: DrawSettings) -> Vec<GlyphPoint> {
944 let mut pen = PointPen::new();
945 draw_to_pen(font, codepoint, settings, &mut pen);
946 pen.into_points()
947 }
948
949 fn insert_implicit_oncurve(pointstream: &[GlyphPoint]) -> Vec<GlyphPoint> {
950 let mut expanded_points = Vec::new();
951
952 for i in 0..pointstream.len() - 1 {
953 expanded_points.push(pointstream[i]);
954 if matches!(
955 (pointstream[i], pointstream[i + 1]),
956 (GlyphPoint::Off { .. }, GlyphPoint::Off { .. })
957 ) {
958 expanded_points.push(pointstream[i].implied_oncurve(pointstream[i + 1]));
959 }
960 }
961
962 expanded_points.push(*pointstream.last().unwrap());
963
964 expanded_points
965 }
966
967 fn as_on_off_sequence(points: &[GlyphPoint]) -> Vec<&'static str> {
968 points
969 .iter()
970 .map(|p| match p {
971 GlyphPoint::On { .. } => "On",
972 GlyphPoint::Off { .. } => "Off",
973 })
974 .collect()
975 }
976
977 #[test]
978 fn always_get_closing_lines() {
979 let period = draw_commands(
981 font_test_data::INTERPOLATE_THIS,
982 PERIOD,
983 Size::unscaled().into(),
984 );
985 let comma = draw_commands(
986 font_test_data::INTERPOLATE_THIS,
987 COMMA,
988 Size::unscaled().into(),
989 );
990
991 assert_eq!(
992 period, comma,
993 "Incompatible\nperiod\n{period:#?}\ncomma\n{comma:#?}\n"
994 );
995 assert_eq!(
996 "MLLLZ", period,
997 "We should get an explicit L for close even when it's a nop"
998 );
999 }
1000
1001 #[test]
1002 fn triangle_and_square_retain_compatibility() {
1003 let period = drawn_points(
1005 font_test_data::INTERPOLATE_THIS,
1006 PERIOD,
1007 Size::unscaled().into(),
1008 );
1009 let comma = drawn_points(
1010 font_test_data::INTERPOLATE_THIS,
1011 COMMA,
1012 Size::unscaled().into(),
1013 );
1014
1015 assert_ne!(period, comma);
1016 assert_eq!(
1017 as_on_off_sequence(&period),
1018 as_on_off_sequence(&comma),
1019 "Incompatible\nperiod\n{period:#?}\ncomma\n{comma:#?}\n"
1020 );
1021 assert_eq!(
1022 4,
1023 period.len(),
1024 "we should have the same # of points we started with"
1025 );
1026 }
1027
1028 fn assert_walked_backwards_like_freetype(pointstream: &[GlyphPoint], font: &[u8]) {
1029 assert!(
1030 matches!(pointstream[0], GlyphPoint::Off { .. }),
1031 "Bad testdata, should start off curve"
1032 );
1033
1034 let mut expected_points = pointstream.to_vec();
1036 let last = *expected_points.last().unwrap();
1037 let first_move = if matches!(last, GlyphPoint::Off { .. }) {
1038 expected_points[0].implied_oncurve(last)
1039 } else {
1040 expected_points.pop().unwrap()
1041 };
1042 expected_points.insert(0, first_move);
1043
1044 expected_points = insert_implicit_oncurve(&expected_points);
1045 let actual = drawn_points(font, PERIOD, Size::unscaled().into());
1046 assert_eq!(
1047 expected_points, actual,
1048 "expected\n{expected_points:#?}\nactual\n{actual:#?}"
1049 );
1050 }
1051
1052 fn assert_walked_forwards_like_harfbuzz(pointstream: &[GlyphPoint], font: &[u8]) {
1053 assert!(
1054 matches!(pointstream[0], GlyphPoint::Off { .. }),
1055 "Bad testdata, should start off curve"
1056 );
1057
1058 let mut expected_points = pointstream.to_vec();
1060 let first = expected_points.remove(0);
1061 expected_points.push(first);
1062 if matches!(expected_points[0], GlyphPoint::Off { .. }) {
1063 expected_points.insert(0, first.implied_oncurve(expected_points[0]))
1064 };
1065
1066 expected_points = insert_implicit_oncurve(&expected_points);
1067
1068 let settings: DrawSettings = Size::unscaled().into();
1069 let settings = settings.with_path_style(PathStyle::HarfBuzz);
1070 let actual = drawn_points(font, PERIOD, settings);
1071 assert_eq!(
1072 expected_points, actual,
1073 "expected\n{expected_points:#?}\nactual\n{actual:#?}"
1074 );
1075 }
1076
1077 #[test]
1078 fn starting_off_curve_walk_backwards_like_freetype() {
1079 assert_walked_backwards_like_freetype(
1080 &STARTING_OFF_CURVE_POINTS,
1081 font_test_data::STARTING_OFF_CURVE,
1082 );
1083 }
1084
1085 #[test]
1086 fn mostly_off_curve_walk_backwards_like_freetype() {
1087 assert_walked_backwards_like_freetype(
1088 &MOSTLY_OFF_CURVE_POINTS,
1089 font_test_data::MOSTLY_OFF_CURVE,
1090 );
1091 }
1092
1093 #[test]
1094 fn starting_off_curve_walk_forwards_like_hbdraw() {
1095 assert_walked_forwards_like_harfbuzz(
1096 &STARTING_OFF_CURVE_POINTS,
1097 font_test_data::STARTING_OFF_CURVE,
1098 );
1099 }
1100
1101 #[test]
1102 fn mostly_off_curve_walk_forwards_like_hbdraw() {
1103 assert_walked_forwards_like_harfbuzz(
1104 &MOSTLY_OFF_CURVE_POINTS,
1105 font_test_data::MOSTLY_OFF_CURVE,
1106 );
1107 }
1108
1109 fn icon_loc_off_default(font: &FontRef) -> Location {
1112 font.axes().location(&[
1113 ("wght", 700.0),
1114 ("opsz", 48.0),
1115 ("GRAD", 200.0),
1116 ("FILL", 1.0),
1117 ])
1118 }
1119
1120 fn pt(x: f32, y: f32) -> Point {
1121 (x as f64, y as f64).into()
1122 }
1123
1124 fn svg_commands(elements: &[PathEl]) -> Vec<String> {
1126 elements
1127 .iter()
1128 .map(|e| match e {
1129 PathEl::MoveTo(p) => format!("M{:.2},{:.2}", p.x, p.y),
1130 PathEl::LineTo(p) => format!("L{:.2},{:.2}", p.x, p.y),
1131 PathEl::QuadTo(c0, p) => format!("Q{:.2},{:.2} {:.2},{:.2}", c0.x, c0.y, p.x, p.y),
1132 PathEl::CurveTo(c0, c1, p) => format!(
1133 "C{:.2},{:.2} {:.2},{:.2} {:.2},{:.2}",
1134 c0.x, c0.y, c1.x, c1.y, p.x, p.y
1135 ),
1136 PathEl::ClosePath => "Z".to_string(),
1137 })
1138 .collect()
1139 }
1140
1141 #[derive(Default)]
1143 struct BezPen {
1144 path: BezPath,
1145 }
1146
1147 impl OutlinePen for BezPen {
1148 fn move_to(&mut self, x: f32, y: f32) {
1149 self.path.move_to(pt(x, y));
1150 }
1151
1152 fn line_to(&mut self, x: f32, y: f32) {
1153 self.path.line_to(pt(x, y));
1154 }
1155
1156 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
1157 self.path.quad_to(pt(cx0, cy0), pt(x, y));
1158 }
1159
1160 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
1161 self.path.curve_to(pt(cx0, cy0), pt(cx1, cy1), pt(x, y));
1162 }
1163
1164 fn close(&mut self) {
1165 self.path.close_path();
1166 }
1167 }
1168
1169 fn assert_glyph_path_start_with(
1171 font: &FontRef,
1172 gid: GlyphId,
1173 loc: Location,
1174 path_style: PathStyle,
1175 expected_path_start: &[PathEl],
1176 ) {
1177 let glyph = font
1178 .outline_glyphs()
1179 .get(gid)
1180 .unwrap_or_else(|| panic!("No glyph for {gid}"));
1181
1182 let mut pen = BezPen::default();
1183 glyph
1184 .draw(
1185 DrawSettings::unhinted(Size::unscaled(), &loc).with_path_style(path_style),
1186 &mut pen,
1187 )
1188 .unwrap_or_else(|e| panic!("Unable to draw {gid}: {e}"));
1189 let bez = Affine::FLIP_Y * pen.path; let actual_path_start = &bez.elements()[..expected_path_start.len()];
1191 assert_eq!(
1194 svg_commands(expected_path_start),
1195 svg_commands(actual_path_start)
1196 );
1197 }
1198
1199 const MATERIAL_SYMBOL_GID_MAIL_AT_DEFAULT: GlyphId = GlyphId::new(1);
1200 const MATERIAL_SYMBOL_GID_MAIL_OFF_DEFAULT: GlyphId = GlyphId::new(2);
1201
1202 #[test]
1203 fn draw_icon_freetype_style_at_default() {
1204 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1205 assert_glyph_path_start_with(
1206 &font,
1207 MATERIAL_SYMBOL_GID_MAIL_AT_DEFAULT,
1208 Location::default(),
1209 PathStyle::FreeType,
1210 &[
1211 PathEl::MoveTo((160.0, -160.0).into()),
1212 PathEl::QuadTo((127.0, -160.0).into(), (103.5, -183.5).into()),
1213 PathEl::QuadTo((80.0, -207.0).into(), (80.0, -240.0).into()),
1214 ],
1215 );
1216 }
1217
1218 #[test]
1219 fn draw_icon_harfbuzz_style_at_default() {
1220 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1221 assert_glyph_path_start_with(
1222 &font,
1223 MATERIAL_SYMBOL_GID_MAIL_AT_DEFAULT,
1224 Location::default(),
1225 PathStyle::HarfBuzz,
1226 &[
1227 PathEl::MoveTo((160.0, -160.0).into()),
1228 PathEl::QuadTo((127.0, -160.0).into(), (103.5, -183.5).into()),
1229 PathEl::QuadTo((80.0, -207.0).into(), (80.0, -240.0).into()),
1230 ],
1231 );
1232 }
1233
1234 #[test]
1235 fn draw_icon_freetype_style_off_default() {
1236 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1237 assert_glyph_path_start_with(
1238 &font,
1239 MATERIAL_SYMBOL_GID_MAIL_OFF_DEFAULT,
1240 icon_loc_off_default(&font),
1241 PathStyle::FreeType,
1242 &[
1243 PathEl::MoveTo((150.0, -138.0).into()),
1244 PathEl::QuadTo((113.0, -138.0).into(), (86.0, -165.5).into()),
1245 PathEl::QuadTo((59.0, -193.0).into(), (59.0, -229.0).into()),
1246 ],
1247 );
1248 }
1249
1250 #[test]
1251 fn draw_icon_harfbuzz_style_off_default() {
1252 let font = FontRef::new(font_test_data::MATERIAL_SYMBOLS_SUBSET).unwrap();
1253 assert_glyph_path_start_with(
1254 &font,
1255 MATERIAL_SYMBOL_GID_MAIL_OFF_DEFAULT,
1256 icon_loc_off_default(&font),
1257 PathStyle::HarfBuzz,
1258 &[
1259 PathEl::MoveTo((150.0, -138.0).into()),
1260 PathEl::QuadTo((113.22, -138.0).into(), (86.11, -165.61).into()),
1261 PathEl::QuadTo((59.0, -193.22).into(), (59.0, -229.0).into()),
1262 ],
1263 );
1264 }
1265
1266 const GLYF_COMPONENT_GID_NON_UNIFORM_SCALE: GlyphId = GlyphId::new(3);
1267 const GLYF_COMPONENT_GID_SCALED_COMPONENT_OFFSET: GlyphId = GlyphId::new(7);
1268 const GLYF_COMPONENT_GID_NO_SCALED_COMPONENT_OFFSET: GlyphId = GlyphId::new(8);
1269
1270 #[test]
1271 fn draw_nonuniform_scale_component_freetype() {
1272 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1273 assert_glyph_path_start_with(
1274 &font,
1275 GLYF_COMPONENT_GID_NON_UNIFORM_SCALE,
1276 Location::default(),
1277 PathStyle::FreeType,
1278 &[
1279 PathEl::MoveTo((-138.0, -185.0).into()),
1280 PathEl::LineTo((-32.0, -259.0).into()),
1281 PathEl::LineTo((26.0, -175.0).into()),
1282 PathEl::LineTo((-80.0, -101.0).into()),
1283 PathEl::ClosePath,
1284 ],
1285 );
1286 }
1287
1288 #[test]
1289 fn draw_nonuniform_scale_component_harfbuzz() {
1290 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1291 assert_glyph_path_start_with(
1292 &font,
1293 GLYF_COMPONENT_GID_NON_UNIFORM_SCALE,
1294 Location::default(),
1295 PathStyle::HarfBuzz,
1296 &[
1297 PathEl::MoveTo((-137.8, -184.86).into()),
1298 PathEl::LineTo((-32.15, -258.52).into()),
1299 PathEl::LineTo((25.9, -175.24).into()),
1300 PathEl::LineTo((-79.75, -101.58).into()),
1301 PathEl::ClosePath,
1302 ],
1303 );
1304 }
1305
1306 #[test]
1307 fn draw_scaled_component_offset_freetype() {
1308 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1309 assert_glyph_path_start_with(
1310 &font,
1311 GLYF_COMPONENT_GID_SCALED_COMPONENT_OFFSET,
1312 Location::default(),
1313 PathStyle::FreeType,
1314 &[
1315 PathEl::MoveTo((715.0, -360.0).into()),
1317 ],
1318 );
1319 }
1320
1321 #[test]
1322 fn draw_no_scaled_component_offset_freetype() {
1323 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1324 assert_glyph_path_start_with(
1325 &font,
1326 GLYF_COMPONENT_GID_NO_SCALED_COMPONENT_OFFSET,
1327 Location::default(),
1328 PathStyle::FreeType,
1329 &[PathEl::MoveTo((705.0, -340.0).into())],
1330 );
1331 }
1332
1333 #[test]
1334 fn draw_scaled_component_offset_harfbuzz() {
1335 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1336 assert_glyph_path_start_with(
1337 &font,
1338 GLYF_COMPONENT_GID_SCALED_COMPONENT_OFFSET,
1339 Location::default(),
1340 PathStyle::HarfBuzz,
1341 &[
1342 PathEl::MoveTo((714.97, -360.0).into()),
1344 ],
1345 );
1346 }
1347
1348 #[test]
1349 fn draw_no_scaled_component_offset_harfbuzz() {
1350 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1351 assert_glyph_path_start_with(
1352 &font,
1353 GLYF_COMPONENT_GID_NO_SCALED_COMPONENT_OFFSET,
1354 Location::default(),
1355 PathStyle::HarfBuzz,
1356 &[PathEl::MoveTo((704.97, -340.0).into())],
1357 );
1358 }
1359
1360 #[cfg(feature = "spec_next")]
1361 const CUBIC_GLYPH: GlyphId = GlyphId::new(2);
1362
1363 #[test]
1364 #[cfg(feature = "spec_next")]
1365 fn draw_cubic() {
1366 let font = FontRef::new(font_test_data::CUBIC_GLYF).unwrap();
1367 assert_glyph_path_start_with(
1368 &font,
1369 CUBIC_GLYPH,
1370 Location::default(),
1371 PathStyle::FreeType,
1372 &[
1373 PathEl::MoveTo((278.0, -710.0).into()),
1374 PathEl::LineTo((278.0, -470.0).into()),
1375 PathEl::CurveTo(
1376 (300.0, -500.0).into(),
1377 (800.0, -500.0).into(),
1378 (998.0, -470.0).into(),
1379 ),
1380 PathEl::LineTo((998.0, -710.0).into()),
1381 ],
1382 );
1383 }
1384
1385 #[test]
1389 fn tthint_with_subset() {
1390 let font = FontRef::new(font_test_data::TTHINT_SUBSET).unwrap();
1391 let glyphs = font.outline_glyphs();
1392 let hinting = HintingInstance::new(
1393 &glyphs,
1394 Size::new(16.0),
1395 LocationRef::default(),
1396 HintingOptions::default(),
1397 )
1398 .unwrap();
1399 let glyph = glyphs.get(GlyphId::new(1)).unwrap();
1400 glyph
1402 .draw(DrawSettings::hinted(&hinting, true), &mut BezPen::default())
1403 .unwrap();
1404 }
1405
1406 #[test]
1407 fn empty_glyph_advance_unhinted() {
1408 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
1409 let outlines = font.outline_glyphs();
1410 let coords = [NormalizedCoord::from_f32(0.5)];
1411 let gid = font.charmap().map(' ').unwrap();
1412 let outline = outlines.get(gid).unwrap();
1413 let advance = outline
1414 .draw(
1415 (Size::new(24.0), LocationRef::new(&coords)),
1416 &mut super::pen::NullPen,
1417 )
1418 .unwrap()
1419 .advance_width
1420 .unwrap();
1421 assert_eq!(advance, 10.796875);
1422 }
1423
1424 #[test]
1425 fn empty_glyph_advance_hinted() {
1426 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
1427 let outlines = font.outline_glyphs();
1428 let coords = [NormalizedCoord::from_f32(0.5)];
1429 let hinter = HintingInstance::new(
1430 &outlines,
1431 Size::new(24.0),
1432 LocationRef::new(&coords),
1433 HintingOptions::default(),
1434 )
1435 .unwrap();
1436 let gid = font.charmap().map(' ').unwrap();
1437 let outline = outlines.get(gid).unwrap();
1438 let advance = outline
1439 .draw(&hinter, &mut super::pen::NullPen)
1440 .unwrap()
1441 .advance_width
1442 .unwrap();
1443 assert_eq!(advance, 11.0);
1444 }
1445}