1use super::{
4 super::{
5 super::unscaled::UnscaledOutlineBuf,
6 shape::{ShapedCluster, Shaper},
7 style::{ScriptGroup, StyleClass},
8 },
9 ScaledWidth,
10};
11use smallvec::SmallVec;
12
13const MAX_BLUES: usize = 8;
17
18const MAX_INLINE_POINTS: usize = 256;
21
22pub(crate) type BlueOutlineBuf = UnscaledOutlineBuf<MAX_INLINE_POINTS>;
24
25const BLUE_STRING_MAX_LEN: usize = 51;
27
28#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
30#[repr(transparent)]
31pub struct BlueZones(u16);
32
33impl BlueZones {
34 pub const NONE: Self = Self(0);
41 pub const TOP: Self = Self(1 << 1);
42 pub const SUB_TOP: Self = Self(1 << 2);
43 pub const NEUTRAL: Self = Self(1 << 3);
44 pub const ADJUSTMENT: Self = Self(1 << 4);
45 pub const X_HEIGHT: Self = Self(1 << 5);
46 pub const LONG: Self = Self(1 << 6);
47 pub const HORIZONTAL: Self = Self(1 << 2);
48 pub const RIGHT: Self = Self::TOP;
49
50 pub const fn contains(self, other: Self) -> bool {
51 self.0 & other.0 == other.0
52 }
53
54 #[must_use]
57 pub(crate) const fn union(self, other: Self) -> Self {
58 Self(self.0 | other.0)
59 }
60
61 pub(crate) fn is_top_like(self) -> bool {
62 self & (Self::TOP | Self::SUB_TOP) != Self::NONE
63 }
64
65 pub fn is_top(self) -> bool {
66 self.contains(Self::TOP)
67 }
68
69 pub fn is_sub_top(self) -> bool {
70 self.contains(Self::SUB_TOP)
71 }
72
73 pub fn is_neutral(self) -> bool {
74 self.contains(Self::NEUTRAL)
75 }
76
77 pub fn is_x_height(self) -> bool {
78 self.contains(Self::X_HEIGHT)
79 }
80
81 pub(crate) fn is_long(self) -> bool {
82 self.contains(Self::LONG)
83 }
84
85 pub(crate) fn is_horizontal(self) -> bool {
86 self.contains(Self::HORIZONTAL)
87 }
88
89 pub(crate) fn is_right(self) -> bool {
90 self.contains(Self::RIGHT)
91 }
92
93 #[must_use]
94 pub fn retain_top_like_or_neutral(self) -> Self {
95 self & (Self::TOP | Self::SUB_TOP | Self::NEUTRAL)
96 }
97}
98
99impl core::ops::Not for BlueZones {
100 type Output = Self;
101
102 fn not(self) -> Self::Output {
103 Self(!self.0)
104 }
105}
106
107impl core::ops::BitOr for BlueZones {
108 type Output = Self;
109
110 fn bitor(self, rhs: Self) -> Self::Output {
111 Self(self.0 | rhs.0)
112 }
113}
114
115impl core::ops::BitOrAssign for BlueZones {
116 fn bitor_assign(&mut self, rhs: Self) {
117 self.0 |= rhs.0;
118 }
119}
120
121impl core::ops::BitAnd for BlueZones {
122 type Output = Self;
123
124 fn bitand(self, rhs: Self) -> Self::Output {
125 Self(self.0 & rhs.0)
126 }
127}
128
129impl core::ops::BitAndAssign for BlueZones {
130 fn bitand_assign(&mut self, rhs: Self) {
131 self.0 &= rhs.0;
132 }
133}
134
135#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
142pub struct UnscaledBlue {
143 pub position: i32,
145 pub overshoot: i32,
147 pub ascender: i32,
149 pub descender: i32,
151 pub zones: BlueZones,
153}
154
155pub(crate) type UnscaledBlues = SmallVec<[UnscaledBlue; MAX_BLUES]>;
156
157#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
159pub struct ScaledBlue {
160 pub position: ScaledWidth,
162 pub overshoot: ScaledWidth,
164 pub zones: BlueZones,
166 pub is_active: bool,
168}
169
170pub(crate) type ScaledBlues = SmallVec<[ScaledBlue; MAX_BLUES]>;
171
172pub(crate) fn compute_unscaled_blues(shaper: &Shaper, style: &StyleClass) -> [UnscaledBlues; 2] {
174 match style.script.group {
175 ScriptGroup::Default => [
176 Default::default(),
178 compute_default_blues(shaper, style),
179 ],
180 ScriptGroup::Cjk => compute_cjk_blues(shaper, style),
181 ScriptGroup::Indic => Default::default(),
183 }
184}
185
186fn compute_default_blues(shaper: &Shaper, style: &StyleClass) -> UnscaledBlues {
190 let mut blues = UnscaledBlues::new();
191 let (mut outline_buf, mut flats, mut rounds) = buffers();
192 let context = shaper.context();
193 let units_per_em = context.units_per_em();
194 let flat_threshold = units_per_em / 14;
195 let mut cluster_shaper = shaper.cluster_shaper(style);
196 let mut shaped_cluster = ShapedCluster::default();
197 for (blue_str, blue_zones) in style.script.blues {
199 let mut ascender = i32::MIN;
200 let mut descender = i32::MAX;
201 let mut n_flats = 0;
202 let mut n_rounds = 0;
203 if blues.len() >= MAX_BLUES {
204 continue;
205 }
206 for cluster in blue_str.split(' ') {
207 let mut best_y_extremum = if blue_zones.is_top() {
208 i32::MIN
209 } else {
210 i32::MAX
211 };
212 let mut best_is_round = false;
213 cluster_shaper.shape(cluster, &mut shaped_cluster);
214 for (glyph, y_offset) in shaped_cluster
215 .iter()
216 .filter(|g| g.id.to_u32() != 0)
217 .map(|g| (g.id, g.y_offset))
218 {
219 outline_buf.clear();
220 match context.draw_unscaled(glyph, &mut outline_buf) {
221 Some(Ok(_)) => {}
222 _ => continue,
223 }
224 let outline = outline_buf.as_ref();
225 if outline.points.len() <= 2 {
227 continue;
228 }
229 let mut best_y: Option<i32> = None;
230 let best_contour_and_point = if blue_zones.is_top_like() {
233 outline.find_last_contour(|point| {
234 if best_y.is_none() || Some(point.y) > best_y {
235 best_y = Some(point.y);
236 ascender = ascender.max(point.y + y_offset);
237 true
238 } else {
239 descender = descender.min(point.y + y_offset);
240 false
241 }
242 })
243 } else {
244 outline.find_last_contour(|point| {
245 if best_y.is_none() || Some(point.y) < best_y {
246 best_y = Some(point.y);
247 descender = descender.min(point.y + y_offset);
248 true
249 } else {
250 ascender = ascender.max(point.y + y_offset);
251 false
252 }
253 })
254 };
255 let Some((best_contour_range, best_point_ix)) = best_contour_and_point else {
256 continue;
257 };
258 let best_contour = &outline.points[best_contour_range];
259 let Some(mut best_y) = best_y else {
260 continue;
261 };
262 let best_x = best_contour[best_point_ix].x;
263 let [mut on_point_first, mut on_point_last] =
266 if best_contour[best_point_ix].is_on_curve() {
267 [Some(best_point_ix); 2]
268 } else {
269 [None; 2]
270 };
271 let mut segment_first = best_point_ix;
272 let mut segment_last = best_point_ix;
273 for (ix, prev) in cycle_backward(best_contour, best_point_ix) {
277 let dist = (prev.y - best_y).abs();
278 if dist > 5 && ((prev.x - best_x).abs() <= (20 * dist)) {
280 break;
281 }
282 segment_first = ix;
283 if prev.is_on_curve() {
284 on_point_first = Some(ix);
285 if on_point_last.is_none() {
286 on_point_last = Some(ix);
287 }
288 }
289 }
290 let mut next_ix = 0;
291 for (ix, next) in cycle_forward(best_contour, best_point_ix) {
292 next_ix = ix;
295 let dist = (next.y - best_y).abs();
296 if dist > 5 && ((next.x - best_x).abs() <= (20 * dist)) {
298 break;
299 }
300 segment_last = ix;
301 if next.is_on_curve() {
302 on_point_last = Some(ix);
303 if on_point_first.is_none() {
304 on_point_first = Some(ix);
305 }
306 }
307 }
308 if blue_zones.is_long() {
309 let length_threshold = units_per_em / 25;
328 let dist = (best_contour[segment_last].x - best_contour[segment_first].x).abs();
329 if dist < length_threshold
330 && satisfies_min_long_segment_len(
331 segment_first,
332 segment_last,
333 best_contour.len() - 1,
334 )
335 {
336 let height_threshold = units_per_em / 4;
338 let mut prev_ix = best_point_ix;
340 for (ix, prev) in cycle_backward(best_contour, best_point_ix) {
341 if prev.x != best_x {
342 prev_ix = ix;
343 break;
344 }
345 }
346 if prev_ix == best_point_ix {
348 continue;
349 }
350 let is_ltr = (best_contour[prev_ix].x) < best_x;
351 let mut first = segment_last;
352 let mut last = first;
353 let mut p_first = None;
354 let mut p_last = None;
355 let mut hit = false;
356 loop {
357 if !hit {
358 first = last;
360 if best_contour[first].is_on_curve() {
362 p_first = Some(first);
363 p_last = Some(first);
364 } else {
365 p_first = None;
366 p_last = None;
367 }
368 hit = true;
369 }
370 if last < best_contour.len() - 1 {
371 last += 1;
372 } else {
373 last = 0;
374 }
375 if (best_y - best_contour[first].y).abs() > height_threshold {
376 hit = false;
378 if last == segment_first {
379 break;
380 }
381 continue;
382 }
383 let dist = (best_contour[last].y - best_contour[first].y).abs();
384 if dist > 5
385 && (best_contour[last].x - best_contour[first].x).abs() <= 20 * dist
386 {
387 hit = false;
388 if last == segment_first {
389 break;
390 }
391 continue;
392 }
393 if best_contour[last].is_on_curve() {
394 p_last = Some(last);
395 if p_first.is_none() {
396 p_first = Some(last);
397 }
398 }
399 let first_x = best_contour[first].x;
400 let last_x = best_contour[last].x;
401 let is_cur_ltr = first_x < last_x;
402 let dx = (last_x - first_x).abs();
403 if is_cur_ltr == is_ltr && dx >= length_threshold {
404 loop {
405 if last < best_contour.len() - 1 {
406 last += 1;
407 } else {
408 last = 0;
409 }
410 let dy = (best_contour[last].y - best_contour[first].y).abs();
411 if dy > 5
412 && (best_contour[next_ix].x - best_contour[first].x).abs()
413 <= 20 * dist
414 {
415 if last > 0 {
416 last -= 1;
417 } else {
418 last = best_contour.len() - 1;
419 }
420 break;
421 }
422 p_last = Some(last);
423 if best_contour[last].is_on_curve() {
424 p_last = Some(last);
425 if p_first.is_none() {
426 p_first = Some(last);
427 }
428 }
429 if last == segment_first {
430 break;
431 }
432 }
433 best_y = best_contour[first].y;
434 segment_first = first;
435 segment_last = last;
436 on_point_first = p_first;
437 on_point_last = p_last;
438 break;
439 }
440 if last == segment_first {
441 break;
442 }
443 }
444 }
445 }
446 best_y += y_offset;
447 let is_round = match (on_point_first, on_point_last) {
453 (Some(first), Some(last))
454 if (best_contour[last].x - best_contour[first].x).abs()
455 > flat_threshold =>
456 {
457 false
458 }
459 _ => {
460 !best_contour[segment_first].is_on_curve()
461 || !best_contour[segment_last].is_on_curve()
462 }
463 };
464 if is_round && blue_zones.is_neutral() {
465 continue;
467 }
468 if blue_zones.is_top() {
470 if best_y > best_y_extremum {
471 best_y_extremum = best_y;
472 best_is_round = is_round;
473 }
474 } else if best_y < best_y_extremum {
475 best_y_extremum = best_y;
476 best_is_round = is_round;
477 }
478 }
479 if best_y_extremum != i32::MIN && best_y_extremum != i32::MAX {
480 if best_is_round {
481 if let Some(round) = rounds.get_mut(n_rounds) {
482 *round = best_y_extremum;
483 n_rounds += 1;
484 }
485 } else if let Some(flat) = flats.get_mut(n_flats) {
486 *flat = best_y_extremum;
487 n_flats += 1;
488 }
489 }
490 }
491 if n_flats == 0 && n_rounds == 0 {
492 continue;
493 }
494 rounds[..n_rounds].sort_unstable();
495 flats[..n_flats].sort_unstable();
496 let (mut blue_ref, mut blue_shoot) = if n_flats == 0 {
497 let val = rounds[n_rounds / 2];
498 (val, val)
499 } else if n_rounds == 0 {
500 let val = flats[n_flats / 2];
501 (val, val)
502 } else {
503 (flats[n_flats / 2], rounds[n_rounds / 2])
504 };
505 if blue_shoot != blue_ref {
506 let over_ref = blue_shoot > blue_ref;
507 if blue_zones.is_top_like() ^ over_ref {
508 let val = (blue_shoot + blue_ref) / 2;
509 blue_ref = val;
510 blue_shoot = val;
511 }
512 }
513 let mut blue = UnscaledBlue {
514 position: blue_ref,
515 overshoot: blue_shoot,
516 ascender,
517 descender,
518 zones: blue_zones.retain_top_like_or_neutral(),
519 };
520 if blue_zones.is_x_height() {
521 blue.zones |= BlueZones::ADJUSTMENT;
522 }
523 blues.push(blue);
524 }
525 let mut sorted_indices: [usize; MAX_BLUES] = core::array::from_fn(|ix| ix);
527 let blue_values = blues.as_mut_slice();
528 let len = blue_values.len();
529 if len == 0 {
530 return blues;
531 }
532 for i in 1..len {
534 for j in (1..=i).rev() {
535 let first = &blue_values[sorted_indices[j - 1]];
536 let second = &blue_values[sorted_indices[j]];
537 let a = if first.zones.is_top_like() {
538 first.position
539 } else {
540 first.overshoot
541 };
542 let b = if second.zones.is_top_like() {
543 second.position
544 } else {
545 second.overshoot
546 };
547 if b >= a {
548 break;
549 }
550 sorted_indices.swap(j, j - 1);
551 }
552 }
553 for i in 0..len - 1 {
555 let index1 = sorted_indices[i];
556 let index2 = sorted_indices[i + 1];
557 let first = &blue_values[index1];
558 let second = &blue_values[index2];
559 let a = if first.zones.is_top_like() {
560 first.overshoot
561 } else {
562 first.position
563 };
564 let b = if second.zones.is_top_like() {
565 second.overshoot
566 } else {
567 second.position
568 };
569 if a > b {
570 if first.zones.is_top_like() {
571 blue_values[index1].overshoot = b;
572 } else {
573 blue_values[index1].position = b;
574 }
575 }
576 }
577 blues
578}
579
580fn satisfies_min_long_segment_len(first_ix: usize, last_ix: usize, contour_last: usize) -> bool {
584 let inclusive_diff = if first_ix <= last_ix {
585 last_ix - first_ix
586 } else {
587 contour_last - first_ix + 1 + last_ix
593 };
594 inclusive_diff + 2 <= contour_last
598}
599
600fn compute_cjk_blues(shaper: &Shaper, style: &StyleClass) -> [UnscaledBlues; 2] {
610 let mut blues = [UnscaledBlues::new(), UnscaledBlues::new()];
611 let (mut outline_buf, mut flats, mut fills) = buffers();
612 let context = shaper.context();
613 let mut cluster_shaper = shaper.cluster_shaper(style);
614 let mut shaped_cluster = ShapedCluster::default();
615 for (blue_str, blue_zones) in style.script.blues {
617 let is_horizontal = blue_zones.is_horizontal();
618 if is_horizontal {
623 continue;
624 }
625 let is_right = blue_zones.is_right();
626 let is_top = blue_zones.is_top();
627 let blues = &mut blues[!is_horizontal as usize];
628 if blues.len() >= MAX_BLUES {
629 continue;
630 }
631 let mut n_flats = 0;
632 let mut n_fills = 0;
633 let mut is_fill = true;
634 for cluster in blue_str.split(' ') {
635 if cluster == "|" {
639 is_fill = false;
640 continue;
641 }
642 cluster_shaper.shape(cluster, &mut shaped_cluster);
643 for glyph in shaped_cluster
644 .iter()
645 .filter(|g| g.id.to_u32() != 0)
646 .map(|g| g.id)
647 {
648 outline_buf.clear();
649 match context.draw_unscaled(glyph, &mut outline_buf) {
650 Some(Ok(_)) => {}
651 _ => continue,
652 }
653 let outline = outline_buf.as_ref();
654 if outline.points.len() <= 2 {
656 continue;
657 }
658 let best_pos = outline
660 .points
661 .iter()
662 .map(|p| if is_horizontal { p.x } else { p.y })
663 .reduce(
664 if (is_horizontal && is_right) || (!is_horizontal && is_top) {
665 |a: i32, c: i32| a.max(c)
666 } else {
667 |a: i32, c: i32| a.min(c)
668 },
669 );
670 let Some(best_pos) = best_pos else {
671 continue;
672 };
673 if is_fill {
674 if let Some(fill) = fills.get_mut(n_fills) {
675 *fill = best_pos;
676 n_fills += 1;
677 }
678 } else if let Some(flat) = flats.get_mut(n_flats) {
679 *flat = best_pos;
680 n_flats += 1;
681 }
682 }
683 }
684 if n_flats == 0 && n_fills == 0 {
685 continue;
686 }
687 fills[..n_fills].sort_unstable();
690 flats[..n_flats].sort_unstable();
691 let (mut blue_ref, mut blue_shoot) = if n_flats == 0 {
692 let value = fills[n_fills / 2];
693 (value, value)
694 } else if n_fills == 0 {
695 let value = flats[n_flats / 2];
696 (value, value)
697 } else {
698 (fills[n_fills / 2], flats[n_flats / 2])
699 };
700 if blue_shoot != blue_ref {
703 let under_ref = blue_shoot < blue_ref;
704 if blue_zones.is_top() ^ under_ref {
705 blue_ref = (blue_shoot + blue_ref) / 2;
706 blue_shoot = blue_ref;
707 }
708 }
709 blues.push(UnscaledBlue {
710 position: blue_ref,
711 overshoot: blue_shoot,
712 ascender: 0,
713 descender: 0,
714 zones: *blue_zones & BlueZones::TOP,
715 });
716 }
717 blues
718}
719
720#[inline(always)]
721fn buffers<T: Copy + Default>() -> (
722 BlueOutlineBuf,
723 [T; BLUE_STRING_MAX_LEN],
724 [T; BLUE_STRING_MAX_LEN],
725) {
726 (
727 BlueOutlineBuf::new(),
728 [T::default(); BLUE_STRING_MAX_LEN],
729 [T::default(); BLUE_STRING_MAX_LEN],
730 )
731}
732
733pub(super) fn cycle_forward<T>(items: &[T], start: usize) -> impl Iterator<Item = (usize, &T)> {
736 let len = items.len();
737 let start = start + 1;
738 (0..len).map(move |ix| {
739 let real_ix = (ix + start) % len;
740 (real_ix, &items[real_ix])
741 })
742}
743
744pub(super) fn cycle_backward<T>(items: &[T], start: usize) -> impl Iterator<Item = (usize, &T)> {
747 let len = items.len();
748 (0..len).rev().map(move |ix| {
749 let real_ix = (ix + start) % len;
750 (real_ix, &items[real_ix])
751 })
752}
753
754#[cfg(test)]
755mod tests {
756 use crate::outline::autohint::context::FontRefContext;
757 use crate::outline::autohint::metrics::BlueZones;
758
759 use super::{
760 super::super::{
761 shape::{Shaper, ShaperMode},
762 style,
763 },
764 satisfies_min_long_segment_len, UnscaledBlue,
765 };
766 use raw::{types::Tag, FontRef};
767
768 #[test]
769 fn latin_blues() {
770 let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
771 let context = FontRefContext::new(&font, &[]);
772 let shaper = Shaper::new(&context, ShaperMode::Nominal);
773 let style = &style::STYLE_CLASSES[super::StyleClass::LATN];
774 let blues = super::compute_default_blues(&shaper, style);
775 let values = blues.as_slice();
776 let expected = [
777 UnscaledBlue {
778 position: 714,
779 overshoot: 725,
780 ascender: 725,
781 descender: -230,
782 zones: BlueZones::TOP,
783 },
784 UnscaledBlue {
785 position: 0,
786 overshoot: -10,
787 ascender: 725,
788 descender: -10,
789 zones: BlueZones::default(),
790 },
791 UnscaledBlue {
792 position: 760,
793 overshoot: 760,
794 ascender: 770,
795 descender: -240,
796 zones: BlueZones::TOP,
797 },
798 UnscaledBlue {
799 position: 536,
800 overshoot: 546,
801 ascender: 546,
802 descender: -10,
803 zones: BlueZones::TOP | BlueZones::ADJUSTMENT,
804 },
805 UnscaledBlue {
806 position: 0,
807 overshoot: -10,
808 ascender: 546,
809 descender: -10,
810 zones: BlueZones::default(),
811 },
812 UnscaledBlue {
813 position: -240,
814 overshoot: -240,
815 ascender: 760,
816 descender: -240,
817 zones: BlueZones::default(),
818 },
819 ];
820 assert_eq!(values, &expected);
821 }
822
823 #[test]
824 fn hebrew_long_blues() {
825 let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
826 let context = FontRefContext::new(&font, &[]);
827 let shaper = Shaper::new(&context, ShaperMode::Nominal);
828 let style = &style::STYLE_CLASSES[super::StyleClass::HEBR];
830 let blues = super::compute_default_blues(&shaper, style);
831 let values = blues.as_slice();
832 assert_eq!(values.len(), 3);
833 let expected = [
834 UnscaledBlue {
835 position: 592,
836 overshoot: 592,
837 ascender: 647,
838 descender: -240,
839 zones: BlueZones::TOP,
840 },
841 UnscaledBlue {
842 position: 0,
843 overshoot: -9,
844 ascender: 647,
845 descender: -9,
846 zones: BlueZones::default(),
847 },
848 UnscaledBlue {
849 position: -240,
850 overshoot: -240,
851 ascender: 647,
852 descender: -240,
853 zones: BlueZones::default(),
854 },
855 ];
856 assert_eq!(values, &expected);
857 }
858
859 #[test]
860 fn cjk_blues() {
861 let font = FontRef::new(font_test_data::NOTOSERIFTC_AUTOHINT_METRICS).unwrap();
862 let context = FontRefContext::new(&font, &[]);
863 let shaper = Shaper::new(&context, ShaperMode::Nominal);
864 let style = &style::STYLE_CLASSES[super::StyleClass::HANI];
865 let blues = super::compute_cjk_blues(&shaper, style);
866 let values = blues[1].as_slice();
867 let expected = [
868 UnscaledBlue {
869 position: 837,
870 overshoot: 824,
871 ascender: 0,
872 descender: 0,
873 zones: BlueZones::TOP,
874 },
875 UnscaledBlue {
876 position: -78,
877 overshoot: -66,
878 ascender: 0,
879 descender: 0,
880 zones: BlueZones::default(),
881 },
882 ];
883 assert_eq!(values, &expected);
884 }
885
886 #[test]
887 fn c2sc_shaped_blues() {
888 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
889 let context = FontRefContext::new(&font, &[]);
890 let shaper = Shaper::new(&context, ShaperMode::BestEffort);
891 let style = &style::STYLE_CLASSES[super::StyleClass::LATN_C2SC];
892 let blues = super::compute_default_blues(&shaper, style);
893 let values = blues.as_slice();
894 let expected = [
896 UnscaledBlue {
897 position: 571,
898 overshoot: 571,
899 ascender: 571,
900 descender: 0,
901 zones: BlueZones::TOP,
902 },
903 UnscaledBlue {
904 position: 0,
905 overshoot: 0,
906 ascender: 571,
907 descender: 0,
908 zones: BlueZones::default(),
909 },
910 ];
911 assert_eq!(values, &expected);
912 }
913
914 #[test]
917 fn long_segment_len_avoid_overflow() {
918 assert!(satisfies_min_long_segment_len(22, 0, 22));
924 }
925
926 #[test]
927 fn cycle_iter_forward() {
928 let items = [0, 1, 2, 3, 4, 5, 6, 7];
929 let from_5 = super::cycle_forward(&items, 5)
930 .map(|(_, val)| *val)
931 .collect::<Vec<_>>();
932 assert_eq!(from_5, &[6, 7, 0, 1, 2, 3, 4, 5]);
933 let from_last = super::cycle_forward(&items, 7)
934 .map(|(_, val)| *val)
935 .collect::<Vec<_>>();
936 assert_eq!(from_last, &items);
937 let _ = super::cycle_forward::<i32>(&[], 5).count();
939 }
940
941 #[test]
942 fn cycle_iter_backward() {
943 let items = [0, 1, 2, 3, 4, 5, 6, 7];
944 let from_5 = super::cycle_backward(&items, 5)
945 .map(|(_, val)| *val)
946 .collect::<Vec<_>>();
947 assert_eq!(from_5, &[4, 3, 2, 1, 0, 7, 6, 5]);
948 let from_0 = super::cycle_backward(&items, 0)
949 .map(|(_, val)| *val)
950 .collect::<Vec<_>>();
951 assert_eq!(from_0, &[7, 6, 5, 4, 3, 2, 1, 0]);
952 let _ = super::cycle_backward::<i32>(&[], 5).count();
954 }
955
956 #[test]
957 fn blue_string_cluster_count_saturates() {
958 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
959 let context = FontRefContext::new(&font, &[]);
960 let shaper = Shaper::new(&context, ShaperMode::Nominal);
961 let style_51 = synthetic_default_style(1, super::BLUE_STRING_MAX_LEN);
962 let style_70 = synthetic_default_style(1, super::BLUE_STRING_MAX_LEN + 19);
963 let blues_51 = super::compute_default_blues(&shaper, &style_51);
964 let blues_70 = super::compute_default_blues(&shaper, &style_70);
965 assert_eq!(blues_70, blues_51);
966 }
967
968 #[test]
969 fn blue_count_saturates_at_max_blues() {
970 let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
971 let context = FontRefContext::new(&font, &[]);
972 let shaper = Shaper::new(&context, ShaperMode::Nominal);
973 let known_good_blues = style::STYLE_CLASSES[super::StyleClass::LATN].script.blues;
974 let mut repeated = Vec::with_capacity(super::MAX_BLUES + 4);
975 for i in 0..(super::MAX_BLUES + 4) {
976 repeated.push(known_good_blues[i % known_good_blues.len()]);
977 }
978 let style = synthetic_style_with_blues(repeated);
979 let blues = super::compute_default_blues(&shaper, &style);
980 assert_eq!(blues.len(), super::MAX_BLUES);
981 }
982
983 fn synthetic_default_style(num_blues: usize, clusters_per_blue: usize) -> style::StyleClass {
984 let cluster = (0..clusters_per_blue)
985 .map(|_| "o")
986 .collect::<Vec<_>>()
987 .join(" ");
988 let cluster: &'static str = Box::leak(cluster.into_boxed_str());
989 let mut blues = Vec::with_capacity(num_blues);
990 for _ in 0..num_blues {
991 blues.push((cluster, BlueZones::TOP));
992 }
993 synthetic_style_with_blues(blues)
994 }
995
996 fn synthetic_style_with_blues(blues: Vec<(&'static str, BlueZones)>) -> style::StyleClass {
997 let blues: &'static [(&'static str, BlueZones)] = Box::leak(blues.into_boxed_slice());
998 let script = Box::leak(Box::new(style::ScriptClass {
999 name: "synthetic",
1000 group: style::ScriptGroup::Default,
1001 tag: Tag::new(b"DFLT"),
1002 hint_top_to_bottom: true,
1003 std_chars: "o",
1004 blues,
1005 }));
1006 style::StyleClass {
1007 name: "synthetic",
1008 index: 0,
1009 script,
1010 feature: None,
1011 }
1012 }
1013}