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