1use super::{
4 super::{
5 path,
6 pen::PathStyle,
7 unscaled::{UnscaledOutlineSink, UnscaledPoint},
8 DrawError, LocationRef, OutlineGlyph, OutlinePen,
9 },
10 metrics::Scale,
11 QuirksMode,
12};
13use crate::collections::SmallVec;
14use core::ops::Range;
15use raw::{
16 tables::glyf::{PointFlags, PointMarker},
17 types::{F26Dot6, F2Dot14},
18};
19
20#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
27#[repr(i8)]
28pub enum Direction {
29 #[default]
31 None = 4,
32 Right = 1,
34 Left = -1,
36 Up = 2,
38 Down = -2,
40}
41
42impl Direction {
43 pub fn new(dx: i32, dy: i32) -> Self {
47 let (dir, long_arm, short_arm) = if dy >= dx {
48 if dy >= -dx {
49 (Direction::Up, dy, dx)
50 } else {
51 (Direction::Left, -dx, dy)
52 }
53 } else if dy >= -dx {
54 (Direction::Right, dx, dy)
55 } else {
56 (Direction::Down, -dy, dx)
57 };
58 if long_arm <= 14 * short_arm.abs() {
61 Direction::None
62 } else {
63 dir
64 }
65 }
66
67 pub fn is_opposite(self, other: Self) -> bool {
68 self as i8 + other as i8 == 0
69 }
70
71 pub fn is_same_axis(self, other: Self) -> bool {
72 (self as i8).abs() == (other as i8).abs()
73 }
74
75 pub(crate) fn normalize(self) -> Self {
76 match self {
78 Self::Left => Self::Right,
79 Self::Down => Self::Up,
80 _ => self,
81 }
82 }
83}
84
85#[derive(Copy, Clone, PartialEq, Eq, Debug)]
87pub(crate) enum Orientation {
88 Clockwise,
89 CounterClockwise,
90}
91
92#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
96pub(crate) struct Point {
97 pub flags: PointFlags,
99 pub fx: i32,
101 pub fy: i32,
103 pub ox: i32,
105 pub oy: i32,
107 pub x: i32,
109 pub y: i32,
111 pub in_dir: Direction,
113 pub out_dir: Direction,
115 pub u: i32,
117 pub v: i32,
119 pub next_ix: u16,
121 pub prev_ix: u16,
123}
124
125impl Point {
126 pub fn is_on_curve(&self) -> bool {
127 self.flags.is_on_curve()
128 }
129
130 pub fn next(&self) -> usize {
132 self.next_ix as usize
133 }
134
135 pub fn prev(&self) -> usize {
137 self.prev_ix as usize
138 }
139
140 #[inline(always)]
141 fn as_contour_point(&self) -> path::ContourPoint<F26Dot6> {
142 path::ContourPoint {
143 x: F26Dot6::from_bits(self.x),
144 y: F26Dot6::from_bits(self.y),
145 flags: self.flags,
146 }
147 }
148}
149
150const MAX_INLINE_POINTS: usize = 96;
154const MAX_INLINE_CONTOURS: usize = 8;
155
156#[derive(Default)]
157pub(crate) struct Outline {
158 pub units_per_em: i32,
159 pub orientation: Option<Orientation>,
160 pub points: SmallVec<Point, MAX_INLINE_POINTS>,
161 pub contours: SmallVec<Contour, MAX_INLINE_CONTOURS>,
162 pub advance: i32,
163}
164
165impl Outline {
166 pub fn fill(
168 &mut self,
169 glyph: &OutlineGlyph,
170 coords: &[F2Dot14],
171 quirks: QuirksMode,
172 ) -> Result<(), DrawError> {
173 self.clear();
174 let advance = glyph.draw_unscaled(LocationRef::new(coords), None, self)?;
175 self.advance = advance;
176 self.units_per_em = glyph.units_per_em() as i32;
177 let near_limit = 20 * self.units_per_em / 2048;
179 self.link_points();
180 self.mark_near_points(near_limit);
181 self.compute_directions(near_limit);
182 self.simplify_topology();
183 if quirks == QuirksMode::Aot {
184 self.check_remaining_weak_points(is_corner_flat_aot);
185 } else {
186 self.check_remaining_weak_points(is_corner_flat_jit);
187 }
188 self.compute_orientation();
189 Ok(())
190 }
191
192 pub fn scale(&mut self, scale: &Scale) {
195 use super::metrics::fixed_mul;
196 for point in &mut self.points {
197 let x = fixed_mul(point.fx, scale.x_scale) + scale.x_delta;
198 let y = fixed_mul(point.fy, scale.y_scale) + scale.y_delta;
199 point.ox = x;
200 point.x = x;
201 point.oy = y;
202 point.y = y;
203 }
204 }
205
206 pub fn clear(&mut self) {
207 self.units_per_em = 0;
208 self.points.clear();
209 self.contours.clear();
210 self.advance = 0;
211 }
212
213 pub fn to_path(
214 &self,
215 style: PathStyle,
216 pen: &mut impl OutlinePen,
217 ) -> Result<(), path::ToPathError> {
218 for contour in &self.contours {
219 let Some(points) = self.points.get(contour.range()) else {
220 continue;
221 };
222 if let (Some(first_point), Some(last_point)) = (
223 points.first().map(Point::as_contour_point),
224 points.last().map(Point::as_contour_point),
225 ) {
226 path::contour_to_path(
227 points.iter().map(Point::as_contour_point),
228 first_point,
229 last_point,
230 style,
231 pen,
232 )?;
233 }
234 }
235 Ok(())
236 }
237}
238
239impl Outline {
240 fn link_points(&mut self) {
242 let points = self.points.as_mut_slice();
243 for contour in &self.contours {
244 let Some(points) = points.get_mut(contour.range()) else {
245 continue;
246 };
247 let first_ix = contour.first() as u16;
248 let mut prev_ix = contour.last() as u16;
249 for (ix, point) in points.iter_mut().enumerate() {
250 let ix = ix as u16 + first_ix;
251 point.prev_ix = prev_ix;
252 prev_ix = ix;
253 point.next_ix = ix + 1;
254 }
255 points.last_mut().unwrap().next_ix = first_ix;
256 }
257 }
258
259 fn mark_near_points(&mut self, near_limit: i32) {
263 let points = self.points.as_mut_slice();
264 for contour in &self.contours {
265 let mut prev_ix = contour.last();
266 for ix in contour.range() {
267 let point = points[ix];
268 let prev = &mut points[prev_ix];
269 let out_x = point.fx - prev.fx;
271 let out_y = point.fy - prev.fy;
272 if out_x.abs() + out_y.abs() < near_limit {
273 prev.flags.set_marker(PointMarker::NEAR);
274 }
275 prev_ix = ix;
276 }
277 }
278 }
279
280 fn compute_directions(&mut self, near_limit: i32) {
284 let near_limit2 = 2 * near_limit - 1;
285 let points = self.points.as_mut_slice();
286 for contour in &self.contours {
287 let mut first_ix = contour.first();
289 let mut ix = first_ix;
290 let mut prev_ix = contour.prev(first_ix);
291 let mut point = points[first_ix];
292 while prev_ix != first_ix {
293 let prev = points[prev_ix];
294 let out_x = point.fx - prev.fx;
295 let out_y = point.fy - prev.fy;
296 if out_x.abs() + out_y.abs() >= near_limit2 {
298 break;
299 }
300 point = prev;
301 ix = prev_ix;
302 prev_ix = contour.prev(prev_ix);
303 }
304 first_ix = ix;
305 let first = &mut points[first_ix];
308 first.u = first_ix as _;
309 first.v = first_ix as _;
310 let mut next_ix = first_ix;
311 let mut ix = first_ix;
312 let mut out_x = 0;
315 let mut out_y = 0;
316 loop {
317 let point_ix = next_ix;
318 next_ix = contour.next(point_ix);
319 let point = points[point_ix];
320 let next = &mut points[next_ix];
321 out_x += next.fx - point.fx;
323 out_y += next.fy - point.fy;
324 if out_x.abs() + out_y.abs() < near_limit {
325 next.flags.set_marker(PointMarker::WEAK_INTERPOLATION);
326 if next_ix == first_ix {
329 break;
330 }
331 continue;
332 }
333 let out_dir = Direction::new(out_x, out_y);
334 next.in_dir = out_dir;
335 next.v = ix as _;
336 let cur = &mut points[ix];
337 cur.u = next_ix as _;
338 cur.out_dir = out_dir;
339 let mut inter_ix = contour.next(ix);
341 while inter_ix != next_ix {
342 let point = &mut points[inter_ix];
343 point.in_dir = out_dir;
344 point.out_dir = out_dir;
345 inter_ix = contour.next(inter_ix);
346 }
347 ix = next_ix;
348 points[ix].u = first_ix as _;
349 points[first_ix].v = ix as _;
350 out_x = 0;
351 out_y = 0;
352 if next_ix == first_ix {
353 break;
354 }
355 }
356 }
357 }
358
359 fn simplify_topology(&mut self) {
363 let points = self.points.as_mut_slice();
364 for i in 0..points.len() {
365 let point = points[i];
366 if point.flags.has_marker(PointMarker::WEAK_INTERPOLATION) {
367 continue;
368 }
369 if point.in_dir == Direction::None && point.out_dir == Direction::None {
370 let u_index = point.u as usize;
371 let v_index = point.v as usize;
372 let next_u = points[u_index];
373 let prev_v = points[v_index];
374 let in_x = point.fx - prev_v.fx;
375 let in_y = point.fy - prev_v.fy;
376 let out_x = next_u.fx - point.fx;
377 let out_y = next_u.fy - point.fy;
378 if (in_x ^ out_x) >= 0 && (in_y ^ out_y) >= 0 {
379 points[i].flags.set_marker(PointMarker::WEAK_INTERPOLATION);
381 points[v_index].u = u_index as _;
382 points[u_index].v = v_index as _;
383 }
384 }
385 }
386 }
387
388 fn check_remaining_weak_points(&mut self, is_corner_flat: impl Fn(i32, i32, i32, i32) -> bool) {
392 let points = self.points.as_mut_slice();
393 for i in 0..points.len() {
394 let point = points[i];
395 let mut make_weak = false;
396 if point.flags.has_marker(PointMarker::WEAK_INTERPOLATION) {
397 continue;
399 }
400 if !point.flags.is_on_curve() {
401 make_weak = true;
403 } else if point.out_dir == point.in_dir {
404 if point.out_dir != Direction::None {
405 make_weak = true;
408 } else {
409 let u_index = point.u as usize;
410 let v_index = point.v as usize;
411 let next_u = points[u_index];
412 let prev_v = points[v_index];
413 if is_corner_flat(
414 point.fx - prev_v.fx,
415 point.fy - prev_v.fy,
416 next_u.fx - point.fx,
417 next_u.fy - point.fy,
418 ) {
419 make_weak = true;
421 points[v_index].u = u_index as _;
422 points[u_index].v = v_index as _;
423 }
424 }
425 } else if point.in_dir.is_opposite(point.out_dir) {
426 make_weak = true;
428 }
429 if make_weak {
430 points[i].flags.set_marker(PointMarker::WEAK_INTERPOLATION);
431 }
432 }
433 }
434
435 fn compute_orientation(&mut self) {
439 self.orientation = None;
440 let points = self.points.as_slice();
441 if points.is_empty() {
442 return;
443 }
444 fn point_to_i64(point: &Point) -> (i64, i64) {
445 (point.fx as i64, point.fy as i64)
446 }
447 let mut area = 0i64;
448 for contour in &self.contours {
449 let last_ix = contour.last();
450 let first_ix = contour.first();
451 let (mut prev_x, mut prev_y) = point_to_i64(&points[last_ix]);
452 for point in &points[first_ix..=last_ix] {
453 let (x, y) = point_to_i64(point);
454 area += (y - prev_y) * (x + prev_x);
455 (prev_x, prev_y) = (x, y);
456 }
457 }
458 use core::cmp::Ordering;
459 self.orientation = match area.cmp(&0) {
460 Ordering::Less => Some(Orientation::CounterClockwise),
461 Ordering::Greater => Some(Orientation::Clockwise),
462 Ordering::Equal => None,
463 };
464 }
465}
466
467fn is_corner_flat_aot(in_x: i32, in_y: i32, out_x: i32, out_y: i32) -> bool {
469 let d_in = in_x.abs() + in_y.abs();
470 let d_out = out_x.abs() + out_y.abs();
471 let d_corner = (in_x + out_x).abs() + (in_y + out_y).abs();
472 (d_in + d_out - d_corner) < (d_corner >> 4)
473}
474
475fn is_corner_flat_jit(in_x: i32, in_y: i32, out_x: i32, out_y: i32) -> bool {
478 let ax = in_x + out_x;
479 let ay = in_y + out_y;
480 fn hypot(x: i32, y: i32) -> i32 {
481 let x = x.abs();
482 let y = y.abs();
483 if x > y {
484 x + ((3 * y) >> 3)
485 } else {
486 y + ((3 * x) >> 3)
487 }
488 }
489 let d_in = hypot(in_x, in_y);
490 let d_out = hypot(out_x, out_y);
491 let d_hypot = hypot(ax, ay);
492 (d_in + d_out - d_hypot) < (d_hypot >> 4)
493}
494
495#[derive(Copy, Clone, Default, Debug)]
496pub(crate) struct Contour {
497 first_ix: u16,
498 last_ix: u16,
499}
500
501impl Contour {
502 pub fn first(self) -> usize {
503 self.first_ix as usize
504 }
505
506 pub fn last(self) -> usize {
507 self.last_ix as usize
508 }
509
510 pub fn next(self, index: usize) -> usize {
511 if index >= self.last_ix as usize {
512 self.first_ix as usize
513 } else {
514 index + 1
515 }
516 }
517
518 pub fn prev(self, index: usize) -> usize {
519 if index <= self.first_ix as usize {
520 self.last_ix as usize
521 } else {
522 index - 1
523 }
524 }
525
526 pub fn range(self) -> Range<usize> {
527 self.first()..self.last() + 1
528 }
529}
530
531impl UnscaledOutlineSink for Outline {
532 fn try_reserve(&mut self, additional: usize) -> Result<(), DrawError> {
533 if self.points.try_reserve(additional) {
534 Ok(())
535 } else {
536 Err(DrawError::InsufficientMemory)
537 }
538 }
539
540 fn push(&mut self, point: UnscaledPoint) -> Result<(), DrawError> {
541 let new_point = Point {
542 flags: point.flags,
543 fx: point.x as i32,
544 fy: point.y as i32,
545 ..Default::default()
546 };
547 let new_point_ix: u16 = self
548 .points
549 .len()
550 .try_into()
551 .map_err(|_| DrawError::InsufficientMemory)?;
552 if point.is_contour_start {
553 self.contours.push(Contour {
554 first_ix: new_point_ix,
555 last_ix: new_point_ix,
556 });
557 } else if let Some(last_contour) = self.contours.last_mut() {
558 last_contour.last_ix += 1;
559 } else {
560 self.contours.push(Contour {
563 first_ix: new_point_ix,
564 last_ix: new_point_ix,
565 });
566 }
567 self.points.push(new_point);
568 Ok(())
569 }
570}
571
572#[cfg(test)]
573mod tests {
574 use super::super::super::{pen::SvgPen, DrawSettings};
575 use super::*;
576 use crate::{prelude::Size, MetadataProvider};
577 use raw::{types::GlyphId, FontRef, TableProvider};
578
579 #[test]
580 fn direction_from_vectors() {
581 assert_eq!(Direction::new(-100, 0), Direction::Left);
582 assert_eq!(Direction::new(100, 0), Direction::Right);
583 assert_eq!(Direction::new(0, -100), Direction::Down);
584 assert_eq!(Direction::new(0, 100), Direction::Up);
585 assert_eq!(Direction::new(7, 100), Direction::Up);
586 assert_eq!(Direction::new(8, 100), Direction::None);
588 }
589
590 #[test]
591 fn direction_axes() {
592 use Direction::*;
593 let hori = [Left, Right];
594 let vert = [Up, Down];
595 for h in hori {
596 for h2 in hori {
597 assert!(h.is_same_axis(h2));
598 if h != h2 {
599 assert!(h.is_opposite(h2));
600 } else {
601 assert!(!h.is_opposite(h2));
602 }
603 }
604 for v in vert {
605 assert!(!h.is_same_axis(v));
606 assert!(!h.is_opposite(v));
607 }
608 }
609 for v in vert {
610 for v2 in vert {
611 assert!(v.is_same_axis(v2));
612 if v != v2 {
613 assert!(v.is_opposite(v2));
614 } else {
615 assert!(!v.is_opposite(v2));
616 }
617 }
618 }
619 }
620
621 #[test]
622 fn fill_outline() {
623 let outline = make_outline(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS, 8);
624 use Direction::*;
625 let expected = &[
626 (107, 0, Left, Left, 3),
628 (85, 0, Left, None, 2),
629 (55, 26, None, Up, 2),
630 (55, 71, Up, Up, 3),
631 (55, 332, Up, Up, 3),
632 (55, 360, Up, None, 2),
633 (67, 411, None, None, 2),
634 (93, 459, None, None, 2),
635 (112, 481, None, Up, 1),
636 (112, 504, Up, Right, 1),
637 (168, 504, Right, Down, 1),
638 (168, 483, Down, None, 1),
639 (153, 473, None, None, 2),
640 (126, 428, None, None, 2),
641 (109, 366, None, Down, 2),
642 (109, 332, Down, Down, 3),
643 (109, 109, Down, Right, 1),
644 (407, 109, Right, Right, 3),
645 (427, 109, Right, None, 2),
646 (446, 136, None, None, 2),
647 (453, 169, None, Up, 2),
648 (453, 178, Up, Up, 3),
649 (453, 374, Up, Up, 3),
650 (453, 432, Up, None, 2),
651 (400, 483, None, Left, 2),
652 (362, 483, Left, Left, 3),
653 (109, 483, Left, Left, 3),
654 (86, 483, Left, None, 2),
655 (62, 517, None, Up, 2),
656 (62, 555, Up, Up, 3),
657 (62, 566, Up, None, 2),
658 (64, 587, None, None, 2),
659 (71, 619, None, None, 2),
660 (76, 647, None, Right, 1),
661 (103, 647, Right, Down, 9),
662 (103, 644, Down, Down, 3),
663 (103, 619, Down, None, 2),
664 (131, 592, None, Right, 2),
665 (155, 592, Right, Right, 3),
666 (386, 592, Right, Right, 3),
667 (437, 592, Right, None, 2),
668 (489, 552, None, None, 2),
669 (507, 485, None, Down, 2),
670 (507, 443, Down, Down, 3),
671 (507, 75, Down, Down, 3),
672 (507, 40, Down, None, 2),
673 (470, 0, None, Left, 2),
674 (436, 0, Left, Left, 3),
675 ];
676 let points = outline
677 .points
678 .iter()
679 .map(|point| {
680 (
681 point.fx,
682 point.fy,
683 point.in_dir,
684 point.out_dir,
685 point.flags.to_bits(),
686 )
687 })
688 .collect::<Vec<_>>();
689 assert_eq!(&points, expected);
690 }
691
692 #[test]
693 fn orientation() {
694 let tt_outline = make_outline(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS, 8);
695 assert_eq!(tt_outline.orientation, Some(Orientation::CounterClockwise));
697 let ps_outline = make_outline(font_test_data::CANTARELL_VF_TRIMMED, 4);
698 assert_eq!(ps_outline.orientation, Some(Orientation::Clockwise));
700 }
701
702 fn make_outline(font_data: &[u8], glyph_id: u32) -> Outline {
703 let font = FontRef::new(font_data).unwrap();
704 let glyphs = font.outline_glyphs();
705 let glyph = glyphs.get(GlyphId::from(glyph_id)).unwrap();
706 let mut outline = Outline::default();
707 outline.fill(&glyph, &[], Default::default()).unwrap();
708 outline
709 }
710
711 #[test]
712 fn mostly_off_curve_to_path_scan_backward() {
713 compare_path_conversion(font_test_data::MOSTLY_OFF_CURVE, PathStyle::FreeType);
714 }
715
716 #[test]
717 fn mostly_off_curve_to_path_scan_forward() {
718 compare_path_conversion(font_test_data::MOSTLY_OFF_CURVE, PathStyle::HarfBuzz);
719 }
720
721 #[test]
722 fn starting_off_curve_to_path_scan_backward() {
723 compare_path_conversion(font_test_data::STARTING_OFF_CURVE, PathStyle::FreeType);
724 }
725
726 #[test]
727 fn starting_off_curve_to_path_scan_forward() {
728 compare_path_conversion(font_test_data::STARTING_OFF_CURVE, PathStyle::HarfBuzz);
729 }
730
731 #[test]
732 fn cubic_to_path_scan_backward() {
733 compare_path_conversion(font_test_data::CUBIC_GLYF, PathStyle::FreeType);
734 }
735
736 #[test]
737 fn cubic_to_path_scan_forward() {
738 compare_path_conversion(font_test_data::CUBIC_GLYF, PathStyle::HarfBuzz);
739 }
740
741 #[test]
742 fn cff_to_path_scan_backward() {
743 compare_path_conversion(font_test_data::CANTARELL_VF_TRIMMED, PathStyle::FreeType);
744 }
745
746 #[test]
747 fn cff_to_path_scan_forward() {
748 compare_path_conversion(font_test_data::CANTARELL_VF_TRIMMED, PathStyle::HarfBuzz);
749 }
750
751 fn compare_path_conversion(font_data: &[u8], path_style: PathStyle) {
755 let font = FontRef::new(font_data).unwrap();
756 let glyph_count = font.maxp().unwrap().num_glyphs();
757 let glyphs = font.outline_glyphs();
758 let mut results = Vec::new();
759 for gid in 0..glyph_count {
761 let glyph = glyphs.get(GlyphId::from(gid)).unwrap();
762 let mut base_svg = SvgPen::default();
764 let settings = DrawSettings::unhinted(Size::unscaled(), LocationRef::default())
765 .with_path_style(path_style);
766 glyph.draw(settings, &mut base_svg).unwrap();
767 let base_svg = base_svg.to_string();
768 let mut outline = Outline::default();
770 outline.fill(&glyph, &[], Default::default()).unwrap();
771 for point in &mut outline.points {
775 point.x = point.fx << 6;
776 point.y = point.fy << 6;
777 }
778 let mut autohint_svg = SvgPen::default();
779 outline.to_path(path_style, &mut autohint_svg).unwrap();
780 let autohint_svg = autohint_svg.to_string();
781 if base_svg != autohint_svg {
782 results.push((gid, base_svg, autohint_svg));
783 }
784 }
785 if !results.is_empty() {
786 let report: String = results
787 .into_iter()
788 .map(|(gid, expected, got)| {
789 format!("[glyph {gid}]\nexpected: {expected}\n got: {got}")
790 })
791 .collect::<Vec<_>>()
792 .join("\n");
793 panic!("outline to path comparison failed:\n{report}");
794 }
795 }
796}