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(last_point) = points.last().map(Point::as_contour_point) {
208 path::contour_to_path(
209 points.iter().map(Point::as_contour_point),
210 last_point,
211 style,
212 pen,
213 )?;
214 }
215 }
216 Ok(())
217 }
218}
219
220impl Outline {
221 fn link_points(&mut self) {
223 let points = self.points.as_mut_slice();
224 for contour in &self.contours {
225 let Some(points) = points.get_mut(contour.range()) else {
226 continue;
227 };
228 let first_ix = contour.first() as u16;
229 let mut prev_ix = contour.last() as u16;
230 for (ix, point) in points.iter_mut().enumerate() {
231 let ix = ix as u16 + first_ix;
232 point.prev_ix = prev_ix;
233 prev_ix = ix;
234 point.next_ix = ix + 1;
235 }
236 points.last_mut().unwrap().next_ix = first_ix;
237 }
238 }
239
240 fn mark_near_points(&mut self, near_limit: i32) {
244 let points = self.points.as_mut_slice();
245 for contour in &self.contours {
246 let mut prev_ix = contour.last();
247 for ix in contour.range() {
248 let point = points[ix];
249 let prev = &mut points[prev_ix];
250 let out_x = point.fx - prev.fx;
252 let out_y = point.fy - prev.fy;
253 if out_x.abs() + out_y.abs() < near_limit {
254 prev.flags.set_marker(PointMarker::NEAR);
255 }
256 prev_ix = ix;
257 }
258 }
259 }
260
261 fn compute_directions(&mut self, near_limit: i32) {
265 let near_limit2 = 2 * near_limit - 1;
266 let points = self.points.as_mut_slice();
267 for contour in &self.contours {
268 let mut first_ix = contour.first();
270 let mut ix = first_ix;
271 let mut prev_ix = contour.prev(first_ix);
272 let mut point = points[first_ix];
273 while prev_ix != first_ix {
274 let prev = points[prev_ix];
275 let out_x = point.fx - prev.fx;
276 let out_y = point.fy - prev.fy;
277 if out_x.abs() + out_y.abs() >= near_limit2 {
279 break;
280 }
281 point = prev;
282 ix = prev_ix;
283 prev_ix = contour.prev(prev_ix);
284 }
285 first_ix = ix;
286 let first = &mut points[first_ix];
289 first.u = first_ix as _;
290 first.v = first_ix as _;
291 let mut next_ix = first_ix;
292 let mut ix = first_ix;
293 let mut out_x = 0;
296 let mut out_y = 0;
297 loop {
298 let point_ix = next_ix;
299 next_ix = contour.next(point_ix);
300 let point = points[point_ix];
301 let next = &mut points[next_ix];
302 out_x += next.fx - point.fx;
304 out_y += next.fy - point.fy;
305 if out_x.abs() + out_y.abs() < near_limit {
306 next.flags.set_marker(PointMarker::WEAK_INTERPOLATION);
307 if next_ix == first_ix {
310 break;
311 }
312 continue;
313 }
314 let out_dir = Direction::new(out_x, out_y);
315 next.in_dir = out_dir;
316 next.v = ix as _;
317 let cur = &mut points[ix];
318 cur.u = next_ix as _;
319 cur.out_dir = out_dir;
320 let mut inter_ix = contour.next(ix);
322 while inter_ix != next_ix {
323 let point = &mut points[inter_ix];
324 point.in_dir = out_dir;
325 point.out_dir = out_dir;
326 inter_ix = contour.next(inter_ix);
327 }
328 ix = next_ix;
329 points[ix].u = first_ix as _;
330 points[first_ix].v = ix as _;
331 out_x = 0;
332 out_y = 0;
333 if next_ix == first_ix {
334 break;
335 }
336 }
337 }
338 }
339
340 fn simplify_topology(&mut self) {
344 let points = self.points.as_mut_slice();
345 for i in 0..points.len() {
346 let point = points[i];
347 if point.flags.has_marker(PointMarker::WEAK_INTERPOLATION) {
348 continue;
349 }
350 if point.in_dir == Direction::None && point.out_dir == Direction::None {
351 let u_index = point.u as usize;
352 let v_index = point.v as usize;
353 let next_u = points[u_index];
354 let prev_v = points[v_index];
355 let in_x = point.fx - prev_v.fx;
356 let in_y = point.fy - prev_v.fy;
357 let out_x = next_u.fx - point.fx;
358 let out_y = next_u.fy - point.fy;
359 if (in_x ^ out_x) >= 0 && (in_y ^ out_y) >= 0 {
360 points[i].flags.set_marker(PointMarker::WEAK_INTERPOLATION);
362 points[v_index].u = u_index as _;
363 points[u_index].v = v_index as _;
364 }
365 }
366 }
367 }
368
369 fn check_remaining_weak_points(&mut self) {
373 let points = self.points.as_mut_slice();
374 for i in 0..points.len() {
375 let point = points[i];
376 let mut make_weak = false;
377 if point.flags.has_marker(PointMarker::WEAK_INTERPOLATION) {
378 continue;
380 }
381 if !point.flags.is_on_curve() {
382 make_weak = true;
384 } else if point.out_dir == point.in_dir {
385 if point.out_dir != Direction::None {
386 make_weak = true;
389 } else {
390 let u_index = point.u as usize;
391 let v_index = point.v as usize;
392 let next_u = points[u_index];
393 let prev_v = points[v_index];
394 if is_corner_flat(
395 point.fx - prev_v.fx,
396 point.fy - prev_v.fy,
397 next_u.fx - point.fx,
398 next_u.fy - point.fy,
399 ) {
400 make_weak = true;
402 points[v_index].u = u_index as _;
403 points[u_index].v = v_index as _;
404 }
405 }
406 } else if point.in_dir.is_opposite(point.out_dir) {
407 make_weak = true;
409 }
410 if make_weak {
411 points[i].flags.set_marker(PointMarker::WEAK_INTERPOLATION);
412 }
413 }
414 }
415
416 fn compute_orientation(&mut self) {
420 self.orientation = None;
421 let points = self.points.as_slice();
422 if points.is_empty() {
423 return;
424 }
425 fn point_to_i64(point: &Point) -> (i64, i64) {
426 (point.fx as i64, point.fy as i64)
427 }
428 let mut area = 0i64;
429 for contour in &self.contours {
430 let last_ix = contour.last();
431 let first_ix = contour.first();
432 let (mut prev_x, mut prev_y) = point_to_i64(&points[last_ix]);
433 for point in &points[first_ix..=last_ix] {
434 let (x, y) = point_to_i64(point);
435 area += (y - prev_y) * (x + prev_x);
436 (prev_x, prev_y) = (x, y);
437 }
438 }
439 use core::cmp::Ordering;
440 self.orientation = match area.cmp(&0) {
441 Ordering::Less => Some(Orientation::CounterClockwise),
442 Ordering::Greater => Some(Orientation::Clockwise),
443 Ordering::Equal => None,
444 };
445 }
446}
447
448fn is_corner_flat(in_x: i32, in_y: i32, out_x: i32, out_y: i32) -> bool {
450 let ax = in_x + out_x;
451 let ay = in_y + out_y;
452 fn hypot(x: i32, y: i32) -> i32 {
453 let x = x.abs();
454 let y = y.abs();
455 if x > y {
456 x + ((3 * y) >> 3)
457 } else {
458 y + ((3 * x) >> 3)
459 }
460 }
461 let d_in = hypot(in_x, in_y);
462 let d_out = hypot(out_x, out_y);
463 let d_hypot = hypot(ax, ay);
464 (d_in + d_out - d_hypot) < (d_hypot >> 4)
465}
466
467#[derive(Copy, Clone, Default, Debug)]
468pub(crate) struct Contour {
469 first_ix: u16,
470 last_ix: u16,
471}
472
473impl Contour {
474 pub fn first(self) -> usize {
475 self.first_ix as usize
476 }
477
478 pub fn last(self) -> usize {
479 self.last_ix as usize
480 }
481
482 pub fn next(self, index: usize) -> usize {
483 if index >= self.last_ix as usize {
484 self.first_ix as usize
485 } else {
486 index + 1
487 }
488 }
489
490 pub fn prev(self, index: usize) -> usize {
491 if index <= self.first_ix as usize {
492 self.last_ix as usize
493 } else {
494 index - 1
495 }
496 }
497
498 pub fn range(self) -> Range<usize> {
499 self.first()..self.last() + 1
500 }
501}
502
503impl UnscaledOutlineSink for Outline {
504 fn try_reserve(&mut self, additional: usize) -> Result<(), DrawError> {
505 if self.points.try_reserve(additional) {
506 Ok(())
507 } else {
508 Err(DrawError::InsufficientMemory)
509 }
510 }
511
512 fn push(&mut self, point: UnscaledPoint) -> Result<(), DrawError> {
513 let new_point = Point {
514 flags: point.flags,
515 fx: point.x as i32,
516 fy: point.y as i32,
517 ..Default::default()
518 };
519 let new_point_ix: u16 = self
520 .points
521 .len()
522 .try_into()
523 .map_err(|_| DrawError::InsufficientMemory)?;
524 if point.is_contour_start {
525 self.contours.push(Contour {
526 first_ix: new_point_ix,
527 last_ix: new_point_ix,
528 });
529 } else if let Some(last_contour) = self.contours.last_mut() {
530 last_contour.last_ix += 1;
531 } else {
532 self.contours.push(Contour {
535 first_ix: new_point_ix,
536 last_ix: new_point_ix,
537 });
538 }
539 self.points.push(new_point);
540 Ok(())
541 }
542}
543
544#[cfg(test)]
545mod tests {
546 use super::super::super::{pen::SvgPen, DrawSettings};
547 use super::*;
548 use crate::{prelude::Size, MetadataProvider};
549 use raw::{types::GlyphId, FontRef, TableProvider};
550
551 #[test]
552 fn direction_from_vectors() {
553 assert_eq!(Direction::new(-100, 0), Direction::Left);
554 assert_eq!(Direction::new(100, 0), Direction::Right);
555 assert_eq!(Direction::new(0, -100), Direction::Down);
556 assert_eq!(Direction::new(0, 100), Direction::Up);
557 assert_eq!(Direction::new(7, 100), Direction::Up);
558 assert_eq!(Direction::new(8, 100), Direction::None);
560 }
561
562 #[test]
563 fn direction_axes() {
564 use Direction::*;
565 let hori = [Left, Right];
566 let vert = [Up, Down];
567 for h in hori {
568 for h2 in hori {
569 assert!(h.is_same_axis(h2));
570 if h != h2 {
571 assert!(h.is_opposite(h2));
572 } else {
573 assert!(!h.is_opposite(h2));
574 }
575 }
576 for v in vert {
577 assert!(!h.is_same_axis(v));
578 assert!(!h.is_opposite(v));
579 }
580 }
581 for v in vert {
582 for v2 in vert {
583 assert!(v.is_same_axis(v2));
584 if v != v2 {
585 assert!(v.is_opposite(v2));
586 } else {
587 assert!(!v.is_opposite(v2));
588 }
589 }
590 }
591 }
592
593 #[test]
594 fn fill_outline() {
595 let outline = make_outline(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS, 8);
596 use Direction::*;
597 let expected = &[
598 (107, 0, Left, Left, 3),
600 (85, 0, Left, None, 2),
601 (55, 26, None, Up, 2),
602 (55, 71, Up, Up, 3),
603 (55, 332, Up, Up, 3),
604 (55, 360, Up, None, 2),
605 (67, 411, None, None, 2),
606 (93, 459, None, None, 2),
607 (112, 481, None, Up, 1),
608 (112, 504, Up, Right, 1),
609 (168, 504, Right, Down, 1),
610 (168, 483, Down, None, 1),
611 (153, 473, None, None, 2),
612 (126, 428, None, None, 2),
613 (109, 366, None, Down, 2),
614 (109, 332, Down, Down, 3),
615 (109, 109, Down, Right, 1),
616 (407, 109, Right, Right, 3),
617 (427, 109, Right, None, 2),
618 (446, 136, None, None, 2),
619 (453, 169, None, Up, 2),
620 (453, 178, Up, Up, 3),
621 (453, 374, Up, Up, 3),
622 (453, 432, Up, None, 2),
623 (400, 483, None, Left, 2),
624 (362, 483, Left, Left, 3),
625 (109, 483, Left, Left, 3),
626 (86, 483, Left, None, 2),
627 (62, 517, None, Up, 2),
628 (62, 555, Up, Up, 3),
629 (62, 566, Up, None, 2),
630 (64, 587, None, None, 2),
631 (71, 619, None, None, 2),
632 (76, 647, None, Right, 1),
633 (103, 647, Right, Down, 9),
634 (103, 644, Down, Down, 3),
635 (103, 619, Down, None, 2),
636 (131, 592, None, Right, 2),
637 (155, 592, Right, Right, 3),
638 (386, 592, Right, Right, 3),
639 (437, 592, Right, None, 2),
640 (489, 552, None, None, 2),
641 (507, 485, None, Down, 2),
642 (507, 443, Down, Down, 3),
643 (507, 75, Down, Down, 3),
644 (507, 40, Down, None, 2),
645 (470, 0, None, Left, 2),
646 (436, 0, Left, Left, 3),
647 ];
648 let points = outline
649 .points
650 .iter()
651 .map(|point| {
652 (
653 point.fx,
654 point.fy,
655 point.in_dir,
656 point.out_dir,
657 point.flags.to_bits(),
658 )
659 })
660 .collect::<Vec<_>>();
661 assert_eq!(&points, expected);
662 }
663
664 #[test]
665 fn orientation() {
666 let tt_outline = make_outline(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS, 8);
667 assert_eq!(tt_outline.orientation, Some(Orientation::CounterClockwise));
669 let ps_outline = make_outline(font_test_data::CANTARELL_VF_TRIMMED, 4);
670 assert_eq!(ps_outline.orientation, Some(Orientation::Clockwise));
672 }
673
674 fn make_outline(font_data: &[u8], glyph_id: u32) -> Outline {
675 let font = FontRef::new(font_data).unwrap();
676 let glyphs = font.outline_glyphs();
677 let glyph = glyphs.get(GlyphId::from(glyph_id)).unwrap();
678 let mut outline = Outline::default();
679 outline.fill(&glyph, Default::default()).unwrap();
680 outline
681 }
682
683 #[test]
684 fn mostly_off_curve_to_path_scan_backward() {
685 compare_path_conversion(font_test_data::MOSTLY_OFF_CURVE, PathStyle::FreeType);
686 }
687
688 #[test]
689 fn mostly_off_curve_to_path_scan_forward() {
690 compare_path_conversion(font_test_data::MOSTLY_OFF_CURVE, PathStyle::HarfBuzz);
691 }
692
693 #[test]
694 fn starting_off_curve_to_path_scan_backward() {
695 compare_path_conversion(font_test_data::STARTING_OFF_CURVE, PathStyle::FreeType);
696 }
697
698 #[test]
699 fn starting_off_curve_to_path_scan_forward() {
700 compare_path_conversion(font_test_data::STARTING_OFF_CURVE, PathStyle::HarfBuzz);
701 }
702
703 #[test]
704 fn cubic_to_path_scan_backward() {
705 compare_path_conversion(font_test_data::CUBIC_GLYF, PathStyle::FreeType);
706 }
707
708 #[test]
709 fn cubic_to_path_scan_forward() {
710 compare_path_conversion(font_test_data::CUBIC_GLYF, PathStyle::HarfBuzz);
711 }
712
713 #[test]
714 fn cff_to_path_scan_backward() {
715 compare_path_conversion(font_test_data::CANTARELL_VF_TRIMMED, PathStyle::FreeType);
716 }
717
718 #[test]
719 fn cff_to_path_scan_forward() {
720 compare_path_conversion(font_test_data::CANTARELL_VF_TRIMMED, PathStyle::HarfBuzz);
721 }
722
723 fn compare_path_conversion(font_data: &[u8], path_style: PathStyle) {
727 let font = FontRef::new(font_data).unwrap();
728 let glyph_count = font.maxp().unwrap().num_glyphs();
729 let glyphs = font.outline_glyphs();
730 let mut results = Vec::new();
731 for gid in 0..glyph_count {
733 let glyph = glyphs.get(GlyphId::from(gid)).unwrap();
734 let mut base_svg = SvgPen::default();
736 let settings = DrawSettings::unhinted(Size::unscaled(), LocationRef::default())
737 .with_path_style(path_style);
738 glyph.draw(settings, &mut base_svg).unwrap();
739 let base_svg = base_svg.to_string();
740 let mut outline = Outline::default();
742 outline.fill(&glyph, Default::default()).unwrap();
743 for point in &mut outline.points {
747 point.x = point.fx << 6;
748 point.y = point.fy << 6;
749 }
750 let mut autohint_svg = SvgPen::default();
751 outline.to_path(path_style, &mut autohint_svg).unwrap();
752 let autohint_svg = autohint_svg.to_string();
753 if base_svg != autohint_svg {
754 results.push((gid, base_svg, autohint_svg));
755 }
756 }
757 if !results.is_empty() {
758 let report: String = results
759 .into_iter()
760 .map(|(gid, expected, got)| {
761 format!("[glyph {gid}]\nexpected: {expected}\n got: {got}")
762 })
763 .collect::<Vec<_>>()
764 .join("\n");
765 panic!("outline to path comparison failed:\n{report}");
766 }
767 }
768}