1use super::{
4 super::{
5 pen::PathStyle,
6 unscaled::{clamp_coord, UnscaledOutlineSink},
7 DrawError, LocationRef, OutlineGlyph, OutlinePen,
8 },
9 metrics::Scale,
10 QuirksMode,
11};
12use core::ops::Range;
13use raw::{
14 limits::MAX_OUTLINE_POINTS,
15 tables::glyf::{PointFlags, PointMarker},
16 types::Point as Coord,
17 types::{F26Dot6, F2Dot14, GlyphId},
18};
19use smallvec::SmallVec;
20
21#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
28#[repr(i8)]
29pub enum Direction {
30 #[default]
32 None = 4,
33 Right = 1,
35 Left = -1,
37 Up = 2,
39 Down = -2,
41}
42
43impl Direction {
44 pub fn new(dx: i32, dy: i32) -> Self {
48 let (dir, long_arm, short_arm) = if dy >= dx {
49 if dy >= -dx {
50 (Direction::Up, dy, dx)
51 } else {
52 (Direction::Left, -dx, dy)
53 }
54 } else if dy >= -dx {
55 (Direction::Right, dx, dy)
56 } else {
57 (Direction::Down, -dy, dx)
58 };
59 if long_arm <= 14 * short_arm.abs() {
62 Direction::None
63 } else {
64 dir
65 }
66 }
67
68 pub fn is_opposite(self, other: Self) -> bool {
69 self as i8 + other as i8 == 0
70 }
71
72 pub fn is_same_axis(self, other: Self) -> bool {
73 (self as i8).abs() == (other as i8).abs()
74 }
75
76 pub(crate) fn normalize(self) -> Self {
77 match self {
79 Self::Left => Self::Right,
80 Self::Down => Self::Up,
81 _ => self,
82 }
83 }
84}
85
86#[derive(Copy, Clone, PartialEq, Eq, Debug)]
88pub(crate) enum Orientation {
89 Clockwise,
90 CounterClockwise,
91}
92
93#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
97pub(crate) struct Point {
98 pub flags: PointFlags,
100 pub fx: i32,
102 pub fy: i32,
104 pub ox: i32,
106 pub oy: i32,
108 pub x: i32,
110 pub y: i32,
112 pub in_dir: Direction,
114 pub out_dir: Direction,
116 pub u: i32,
118 pub v: i32,
120 pub next_ix: u16,
122 pub prev_ix: u16,
124}
125
126impl Point {
127 pub fn is_on_curve(&self) -> bool {
128 self.flags.is_on_curve()
129 }
130
131 pub fn next(&self) -> usize {
133 self.next_ix as usize
134 }
135
136 pub fn prev(&self) -> usize {
138 self.prev_ix as usize
139 }
140
141 #[inline(always)]
143 fn as_contour_point(&self) -> (Coord<F26Dot6>, PointFlags) {
144 (
145 Coord::new(F26Dot6::from_bits(self.x), F26Dot6::from_bits(self.y)),
146 self.flags,
147 )
148 }
149}
150
151const MAX_INLINE_POINTS: usize = 96;
155const MAX_INLINE_CONTOURS: usize = 8;
156
157#[derive(Default)]
158pub(crate) struct Outline {
159 pub units_per_em: i32,
160 pub orientation: Option<Orientation>,
161 pub points: SmallVec<[Point; MAX_INLINE_POINTS]>,
162 pub contours: SmallVec<[Contour; MAX_INLINE_CONTOURS]>,
163 pub advance: i32,
164}
165
166impl Outline {
167 pub fn fill(
169 &mut self,
170 glyph: &OutlineGlyph,
171 coords: &[F2Dot14],
172 quirks: QuirksMode,
173 ) -> Result<(), DrawError> {
174 let units_per_em = glyph.units_per_em() as i32;
175 self.fill_with(glyph.glyph_id(), units_per_em, quirks, |outline| {
176 glyph.draw_unscaled(LocationRef::new(coords), None, outline)
177 })
178 }
179
180 pub fn fill_with(
185 &mut self,
186 glyph_id: GlyphId,
187 units_per_em: i32,
188 quirks: QuirksMode,
189 draw: impl FnOnce(&mut Self) -> Result<i32, DrawError>,
190 ) -> Result<(), DrawError> {
191 self.clear();
192 self.units_per_em = units_per_em;
193 self.advance = draw(self)?;
194 self.analyze_and_validate(glyph_id, quirks)
195 }
196
197 fn analyze_and_validate(&mut self, gid: GlyphId, quirks: QuirksMode) -> Result<(), DrawError> {
198 if self.points.len() > MAX_OUTLINE_POINTS || self.contours.len() > MAX_OUTLINE_POINTS {
199 return Err(DrawError::TooManyPoints(gid));
200 }
201 let near_limit = 20 * self.units_per_em / 2048;
203 self.link_points();
204 self.mark_near_points(near_limit);
205 self.compute_directions(near_limit);
206 self.simplify_topology();
207 if quirks == QuirksMode::Aot {
208 self.check_remaining_weak_points(is_corner_flat_aot);
209 } else {
210 self.check_remaining_weak_points(is_corner_flat_jit);
211 }
212 self.compute_orientation();
213 Ok(())
214 }
215
216 pub fn scale(&mut self, scale: &Scale) {
219 use super::metrics::fixed_mul;
220 for point in &mut self.points {
221 let x = fixed_mul(point.fx, scale.x_scale) + scale.x_delta;
222 let y = fixed_mul(point.fy, scale.y_scale) + scale.y_delta;
223 point.ox = x;
224 point.x = x;
225 point.oy = y;
226 point.y = y;
227 }
228 }
229
230 pub fn clear(&mut self) {
231 self.units_per_em = 0;
232 self.points.clear();
233 self.contours.clear();
234 self.advance = 0;
235 }
236
237 pub fn to_path(&self, style: PathStyle, pen: &mut impl OutlinePen) -> Result<(), DrawError> {
238 for contour in &self.contours {
239 let Some(points) = self.points.get(contour.range()) else {
240 continue;
241 };
242 if !read_fonts::tables::glyf::outline::contour_to_path(
243 points,
244 Point::as_contour_point,
245 style.into(),
246 pen,
247 ) {
248 return Err(DrawError::Malformed);
249 }
250 }
251 Ok(())
252 }
253}
254
255impl Outline {
256 fn link_points(&mut self) {
258 let points = self.points.as_mut_slice();
259 for contour in &self.contours {
260 let Some(points) = points.get_mut(contour.range()) else {
261 continue;
262 };
263 let first_ix = contour.first();
264 let mut prev_ix = contour.last() as u16;
265 for (ix, point) in points.iter_mut().enumerate() {
266 let ix = (ix + first_ix) as u16;
267 point.prev_ix = prev_ix;
268 prev_ix = ix;
269 point.next_ix = ix.wrapping_add(1);
273 }
274 points.last_mut().unwrap().next_ix = first_ix as u16;
275 }
276 }
277
278 fn mark_near_points(&mut self, near_limit: i32) {
282 let points = self.points.as_mut_slice();
283 for contour in &self.contours {
284 let mut prev_ix = contour.last();
285 for ix in contour.range() {
286 let point = points[ix];
287 let prev = &mut points[prev_ix];
288 let out_x = point.fx - prev.fx;
290 let out_y = point.fy - prev.fy;
291 if out_x.abs() + out_y.abs() < near_limit {
292 prev.flags.set_marker(PointMarker::NEAR);
293 }
294 prev_ix = ix;
295 }
296 }
297 }
298
299 fn compute_directions(&mut self, near_limit: i32) {
303 let near_limit2 = 2 * near_limit - 1;
304 let points = self.points.as_mut_slice();
305 for contour in &self.contours {
306 let mut first_ix = contour.first();
308 let mut ix = first_ix;
309 let mut prev_ix = contour.prev(first_ix);
310 let mut point = points[first_ix];
311 while prev_ix != first_ix {
312 let prev = points[prev_ix];
313 let out_x = point.fx - prev.fx;
314 let out_y = point.fy - prev.fy;
315 if out_x.abs() + out_y.abs() >= near_limit2 {
317 break;
318 }
319 point = prev;
320 ix = prev_ix;
321 prev_ix = contour.prev(prev_ix);
322 }
323 first_ix = ix;
324 let first = &mut points[first_ix];
327 first.u = first_ix as _;
328 first.v = first_ix as _;
329 let mut next_ix = first_ix;
330 let mut ix = first_ix;
331 let mut out_x = 0;
334 let mut out_y = 0;
335 loop {
336 let point_ix = next_ix;
337 next_ix = contour.next(point_ix);
338 let point = points[point_ix];
339 let next = &mut points[next_ix];
340 out_x += next.fx - point.fx;
342 out_y += next.fy - point.fy;
343 if out_x.abs() + out_y.abs() < near_limit {
344 next.flags.set_marker(PointMarker::WEAK_INTERPOLATION);
345 if next_ix == first_ix {
348 break;
349 }
350 continue;
351 }
352 let out_dir = Direction::new(out_x, out_y);
353 next.in_dir = out_dir;
354 next.v = ix as _;
355 let cur = &mut points[ix];
356 cur.u = next_ix as _;
357 cur.out_dir = out_dir;
358 let mut inter_ix = contour.next(ix);
360 while inter_ix != next_ix {
361 let point = &mut points[inter_ix];
362 point.in_dir = out_dir;
363 point.out_dir = out_dir;
364 inter_ix = contour.next(inter_ix);
365 }
366 ix = next_ix;
367 points[ix].u = first_ix as _;
368 points[first_ix].v = ix as _;
369 out_x = 0;
370 out_y = 0;
371 if next_ix == first_ix {
372 break;
373 }
374 }
375 }
376 }
377
378 fn simplify_topology(&mut self) {
382 let points = self.points.as_mut_slice();
383 for i in 0..points.len() {
384 let point = points[i];
385 if point.flags.has_marker(PointMarker::WEAK_INTERPOLATION) {
386 continue;
387 }
388 if point.in_dir == Direction::None && point.out_dir == Direction::None {
389 let u_index = point.u as usize;
390 let v_index = point.v as usize;
391 let next_u = points[u_index];
392 let prev_v = points[v_index];
393 let in_x = point.fx - prev_v.fx;
394 let in_y = point.fy - prev_v.fy;
395 let out_x = next_u.fx - point.fx;
396 let out_y = next_u.fy - point.fy;
397 if (in_x ^ out_x) >= 0 && (in_y ^ out_y) >= 0 {
398 points[i].flags.set_marker(PointMarker::WEAK_INTERPOLATION);
400 points[v_index].u = u_index as _;
401 points[u_index].v = v_index as _;
402 }
403 }
404 }
405 }
406
407 fn check_remaining_weak_points(&mut self, is_corner_flat: impl Fn(i32, i32, i32, i32) -> bool) {
411 let points = self.points.as_mut_slice();
412 for i in 0..points.len() {
413 let point = points[i];
414 let mut make_weak = false;
415 if point.flags.has_marker(PointMarker::WEAK_INTERPOLATION) {
416 continue;
418 }
419 if !point.flags.is_on_curve() {
420 make_weak = true;
422 } else if point.out_dir == point.in_dir {
423 if point.out_dir != Direction::None {
424 make_weak = true;
427 } else {
428 let u_index = point.u as usize;
429 let v_index = point.v as usize;
430 let next_u = points[u_index];
431 let prev_v = points[v_index];
432 if is_corner_flat(
433 point.fx - prev_v.fx,
434 point.fy - prev_v.fy,
435 next_u.fx - point.fx,
436 next_u.fy - point.fy,
437 ) {
438 make_weak = true;
440 points[v_index].u = u_index as _;
441 points[u_index].v = v_index as _;
442 }
443 }
444 } else if point.in_dir.is_opposite(point.out_dir) {
445 make_weak = true;
447 }
448 if make_weak {
449 points[i].flags.set_marker(PointMarker::WEAK_INTERPOLATION);
450 }
451 }
452 }
453
454 fn compute_orientation(&mut self) {
458 self.orientation = None;
459 let points = self.points.as_slice();
460 if points.is_empty() {
461 return;
462 }
463 fn point_to_i64(point: &Point) -> (i64, i64) {
464 (point.fx as i64, point.fy as i64)
465 }
466 let mut area = 0i64;
467 for contour in &self.contours {
468 let last_ix = contour.last();
469 let first_ix = contour.first();
470 let (mut prev_x, mut prev_y) = point_to_i64(&points[last_ix]);
471 for point in &points[first_ix..=last_ix] {
472 let (x, y) = point_to_i64(point);
473 area += (y - prev_y) * (x + prev_x);
474 (prev_x, prev_y) = (x, y);
475 }
476 }
477 use core::cmp::Ordering;
478 self.orientation = match area.cmp(&0) {
479 Ordering::Less => Some(Orientation::CounterClockwise),
480 Ordering::Greater => Some(Orientation::Clockwise),
481 Ordering::Equal => None,
482 };
483 }
484}
485
486fn is_corner_flat_aot(in_x: i32, in_y: i32, out_x: i32, out_y: i32) -> bool {
488 let d_in = in_x.abs() + in_y.abs();
489 let d_out = out_x.abs() + out_y.abs();
490 let d_corner = (in_x + out_x).abs() + (in_y + out_y).abs();
491 (d_in + d_out - d_corner) < (d_corner >> 4)
492}
493
494fn is_corner_flat_jit(in_x: i32, in_y: i32, out_x: i32, out_y: i32) -> bool {
497 let ax = in_x + out_x;
498 let ay = in_y + out_y;
499 fn hypot(x: i32, y: i32) -> i32 {
500 let x = x.abs();
501 let y = y.abs();
502 if x > y {
503 x + ((3 * y) >> 3)
504 } else {
505 y + ((3 * x) >> 3)
506 }
507 }
508 let d_in = hypot(in_x, in_y);
509 let d_out = hypot(out_x, out_y);
510 let d_hypot = hypot(ax, ay);
511 (d_in + d_out - d_hypot) < (d_hypot >> 4)
512}
513
514#[derive(Copy, Clone, Default, Debug)]
515pub(crate) struct Contour {
516 first_ix: u16,
517 last_ix: u16,
518}
519
520impl Contour {
521 pub fn first(self) -> usize {
522 self.first_ix as usize
523 }
524
525 pub fn last(self) -> usize {
526 self.last_ix as usize
527 }
528
529 pub fn next(self, index: usize) -> usize {
530 if index >= self.last_ix as usize {
531 self.first_ix as usize
532 } else {
533 index + 1
534 }
535 }
536
537 pub fn prev(self, index: usize) -> usize {
538 if index <= self.first_ix as usize {
539 self.last_ix as usize
540 } else {
541 index - 1
542 }
543 }
544
545 pub fn range(self) -> Range<usize> {
546 self.first()..self.last() + 1
547 }
548}
549
550impl UnscaledOutlineSink for Outline {
551 fn start_contour(&mut self) {
552 let ix = self.points.len() as u16;
555 self.contours.push(Contour {
556 first_ix: ix,
557 last_ix: ix,
558 });
559 }
560
561 fn point(&mut self, x: i32, y: i32, flags: PointFlags) {
562 let ix = self.points.len() as u16;
565 match self.contours.last_mut() {
566 Some(contour) if contour.first_ix == ix => {}
568 Some(contour) => contour.last_ix = ix,
569 None => self.contours.push(Contour {
571 first_ix: ix,
572 last_ix: ix,
573 }),
574 }
575 self.points.push(Point {
576 flags,
577 fx: clamp_coord(x),
578 fy: clamp_coord(y),
579 ..Default::default()
580 });
581 }
582
583 fn end_contour(&mut self) {
584 if self
585 .contours
586 .last()
587 .is_some_and(|contour| contour.first_ix as usize == self.points.len())
588 {
589 self.contours.pop();
590 }
591 }
592}
593
594#[cfg(test)]
595mod tests {
596 use super::super::super::{pen::SvgPen, DrawSettings};
597 use super::*;
598 use crate::{prelude::Size, MetadataProvider};
599 use raw::{types::GlyphId, FontRef, TableProvider};
600
601 #[test]
604 fn distant_points_are_analyzed() {
605 let mut outline = Outline::default();
606 for far in [1 << 26, i32::MAX] {
607 outline
608 .fill_with(GlyphId::new(0), 1000, QuirksMode::default(), |outline| {
609 outline.start_contour();
610 for (x, y) in [(-far, -far), (far, -far), (far, far), (i32::MIN, far)] {
611 outline.point(x, y, PointFlags::on_curve());
612 }
613 outline.end_contour();
614 Ok(far)
615 })
616 .unwrap();
617 let scale = Scale::new(
618 16.0,
619 1000,
620 Default::default(),
621 Default::default(),
622 Default::default(),
623 );
624 outline.scale(&scale);
625 }
626 }
627
628 #[test]
629 fn direction_from_vectors() {
630 assert_eq!(Direction::new(-100, 0), Direction::Left);
631 assert_eq!(Direction::new(100, 0), Direction::Right);
632 assert_eq!(Direction::new(0, -100), Direction::Down);
633 assert_eq!(Direction::new(0, 100), Direction::Up);
634 assert_eq!(Direction::new(7, 100), Direction::Up);
635 assert_eq!(Direction::new(8, 100), Direction::None);
637 }
638
639 #[test]
640 fn direction_axes() {
641 use Direction::*;
642 let hori = [Left, Right];
643 let vert = [Up, Down];
644 for h in hori {
645 for h2 in hori {
646 assert!(h.is_same_axis(h2));
647 if h != h2 {
648 assert!(h.is_opposite(h2));
649 } else {
650 assert!(!h.is_opposite(h2));
651 }
652 }
653 for v in vert {
654 assert!(!h.is_same_axis(v));
655 assert!(!h.is_opposite(v));
656 }
657 }
658 for v in vert {
659 for v2 in vert {
660 assert!(v.is_same_axis(v2));
661 if v != v2 {
662 assert!(v.is_opposite(v2));
663 } else {
664 assert!(!v.is_opposite(v2));
665 }
666 }
667 }
668 }
669
670 #[test]
671 fn fill_outline() {
672 let outline = make_outline(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS, 8);
673 use Direction::*;
674 let expected = &[
675 (107, 0, Left, Left, 3),
677 (85, 0, Left, None, 2),
678 (55, 26, None, Up, 2),
679 (55, 71, Up, Up, 3),
680 (55, 332, Up, Up, 3),
681 (55, 360, Up, None, 2),
682 (67, 411, None, None, 2),
683 (93, 459, None, None, 2),
684 (112, 481, None, Up, 1),
685 (112, 504, Up, Right, 1),
686 (168, 504, Right, Down, 1),
687 (168, 483, Down, None, 1),
688 (153, 473, None, None, 2),
689 (126, 428, None, None, 2),
690 (109, 366, None, Down, 2),
691 (109, 332, Down, Down, 3),
692 (109, 109, Down, Right, 1),
693 (407, 109, Right, Right, 3),
694 (427, 109, Right, None, 2),
695 (446, 136, None, None, 2),
696 (453, 169, None, Up, 2),
697 (453, 178, Up, Up, 3),
698 (453, 374, Up, Up, 3),
699 (453, 432, Up, None, 2),
700 (400, 483, None, Left, 2),
701 (362, 483, Left, Left, 3),
702 (109, 483, Left, Left, 3),
703 (86, 483, Left, None, 2),
704 (62, 517, None, Up, 2),
705 (62, 555, Up, Up, 3),
706 (62, 566, Up, None, 2),
707 (64, 587, None, None, 2),
708 (71, 619, None, None, 2),
709 (76, 647, None, Right, 1),
710 (103, 647, Right, Down, 9),
711 (103, 644, Down, Down, 3),
712 (103, 619, Down, None, 2),
713 (131, 592, None, Right, 2),
714 (155, 592, Right, Right, 3),
715 (386, 592, Right, Right, 3),
716 (437, 592, Right, None, 2),
717 (489, 552, None, None, 2),
718 (507, 485, None, Down, 2),
719 (507, 443, Down, Down, 3),
720 (507, 75, Down, Down, 3),
721 (507, 40, Down, None, 2),
722 (470, 0, None, Left, 2),
723 (436, 0, Left, Left, 3),
724 ];
725 let points = outline
726 .points
727 .iter()
728 .map(|point| {
729 (
730 point.fx,
731 point.fy,
732 point.in_dir,
733 point.out_dir,
734 point.flags.to_bits(),
735 )
736 })
737 .collect::<Vec<_>>();
738 assert_eq!(&points, expected);
739 }
740
741 #[test]
742 fn fill_sanity_checks_u16_index_bounds() {
743 let mut outline = Outline::default();
744 let gid = GlyphId::new(42);
745 let quirks = QuirksMode::Jit;
746 outline
747 .points
748 .resize(u16::MAX as usize + 1, Point::default());
749 outline
750 .contours
751 .resize(u16::MAX as usize + 1, Contour::default());
752 assert!(outline.analyze_and_validate(gid, quirks).is_ok());
753 outline
754 .points
755 .resize(u16::MAX as usize + 2, Point::default());
756 assert!(matches!(
757 outline.analyze_and_validate(gid, quirks),
758 Err(DrawError::TooManyPoints(err_gid)) if err_gid == gid
759 ));
760 outline.points.clear();
761 outline
762 .contours
763 .resize(u16::MAX as usize + 2, Contour::default());
764 assert!(matches!(
765 outline.analyze_and_validate(gid, quirks),
766 Err(DrawError::TooManyPoints(err_gid)) if err_gid == gid
767 ));
768 }
769
770 #[test]
771 fn link_points_handles_max_points_last_next_overflow() {
772 let mut outline = Outline::default();
773 outline
774 .points
775 .resize(u16::MAX as usize + 1, Point::default());
776 outline.contours.push(Contour {
777 first_ix: 0,
778 last_ix: u16::MAX,
779 });
780 let quirks = QuirksMode::Jit;
781 outline.analyze_and_validate(0u32.into(), quirks).unwrap();
782 assert_eq!(outline.points[0].prev(), u16::MAX as usize);
785 assert_eq!(outline.points[0].next(), 1);
786 assert_eq!(
787 outline.points[u16::MAX as usize].prev(),
788 u16::MAX as usize - 1
789 );
790 assert_eq!(outline.points[u16::MAX as usize].next(), 0);
791 }
792
793 #[test]
794 fn orientation() {
795 let tt_outline = make_outline(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS, 8);
796 assert_eq!(tt_outline.orientation, Some(Orientation::CounterClockwise));
798 let ps_outline = make_outline(font_test_data::CANTARELL_VF_TRIMMED, 4);
799 assert_eq!(ps_outline.orientation, Some(Orientation::Clockwise));
801 }
802
803 fn make_outline(font_data: &[u8], glyph_id: u32) -> Outline {
804 let font = FontRef::new(font_data).unwrap();
805 let glyphs = font.outline_glyphs();
806 let glyph = glyphs.get(GlyphId::from(glyph_id)).unwrap();
807 let mut outline = Outline::default();
808 outline.fill(&glyph, &[], Default::default()).unwrap();
809 outline
810 }
811
812 #[test]
813 fn mostly_off_curve_to_path_scan_backward() {
814 compare_path_conversion(font_test_data::MOSTLY_OFF_CURVE, PathStyle::FreeType);
815 }
816
817 #[test]
818 fn mostly_off_curve_to_path_scan_forward() {
819 compare_path_conversion(font_test_data::MOSTLY_OFF_CURVE, PathStyle::HarfBuzz);
820 }
821
822 #[test]
823 fn starting_off_curve_to_path_scan_backward() {
824 compare_path_conversion(font_test_data::STARTING_OFF_CURVE, PathStyle::FreeType);
825 }
826
827 #[test]
828 fn starting_off_curve_to_path_scan_forward() {
829 compare_path_conversion(font_test_data::STARTING_OFF_CURVE, PathStyle::HarfBuzz);
830 }
831
832 #[test]
833 fn cubic_to_path_scan_backward() {
834 compare_path_conversion(font_test_data::CUBIC_GLYF, PathStyle::FreeType);
835 }
836
837 #[test]
838 fn cubic_to_path_scan_forward() {
839 compare_path_conversion(font_test_data::CUBIC_GLYF, PathStyle::HarfBuzz);
840 }
841
842 #[test]
843 fn cff_to_path_scan_backward() {
844 compare_path_conversion(font_test_data::CANTARELL_VF_TRIMMED, PathStyle::FreeType);
845 }
846
847 #[test]
848 fn cff_to_path_scan_forward() {
849 compare_path_conversion(font_test_data::CANTARELL_VF_TRIMMED, PathStyle::HarfBuzz);
850 }
851
852 fn compare_path_conversion(font_data: &[u8], path_style: PathStyle) {
856 let font = FontRef::new(font_data).unwrap();
857 let glyph_count = font.maxp().unwrap().num_glyphs();
858 let glyphs = font.outline_glyphs();
859 let mut results = Vec::new();
860 for gid in 0..glyph_count {
862 let glyph = glyphs.get(GlyphId::from(gid)).unwrap();
863 let mut base_svg = SvgPen::default();
865 let settings = DrawSettings::unhinted(Size::unscaled(), LocationRef::default())
866 .with_path_style(path_style);
867 glyph.draw(settings, &mut base_svg).unwrap();
868 let base_svg = base_svg.to_string();
869 let mut outline = Outline::default();
871 outline.fill(&glyph, &[], Default::default()).unwrap();
872 for point in &mut outline.points {
876 point.x = point.fx << 6;
877 point.y = point.fy << 6;
878 }
879 let mut autohint_svg = SvgPen::default();
880 outline.to_path(path_style, &mut autohint_svg).unwrap();
881 let autohint_svg = autohint_svg.to_string();
882 if base_svg != autohint_svg {
883 results.push((gid, base_svg, autohint_svg));
884 }
885 }
886 if !results.is_empty() {
887 let report: String = results
888 .into_iter()
889 .map(|(gid, expected, got)| {
890 format!("[glyph {gid}]\nexpected: {expected}\n got: {got}")
891 })
892 .collect::<Vec<_>>()
893 .join("\n");
894 panic!("outline to path comparison failed:\n{report}");
895 }
896 }
897}