1use super::{
10 super::{
11 metrics::{fixed_div, fixed_mul, Scale, ScaledAxisMetrics, ScaledBlue, UnscaledBlue},
12 outline::Direction,
13 style::ScriptGroup,
14 },
15 Axis, BlueProvenance, Dimension, Edge, TopoFlags,
16};
17
18pub(crate) fn compute_edges(
22 axis: &mut Axis,
23 metrics: &ScaledAxisMetrics,
24 top_to_bottom_hinting: bool,
25 y_scale: i32,
26 group: ScriptGroup,
27) {
28 axis.edges.clear();
29 let scale = metrics.scale;
30 let top_to_bottom_hinting =
34 if axis.dim == Dimension::Horizontal || group != ScriptGroup::Default {
35 false
36 } else {
37 top_to_bottom_hinting
38 };
39 let segment_length_threshold = if axis.dim == Dimension::Horizontal {
41 fixed_div(64, y_scale)
42 } else {
43 0
44 };
45 let segment_width_threshold = fixed_div(32, scale);
47 let initial_threshold = metrics.width_metrics.edge_distance_threshold;
50 const EDGE_DISTANCE_THRESHOLD_MAX: i32 = 64 / 4;
51 let edge_distance_threshold = if group == ScriptGroup::Default {
52 fixed_div(
53 fixed_mul(initial_threshold, scale).min(EDGE_DISTANCE_THRESHOLD_MAX),
54 scale,
55 )
56 } else {
57 let threshold = fixed_mul(initial_threshold, scale);
60 if threshold > EDGE_DISTANCE_THRESHOLD_MAX {
61 fixed_div(EDGE_DISTANCE_THRESHOLD_MAX, scale)
62 } else {
63 initial_threshold
64 }
65 };
66 for segment_ix in 0..axis.segments.len() {
71 let segment = &axis.segments[segment_ix];
72 if group == ScriptGroup::Default {
73 if (segment.height as i32) < segment_length_threshold
75 || (segment.delta as i32 > segment_width_threshold)
76 || segment.dir == Direction::None
77 {
78 continue;
79 }
80 if segment.serif_ix.is_some()
82 && (2 * segment.height as i32) < (3 * segment_length_threshold)
83 {
84 continue;
85 }
86 }
87 let mut best_dist = i32::MAX;
89 let mut best_edge_ix = None;
90 for edge_ix in 0..axis.edges.len() {
91 let edge = &axis.edges[edge_ix];
92 let dist = (segment.pos as i32 - edge.fpos as i32).abs();
93 if dist < edge_distance_threshold && edge.dir == segment.dir && dist < best_dist {
94 if group == ScriptGroup::Default {
95 best_edge_ix = Some(edge_ix);
96 break;
97 }
98 if let Some(link) = segment.link(&axis.segments).copied() {
101 let first_ix = edge.first_ix as usize;
104 let mut seg1 = &axis.segments[first_ix];
105 let mut dist2 = 0;
106 loop {
107 if let Some(link1) = seg1.link(&axis.segments).copied() {
108 dist2 = (link.pos as i32 - link1.pos as i32).abs();
109 if dist2 >= edge_distance_threshold {
110 break;
111 }
112 }
113 if seg1.edge_next_ix == Some(first_ix as u16) {
114 break;
115 }
116 if let Some(next) = seg1.next_in_edge(&axis.segments) {
117 seg1 = next;
118 } else {
119 break;
120 }
121 }
122 if dist2 >= edge_distance_threshold {
123 continue;
124 }
125 }
126 best_dist = dist;
127 best_edge_ix = Some(edge_ix);
128 }
129 }
130 if let Some(edge_ix) = best_edge_ix {
131 axis.append_segment_to_edge(segment_ix, edge_ix);
132 } else {
133 let opos = fixed_mul(segment.pos as i32, scale);
135 let edge = Edge {
136 fpos: segment.pos,
137 opos,
138 pos: opos,
139 dir: segment.dir,
140 first_ix: segment_ix as u16,
141 last_ix: segment_ix as u16,
142 ..Default::default()
143 };
144 axis.insert_edge(edge, top_to_bottom_hinting);
145 axis.segments[segment_ix].edge_next_ix = Some(segment_ix as u16);
146 }
147 }
148 if group == ScriptGroup::Default {
149 for segment_ix in 0..axis.segments.len() {
152 let segment = &axis.segments[segment_ix];
153 if segment.dir != Direction::None {
154 continue;
155 }
156 if let Some(edge_ix) = axis
159 .edges
160 .iter()
161 .enumerate()
162 .filter_map(|(ix, edge)| {
163 ((segment.pos as i32 - edge.fpos as i32).abs() < edge_distance_threshold)
164 .then_some(ix)
165 })
166 .next()
167 {
168 axis.append_segment_to_edge(segment_ix, edge_ix);
170 }
171 }
172 }
173 link_segments_to_edges(axis);
174 compute_edge_properties(axis);
175}
176
177fn link_segments_to_edges(axis: &mut Axis) {
180 let segments = axis.segments.as_mut_slice();
181 for edge_ix in 0..axis.edges.len() {
182 let edge = &axis.edges[edge_ix];
183 let mut ix = edge.first_ix as usize;
184 let last_ix = edge.last_ix as usize;
185 loop {
186 let segment = &mut segments[ix];
187 segment.edge_ix = Some(edge_ix as u16);
188 if ix == last_ix {
189 break;
190 }
191 ix = segment
192 .edge_next_ix
193 .map(|ix| ix as usize)
194 .unwrap_or(last_ix);
195 }
196 }
197}
198
199fn compute_edge_properties(axis: &mut Axis) {
204 let edges = axis.edges.as_mut_slice();
205 let segments = axis.segments.as_slice();
206 for edge_ix in 0..edges.len() {
207 let mut roundness = 0;
208 let mut straightness = 0;
209 let edge = edges[edge_ix];
210 let mut segment_ix = edge.first_ix as usize;
211 let last_segment_ix = edge.last_ix as usize;
212 loop {
213 let edge = edges[edge_ix];
216 let segment = &segments[segment_ix];
217 let next_segment_ix = segment.edge_next_ix;
218 if segment.flags.contains(TopoFlags::ROUND) {
220 roundness += 1;
221 } else {
222 straightness += 1;
223 }
224 let is_serif = if let Some(serif_ix) = segment.serif_ix {
226 let serif = &segments[serif_ix as usize];
227 serif.edge_ix.is_some() && serif.edge_ix != Some(edge_ix as u16)
228 } else {
229 false
230 };
231 if is_serif
233 || (segment.link_ix.is_some()
234 && segments[segment.link_ix.unwrap() as usize]
235 .edge_ix
236 .is_some())
237 {
238 let (edge2_ix, segment2_ix) = if is_serif {
239 (edge.serif_ix, segment.serif_ix)
240 } else {
241 (edge.link_ix, segment.link_ix)
242 };
243 let edge2_ix = if let (Some(edge2_ix), Some(segment2_ix)) = (edge2_ix, segment2_ix)
244 {
245 let edge2 = &edges[edge2_ix as usize];
246 let edge_delta = (edge.fpos as i32 - edge2.fpos as i32).abs();
247 let segment2 = &segments[segment2_ix as usize];
248 let segment_delta = (segment.pos as i32 - segment2.pos as i32).abs();
249 if segment_delta < edge_delta {
250 segment2.edge_ix
251 } else {
252 Some(edge2_ix)
253 }
254 } else if let Some(segment2_ix) = segment2_ix {
255 segments[segment2_ix as usize].edge_ix
256 } else {
257 edge2_ix
258 };
259 if is_serif {
260 edges[edge_ix].serif_ix = edge2_ix;
261 edges[edge2_ix.unwrap() as usize].flags |= TopoFlags::SERIF;
262 } else {
263 edges[edge_ix].link_ix = edge2_ix;
264 }
265 }
266 if segment_ix == last_segment_ix {
267 break;
268 }
269 segment_ix = next_segment_ix
270 .map(|ix| ix as usize)
271 .unwrap_or(last_segment_ix);
272 }
273 let edge = &mut edges[edge_ix];
274 edge.flags = TopoFlags::NORMAL;
275 if roundness > 0 && roundness >= straightness {
276 edge.flags |= TopoFlags::ROUND;
277 }
278 if edge.serif_ix.is_some() && edge.link_ix.is_some() {
280 edge.serif_ix = None;
281 }
282 }
283}
284
285pub(crate) fn compute_blue_edges(
291 axis: &mut Axis,
292 scale: &Scale,
293 unscaled_blues: &[UnscaledBlue],
294 blues: &[ScaledBlue],
295 group: ScriptGroup,
296) {
297 if axis.dim != Dimension::Vertical && group == ScriptGroup::Default {
301 return;
302 }
303 let axis_scale = if axis.dim == Dimension::Horizontal {
304 scale.x_scale
305 } else {
306 scale.y_scale
307 };
308 let initial_best_dest = fixed_mul(scale.units_per_em / 40, axis_scale).min(64 / 2);
310 for edge in &mut axis.edges {
311 let mut best_blue = None;
312 let mut best_is_neutral = false;
313 let mut best_blue_idx = None;
314 let mut best_blue_is_shoot = false;
315 let mut best_dist = initial_best_dest;
318 for (blue_ix, (unscaled_blue, blue)) in unscaled_blues.iter().zip(blues).enumerate() {
319 if !blue.is_active {
321 continue;
322 }
323 let is_top = blue.zones.is_top_like();
324 let is_neutral = blue.zones.is_neutral();
325 let is_major_dir = edge.dir == axis.major_dir;
326 if is_top ^ is_major_dir || is_neutral {
328 let (ref_pos, matching_blue) = if group == ScriptGroup::Default {
330 (unscaled_blue.position, blue.position)
331 } else {
332 if (edge.fpos as i32 - unscaled_blue.position).abs()
336 > (edge.fpos as i32 - unscaled_blue.overshoot).abs()
337 {
338 (unscaled_blue.overshoot, blue.overshoot)
339 } else {
340 (unscaled_blue.position, blue.position)
341 }
342 };
343 let dist = fixed_mul((edge.fpos as i32 - ref_pos).abs(), axis_scale);
344 if dist < best_dist {
345 best_dist = dist;
346 best_blue = Some(matching_blue);
347 best_is_neutral = is_neutral;
348 best_blue_idx = Some(blue_ix as u16);
349 best_blue_is_shoot = false;
350 }
351 if group == ScriptGroup::Default {
352 if edge.flags.contains(TopoFlags::ROUND) && dist != 0 && !is_neutral {
356 let is_under_ref = (edge.fpos as i32) < unscaled_blue.position;
357 if is_top ^ is_under_ref {
358 let dist = fixed_mul(
359 (edge.fpos as i32 - unscaled_blue.overshoot).abs(),
360 axis_scale,
361 );
362 if dist < best_dist {
363 best_dist = dist;
364 best_blue = Some(blue.overshoot);
365 best_is_neutral = is_neutral;
366 best_blue_idx = Some(blue_ix as u16);
367 best_blue_is_shoot = true;
368 }
369 }
370 }
371 }
372 }
373 }
374 if let Some(best_blue) = best_blue {
375 edge.blue_edge = Some(best_blue);
376 edge.blue_provenance = Some(BlueProvenance {
377 index: best_blue_idx.unwrap_or_default(),
378 is_shoot: best_blue_is_shoot,
379 });
380 if best_is_neutral {
381 edge.flags |= TopoFlags::NEUTRAL;
382 }
383 }
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use super::{
390 super::super::{
391 metrics::{self, ScaledWidth},
392 outline::Outline,
393 shape::{Shaper, ShaperMode},
394 style,
395 },
396 super::segments,
397 *,
398 };
399 use crate::{attribute::Style, MetadataProvider};
400 use raw::{types::GlyphId, FontRef, TableProvider};
401
402 #[test]
403 fn edges_default() {
404 let expected_h_edges = [
405 Edge {
406 fpos: 15,
407 opos: 15,
408 pos: 15,
409 flags: TopoFlags::ROUND,
410 dir: Direction::Up,
411 blue_edge: None,
412 blue_provenance: None,
413 link_ix: Some(3),
414 serif_ix: None,
415 scale: 0,
416 first_ix: 1,
417 last_ix: 1,
418 },
419 Edge {
420 fpos: 123,
421 opos: 126,
422 pos: 126,
423 flags: TopoFlags::NORMAL,
424 dir: Direction::Up,
425 blue_edge: None,
426 blue_provenance: None,
427 link_ix: Some(2),
428 serif_ix: None,
429 scale: 0,
430 first_ix: 0,
431 last_ix: 0,
432 },
433 Edge {
434 fpos: 186,
435 opos: 190,
436 pos: 190,
437 flags: TopoFlags::NORMAL,
438 dir: Direction::Down,
439 blue_edge: None,
440 blue_provenance: None,
441 link_ix: Some(1),
442 serif_ix: None,
443 scale: 0,
444 first_ix: 4,
445 last_ix: 4,
446 },
447 Edge {
448 fpos: 205,
449 opos: 210,
450 pos: 210,
451 flags: TopoFlags::ROUND,
452 dir: Direction::Down,
453 blue_edge: None,
454 blue_provenance: None,
455 link_ix: Some(0),
456 serif_ix: None,
457 scale: 0,
458 first_ix: 3,
459 last_ix: 3,
460 },
461 ];
462 let expected_v_edges = [
463 Edge {
464 fpos: -240,
465 opos: -246,
466 pos: -246,
467 flags: TopoFlags::NORMAL,
468 dir: Direction::Left,
469 blue_edge: Some(ScaledWidth {
470 scaled: -246,
471 fitted: -256,
472 }),
473 blue_provenance: Some(BlueProvenance {
474 index: 2,
475 is_shoot: false,
476 }),
477 link_ix: None,
478 serif_ix: Some(1),
479 scale: 0,
480 first_ix: 3,
481 last_ix: 3,
482 },
483 Edge {
484 fpos: 481,
485 opos: 493,
486 pos: 493,
487 flags: TopoFlags::NORMAL,
488 dir: Direction::Left,
489 blue_edge: None,
490 blue_provenance: None,
491 link_ix: Some(2),
492 serif_ix: None,
493 scale: 0,
494 first_ix: 0,
495 last_ix: 0,
496 },
497 Edge {
498 fpos: 592,
499 opos: 606,
500 pos: 606,
501 flags: TopoFlags::ROUND | TopoFlags::SERIF,
502 dir: Direction::Right,
503 blue_edge: Some(ScaledWidth {
504 scaled: 606,
505 fitted: 576,
506 }),
507 blue_provenance: Some(BlueProvenance {
508 index: 0,
509 is_shoot: false,
510 }),
511 link_ix: Some(1),
512 serif_ix: None,
513 scale: 0,
514 first_ix: 2,
515 last_ix: 2,
516 },
517 Edge {
518 fpos: 647,
519 opos: 663,
520 pos: 663,
521 flags: TopoFlags::NORMAL,
522 dir: Direction::Right,
523 blue_edge: None,
524 blue_provenance: None,
525 link_ix: None,
526 serif_ix: Some(2),
527 scale: 0,
528 first_ix: 1,
529 last_ix: 1,
530 },
531 ];
532 check_edges(
533 font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS,
534 GlyphId::new(9),
535 style::StyleClass::HEBR,
536 &expected_h_edges,
537 &expected_v_edges,
538 );
539 }
540
541 #[test]
542 fn edges_cjk() {
543 let expected_h_edges = [
544 Edge {
545 fpos: 138,
546 opos: 141,
547 pos: 141,
548 flags: TopoFlags::NORMAL,
549 dir: Direction::Up,
550 blue_edge: None,
551 blue_provenance: None,
552 link_ix: Some(1),
553 serif_ix: None,
554 scale: 0,
555 first_ix: 8,
556 last_ix: 8,
557 },
558 Edge {
559 fpos: 201,
560 opos: 206,
561 pos: 206,
562 flags: TopoFlags::NORMAL,
563 dir: Direction::Down,
564 blue_edge: None,
565 blue_provenance: None,
566 link_ix: Some(0),
567 serif_ix: None,
568 scale: 0,
569 first_ix: 7,
570 last_ix: 7,
571 },
572 Edge {
573 fpos: 458,
574 opos: 469,
575 pos: 469,
576 flags: TopoFlags::NORMAL,
577 dir: Direction::Down,
578 blue_edge: None,
579 blue_provenance: None,
580 link_ix: None,
581 serif_ix: None,
582 scale: 0,
583 first_ix: 2,
584 last_ix: 2,
585 },
586 Edge {
587 fpos: 569,
588 opos: 583,
589 pos: 583,
590 flags: TopoFlags::NORMAL,
591 dir: Direction::Down,
592 blue_edge: None,
593 blue_provenance: None,
594 link_ix: None,
595 serif_ix: None,
596 scale: 0,
597 first_ix: 6,
598 last_ix: 6,
599 },
600 Edge {
601 fpos: 670,
602 opos: 686,
603 pos: 686,
604 flags: TopoFlags::NORMAL,
605 dir: Direction::Up,
606 blue_edge: None,
607 blue_provenance: None,
608 link_ix: Some(6),
609 serif_ix: None,
610 scale: 0,
611 first_ix: 1,
612 last_ix: 1,
613 },
614 Edge {
615 fpos: 693,
616 opos: 710,
617 pos: 710,
618 flags: TopoFlags::NORMAL,
619 dir: Direction::Up,
620 blue_edge: None,
621 blue_provenance: None,
622 link_ix: None,
623 serif_ix: Some(7),
624 scale: 0,
625 first_ix: 4,
626 last_ix: 4,
627 },
628 Edge {
629 fpos: 731,
630 opos: 749,
631 pos: 749,
632 flags: TopoFlags::NORMAL,
633 dir: Direction::Down,
634 blue_edge: None,
635 blue_provenance: None,
636 link_ix: Some(4),
637 serif_ix: None,
638 scale: 0,
639 first_ix: 0,
640 last_ix: 0,
641 },
642 Edge {
643 fpos: 849,
644 opos: 869,
645 pos: 869,
646 flags: TopoFlags::NORMAL,
647 dir: Direction::Up,
648 blue_edge: None,
649 blue_provenance: None,
650 link_ix: Some(8),
651 serif_ix: None,
652 scale: 0,
653 first_ix: 5,
654 last_ix: 5,
655 },
656 Edge {
657 fpos: 911,
658 opos: 933,
659 pos: 933,
660 flags: TopoFlags::NORMAL,
661 dir: Direction::Down,
662 blue_edge: None,
663 blue_provenance: None,
664 link_ix: Some(7),
665 serif_ix: None,
666 scale: 0,
667 first_ix: 3,
668 last_ix: 3,
669 },
670 ];
671 let expected_v_edges = [
672 Edge {
673 fpos: -78,
674 opos: -80,
675 pos: -80,
676 flags: TopoFlags::ROUND,
677 dir: Direction::Left,
678 blue_edge: Some(ScaledWidth {
679 scaled: -80,
680 fitted: -64,
681 }),
682 blue_provenance: Some(BlueProvenance {
683 index: 1,
684 is_shoot: false,
685 }),
686 link_ix: None,
687 serif_ix: None,
688 scale: 0,
689 first_ix: 8,
690 last_ix: 8,
691 },
692 Edge {
693 fpos: 3,
694 opos: 3,
695 pos: 3,
696 flags: TopoFlags::ROUND,
697 dir: Direction::Right,
698 blue_edge: None,
699 blue_provenance: None,
700 link_ix: None,
701 serif_ix: None,
702 scale: 0,
703 first_ix: 4,
704 last_ix: 4,
705 },
706 Edge {
707 fpos: 133,
708 opos: 136,
709 pos: 136,
710 flags: TopoFlags::ROUND,
711 dir: Direction::Left,
712 blue_edge: None,
713 blue_provenance: None,
714 link_ix: None,
715 serif_ix: None,
716 scale: 0,
717 first_ix: 2,
718 last_ix: 2,
719 },
720 Edge {
721 fpos: 547,
722 opos: 560,
723 pos: 560,
724 flags: TopoFlags::NORMAL,
725 dir: Direction::Left,
726 blue_edge: None,
727 blue_provenance: None,
728 link_ix: None,
729 serif_ix: Some(5),
730 scale: 0,
731 first_ix: 6,
732 last_ix: 6,
733 },
734 Edge {
735 fpos: 576,
736 opos: 590,
737 pos: 590,
738 flags: TopoFlags::NORMAL,
739 dir: Direction::Right,
740 blue_edge: None,
741 blue_provenance: None,
742 link_ix: Some(5),
743 serif_ix: None,
744 scale: 0,
745 first_ix: 5,
746 last_ix: 5,
747 },
748 Edge {
749 fpos: 576,
750 opos: 590,
751 pos: 590,
752 flags: TopoFlags::NORMAL,
753 dir: Direction::Left,
754 blue_edge: None,
755 blue_provenance: None,
756 link_ix: Some(4),
757 serif_ix: None,
758 scale: 0,
759 first_ix: 7,
760 last_ix: 7,
761 },
762 Edge {
763 fpos: 729,
764 opos: 746,
765 pos: 746,
766 flags: TopoFlags::NORMAL,
767 dir: Direction::Left,
768 blue_edge: None,
769 blue_provenance: None,
770 link_ix: Some(7),
771 serif_ix: None,
772 scale: 0,
773 first_ix: 1,
774 last_ix: 1,
775 },
776 Edge {
777 fpos: 758,
778 opos: 776,
779 pos: 776,
780 flags: TopoFlags::NORMAL,
781 dir: Direction::Right,
782 blue_edge: None,
783 blue_provenance: None,
784 link_ix: Some(6),
785 serif_ix: None,
786 scale: 0,
787 first_ix: 0,
788 last_ix: 3,
789 },
790 Edge {
791 fpos: 788,
792 opos: 807,
793 pos: 807,
794 flags: TopoFlags::ROUND,
795 dir: Direction::Left,
796 blue_edge: None,
797 blue_provenance: None,
798 link_ix: None,
799 serif_ix: None,
800 scale: 0,
801 first_ix: 9,
802 last_ix: 9,
803 },
804 ];
805 check_edges(
806 font_test_data::NOTOSERIFTC_AUTOHINT_METRICS,
807 GlyphId::new(9),
808 style::StyleClass::HANI,
809 &expected_h_edges,
810 &expected_v_edges,
811 );
812 }
813
814 fn check_edges(
815 font_data: &[u8],
816 glyph_id: GlyphId,
817 style_class: usize,
818 expected_h_edges: &[Edge],
819 expected_v_edges: &[Edge],
820 ) {
821 let font = FontRef::new(font_data).unwrap();
822 let shaper = Shaper::new(&font, ShaperMode::Nominal);
823 let class = &style::STYLE_CLASSES[style_class];
824 let unscaled_metrics = metrics::compute_unscaled_style_metrics(
825 &shaper,
826 Default::default(),
827 class,
828 Default::default(),
829 );
830 let scale = metrics::Scale::new(
831 16.0,
832 font.head().unwrap().units_per_em() as i32,
833 Style::Normal,
834 Default::default(),
835 class.script.group,
836 );
837 let scaled_metrics =
838 metrics::scale_style_metrics(&unscaled_metrics, scale, Default::default());
839 let glyphs = font.outline_glyphs();
840 let glyph = glyphs.get(glyph_id).unwrap();
841 let mut outline = Outline::default();
842 outline.fill(&glyph, &[], Default::default()).unwrap();
843 let mut axes = [
844 Axis::new(Dimension::Horizontal, outline.orientation),
845 Axis::new(Dimension::Vertical, outline.orientation),
846 ];
847 for (dim, axis) in axes.iter_mut().enumerate() {
848 segments::compute_segments(&mut outline, axis, class.script.group);
849 segments::link_segments(
850 &outline,
851 axis,
852 scaled_metrics.axes[dim].scale,
853 class.script.group,
854 unscaled_metrics.axes[dim].max_width(),
855 );
856 compute_edges(
857 axis,
858 &scaled_metrics.axes[dim],
859 class.script.hint_top_to_bottom,
860 scaled_metrics.axes[1].scale,
861 class.script.group,
862 );
863 compute_blue_edges(
864 axis,
865 &scale,
866 &unscaled_metrics.axes[dim].blues,
867 &scaled_metrics.axes[dim].blues,
868 class.script.group,
869 );
870 }
871 assert_eq!(
872 axes[Dimension::Horizontal].edges.as_slice(),
873 expected_h_edges
874 );
875 assert_eq!(axes[Dimension::Vertical].edges.as_slice(), expected_v_edges);
876 }
877}