1use super::{
4 super::{
5 super::{unscaled::UnscaledOutlineBuf, OutlineGlyphCollection},
6 shape::{ShapedCluster, Shaper},
7 style::{ScriptGroup, StyleClass},
8 },
9 ScaledWidth,
10};
11use crate::{collections::SmallVec, FontRef, MetadataProvider};
12use raw::types::F2Dot14;
13use raw::TableProvider;
14
15const MAX_BLUES: usize = 8;
19
20const MAX_INLINE_POINTS: usize = 256;
23
24const BLUE_STRING_MAX_LEN: usize = 51;
26
27#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
29#[repr(transparent)]
30pub(crate) struct BlueZones(u16);
31
32impl BlueZones {
33 pub const NONE: Self = Self(0);
40 pub const TOP: Self = Self(1 << 1);
41 pub const SUB_TOP: Self = Self(1 << 2);
42 pub const NEUTRAL: Self = Self(1 << 3);
43 pub const ADJUSTMENT: Self = Self(1 << 4);
44 pub const X_HEIGHT: Self = Self(1 << 5);
45 pub const LONG: Self = Self(1 << 6);
46 pub const HORIZONTAL: Self = Self(1 << 2);
47 pub const RIGHT: Self = Self::TOP;
48
49 pub const fn contains(self, other: Self) -> bool {
50 self.0 & other.0 == other.0
51 }
52
53 #[must_use]
56 pub const fn union(self, other: Self) -> Self {
57 Self(self.0 | other.0)
58 }
59
60 pub fn is_top_like(self) -> bool {
61 self & (Self::TOP | Self::SUB_TOP) != Self::NONE
62 }
63
64 pub fn is_top(self) -> bool {
65 self.contains(Self::TOP)
66 }
67
68 pub fn is_sub_top(self) -> bool {
69 self.contains(Self::SUB_TOP)
70 }
71
72 pub fn is_neutral(self) -> bool {
73 self.contains(Self::NEUTRAL)
74 }
75
76 pub fn is_x_height(self) -> bool {
77 self.contains(Self::X_HEIGHT)
78 }
79
80 pub fn is_long(self) -> bool {
81 self.contains(Self::LONG)
82 }
83
84 pub fn is_horizontal(self) -> bool {
85 self.contains(Self::HORIZONTAL)
86 }
87
88 pub fn is_right(self) -> bool {
89 self.contains(Self::RIGHT)
90 }
91
92 #[must_use]
93 pub fn retain_top_like_or_neutral(self) -> Self {
94 self & (Self::TOP | Self::SUB_TOP | Self::NEUTRAL)
95 }
96}
97
98impl core::ops::Not for BlueZones {
99 type Output = Self;
100
101 fn not(self) -> Self::Output {
102 Self(!self.0)
103 }
104}
105
106impl core::ops::BitOr for BlueZones {
107 type Output = Self;
108
109 fn bitor(self, rhs: Self) -> Self::Output {
110 Self(self.0 | rhs.0)
111 }
112}
113
114impl core::ops::BitOrAssign for BlueZones {
115 fn bitor_assign(&mut self, rhs: Self) {
116 self.0 |= rhs.0;
117 }
118}
119
120impl core::ops::BitAnd for BlueZones {
121 type Output = Self;
122
123 fn bitand(self, rhs: Self) -> Self::Output {
124 Self(self.0 & rhs.0)
125 }
126}
127
128impl core::ops::BitAndAssign for BlueZones {
129 fn bitand_assign(&mut self, rhs: Self) {
130 self.0 &= rhs.0;
131 }
132}
133
134#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
140pub(crate) struct UnscaledBlue {
141 pub position: i32,
142 pub overshoot: i32,
143 pub ascender: i32,
144 pub descender: i32,
145 pub zones: BlueZones,
146}
147
148pub(crate) type UnscaledBlues = SmallVec<UnscaledBlue, MAX_BLUES>;
149
150#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
151pub(crate) struct ScaledBlue {
152 pub position: ScaledWidth,
153 pub overshoot: ScaledWidth,
154 pub zones: BlueZones,
155 pub is_active: bool,
156}
157
158pub(crate) type ScaledBlues = SmallVec<ScaledBlue, MAX_BLUES>;
159
160pub(crate) fn compute_unscaled_blues(
162 shaper: &Shaper,
163 coords: &[F2Dot14],
164 style: &StyleClass,
165) -> [UnscaledBlues; 2] {
166 match style.script.group {
167 ScriptGroup::Default => [
168 Default::default(),
170 compute_default_blues(shaper, coords, style),
171 ],
172 ScriptGroup::Cjk => compute_cjk_blues(shaper, coords, style),
173 ScriptGroup::Indic => Default::default(),
175 }
176}
177
178fn compute_default_blues(shaper: &Shaper, coords: &[F2Dot14], style: &StyleClass) -> UnscaledBlues {
182 let mut blues = UnscaledBlues::new();
183 let (mut outline_buf, mut flats, mut rounds) = buffers();
184 let (glyphs, units_per_em) = things_all_blues_need(shaper.font());
185 let flat_threshold = units_per_em / 14;
186 let mut cluster_shaper = shaper.cluster_shaper(style);
187 let mut shaped_cluster = ShapedCluster::default();
188 for (blue_str, blue_zones) in style.script.blues {
190 let mut ascender = i32::MIN;
191 let mut descender = i32::MAX;
192 let mut n_flats = 0;
193 let mut n_rounds = 0;
194 for cluster in blue_str.split(' ') {
195 let mut best_y_extremum = if blue_zones.is_top() {
196 i32::MIN
197 } else {
198 i32::MAX
199 };
200 let mut best_is_round = false;
201 cluster_shaper.shape(cluster, &mut shaped_cluster);
202 for (glyph, y_offset) in shaped_cluster
203 .iter()
204 .filter(|g| g.id.to_u32() != 0)
205 .filter_map(|g| Some((glyphs.get(g.id)?, g.y_offset)))
206 {
207 outline_buf.clear();
208 if glyph.draw_unscaled(coords, None, &mut outline_buf).is_err() {
209 continue;
210 }
211 let outline = outline_buf.as_ref();
212 if outline.points.len() <= 2 {
214 continue;
215 }
216 let mut best_y: Option<i16> = None;
217 let best_contour_and_point = if blue_zones.is_top_like() {
220 outline.find_last_contour(|point| {
221 if best_y.is_none() || Some(point.y) > best_y {
222 best_y = Some(point.y);
223 ascender = ascender.max(point.y as i32 + y_offset);
224 true
225 } else {
226 descender = descender.min(point.y as i32 + y_offset);
227 false
228 }
229 })
230 } else {
231 outline.find_last_contour(|point| {
232 if best_y.is_none() || Some(point.y) < best_y {
233 best_y = Some(point.y);
234 descender = descender.min(point.y as i32 + y_offset);
235 true
236 } else {
237 ascender = ascender.max(point.y as i32 + y_offset);
238 false
239 }
240 })
241 };
242 let Some((best_contour_range, best_point_ix)) = best_contour_and_point else {
243 continue;
244 };
245 let best_contour = &outline.points[best_contour_range];
246 let mut best_y = best_y.unwrap() as i32;
249 let best_x = best_contour[best_point_ix].x as i32;
250 let [mut on_point_first, mut on_point_last] =
253 if best_contour[best_point_ix].is_on_curve() {
254 [Some(best_point_ix); 2]
255 } else {
256 [None; 2]
257 };
258 let mut segment_first = best_point_ix;
259 let mut segment_last = best_point_ix;
260 for (ix, prev) in cycle_backward(best_contour, best_point_ix) {
264 let dist = (prev.y as i32 - best_y).abs();
265 if dist > 5 && ((prev.x as i32 - best_x).abs() <= (20 * dist)) {
267 break;
268 }
269 segment_first = ix;
270 if prev.is_on_curve() {
271 on_point_first = Some(ix);
272 if on_point_last.is_none() {
273 on_point_last = Some(ix);
274 }
275 }
276 }
277 let mut next_ix = 0;
278 for (ix, next) in cycle_forward(best_contour, best_point_ix) {
279 next_ix = ix;
282 let dist = (next.y as i32 - best_y).abs();
283 if dist > 5 && ((next.x as i32 - best_x).abs() <= (20 * dist)) {
285 break;
286 }
287 segment_last = ix;
288 if next.is_on_curve() {
289 on_point_last = Some(ix);
290 if on_point_first.is_none() {
291 on_point_first = Some(ix);
292 }
293 }
294 }
295 if blue_zones.is_long() {
296 let length_threshold = units_per_em / 25;
315 let dist = (best_contour[segment_last].x as i32
316 - best_contour[segment_first].x as i32)
317 .abs();
318 if dist < length_threshold
319 && satisfies_min_long_segment_len(
320 segment_first,
321 segment_last,
322 best_contour.len() - 1,
323 )
324 {
325 let height_threshold = units_per_em / 4;
327 let mut prev_ix = best_point_ix;
329 for (ix, prev) in cycle_backward(best_contour, best_point_ix) {
330 if prev.x as i32 != best_x {
331 prev_ix = ix;
332 break;
333 }
334 }
335 if prev_ix == best_point_ix {
337 continue;
338 }
339 let is_ltr = (best_contour[prev_ix].x as i32) < best_x;
340 let mut first = segment_last;
341 let mut last = first;
342 let mut p_first = None;
343 let mut p_last = None;
344 let mut hit = false;
345 loop {
346 if !hit {
347 first = last;
349 if best_contour[first].is_on_curve() {
351 p_first = Some(first);
352 p_last = Some(first);
353 } else {
354 p_first = None;
355 p_last = None;
356 }
357 hit = true;
358 }
359 if last < best_contour.len() - 1 {
360 last += 1;
361 } else {
362 last = 0;
363 }
364 if (best_y - best_contour[first].y as i32).abs() > height_threshold {
365 hit = false;
367 continue;
368 }
369 let dist =
370 (best_contour[last].y as i32 - best_contour[first].y as i32).abs();
371 if dist > 5
372 && (best_contour[last].x as i32 - best_contour[first].x as i32)
373 .abs()
374 <= 20 * dist
375 {
376 hit = false;
377 if last == segment_first {
378 break;
379 }
380 continue;
381 }
382 if best_contour[last].is_on_curve() {
383 p_last = Some(last);
384 if p_first.is_none() {
385 p_first = Some(last);
386 }
387 }
388 let first_x = best_contour[first].x as i32;
389 let last_x = best_contour[last].x as i32;
390 let is_cur_ltr = first_x < last_x;
391 let dx = (last_x - first_x).abs();
392 if is_cur_ltr == is_ltr && dx >= length_threshold {
393 loop {
394 if last < best_contour.len() - 1 {
395 last += 1;
396 } else {
397 last = 0;
398 }
399 let dy = (best_contour[last].y as i32
400 - best_contour[first].y as i32)
401 .abs();
402 if dy > 5
403 && (best_contour[next_ix].x as i32
404 - best_contour[first].x as i32)
405 .abs()
406 <= 20 * dist
407 {
408 if last > 0 {
409 last -= 1;
410 } else {
411 last = best_contour.len() - 1;
412 }
413 break;
414 }
415 p_last = Some(last);
416 if best_contour[last].is_on_curve() {
417 p_last = Some(last);
418 if p_first.is_none() {
419 p_first = Some(last);
420 }
421 }
422 if last == segment_first {
423 break;
424 }
425 }
426 best_y = best_contour[first].y as i32;
427 segment_first = first;
428 segment_last = last;
429 on_point_first = p_first;
430 on_point_last = p_last;
431 break;
432 }
433 if last == segment_first {
434 break;
435 }
436 }
437 }
438 }
439 best_y += y_offset;
440 let is_round = match (on_point_first, on_point_last) {
446 (Some(first), Some(last))
447 if (best_contour[last].x as i32 - best_contour[first].x as i32).abs()
448 > flat_threshold =>
449 {
450 false
451 }
452 _ => {
453 !best_contour[segment_first].is_on_curve()
454 || !best_contour[segment_last].is_on_curve()
455 }
456 };
457 if is_round && blue_zones.is_neutral() {
458 continue;
460 }
461 if blue_zones.is_top() {
463 if best_y > best_y_extremum {
464 best_y_extremum = best_y;
465 best_is_round = is_round;
466 }
467 } else if best_y < best_y_extremum {
468 best_y_extremum = best_y;
469 best_is_round = is_round;
470 }
471 }
472 if best_y_extremum != i32::MIN && best_y_extremum != i32::MAX {
473 if best_is_round {
474 rounds[n_rounds] = best_y_extremum;
475 n_rounds += 1;
476 } else {
477 flats[n_flats] = best_y_extremum;
478 n_flats += 1;
479 }
480 }
481 }
482 if n_flats == 0 && n_rounds == 0 {
483 continue;
484 }
485 rounds[..n_rounds].sort_unstable();
486 flats[..n_flats].sort_unstable();
487 let (mut blue_ref, mut blue_shoot) = if n_flats == 0 {
488 let val = rounds[n_rounds / 2];
489 (val, val)
490 } else if n_rounds == 0 {
491 let val = flats[n_flats / 2];
492 (val, val)
493 } else {
494 (flats[n_flats / 2], rounds[n_rounds / 2])
495 };
496 if blue_shoot != blue_ref {
497 let over_ref = blue_shoot > blue_ref;
498 if blue_zones.is_top_like() ^ over_ref {
499 let val = (blue_shoot + blue_ref) / 2;
500 blue_ref = val;
501 blue_shoot = val;
502 }
503 }
504 let mut blue = UnscaledBlue {
505 position: blue_ref,
506 overshoot: blue_shoot,
507 ascender,
508 descender,
509 zones: blue_zones.retain_top_like_or_neutral(),
510 };
511 if blue_zones.is_x_height() {
512 blue.zones |= BlueZones::ADJUSTMENT;
513 }
514 blues.push(blue);
515 }
516 let mut sorted_indices: [usize; MAX_BLUES] = core::array::from_fn(|ix| ix);
518 let blue_values = blues.as_mut_slice();
519 let len = blue_values.len();
520 if len == 0 {
521 return blues;
522 }
523 for i in 1..len {
525 for j in (1..=i).rev() {
526 let first = &blue_values[sorted_indices[j - 1]];
527 let second = &blue_values[sorted_indices[j]];
528 let a = if first.zones.is_top_like() {
529 first.position
530 } else {
531 first.overshoot
532 };
533 let b = if second.zones.is_top_like() {
534 second.position
535 } else {
536 second.overshoot
537 };
538 if b >= a {
539 break;
540 }
541 sorted_indices.swap(j, j - 1);
542 }
543 }
544 for i in 0..len - 1 {
546 let index1 = sorted_indices[i];
547 let index2 = sorted_indices[i + 1];
548 let first = &blue_values[index1];
549 let second = &blue_values[index2];
550 let a = if first.zones.is_top_like() {
551 first.overshoot
552 } else {
553 first.position
554 };
555 let b = if second.zones.is_top_like() {
556 second.overshoot
557 } else {
558 second.position
559 };
560 if a > b {
561 if first.zones.is_top_like() {
562 blue_values[index1].overshoot = b;
563 } else {
564 blue_values[index1].position = b;
565 }
566 }
567 }
568 blues
569}
570
571fn satisfies_min_long_segment_len(first_ix: usize, last_ix: usize, contour_last: usize) -> bool {
575 let inclusive_diff = if first_ix <= last_ix {
576 last_ix - first_ix
577 } else {
578 contour_last - first_ix + 1 + last_ix
584 };
585 inclusive_diff + 2 <= contour_last
589}
590
591fn compute_cjk_blues(
601 shaper: &Shaper,
602 coords: &[F2Dot14],
603 style: &StyleClass,
604) -> [UnscaledBlues; 2] {
605 let mut blues = [UnscaledBlues::new(), UnscaledBlues::new()];
606 let (mut outline_buf, mut flats, mut fills) = buffers();
607 let (glyphs, _) = things_all_blues_need(shaper.font());
608 let mut cluster_shaper = shaper.cluster_shaper(style);
609 let mut shaped_cluster = ShapedCluster::default();
610 for (blue_str, blue_zones) in style.script.blues {
612 let is_horizontal = blue_zones.is_horizontal();
613 if is_horizontal {
618 continue;
619 }
620 let is_right = blue_zones.is_right();
621 let is_top = blue_zones.is_top();
622 let blues = &mut blues[!is_horizontal as usize];
623 if blues.len() >= MAX_BLUES {
624 continue;
625 }
626 let mut n_flats = 0;
627 let mut n_fills = 0;
628 let mut is_fill = true;
629 for cluster in blue_str.split(' ') {
630 if cluster == "|" {
634 is_fill = false;
635 continue;
636 }
637 cluster_shaper.shape(cluster, &mut shaped_cluster);
638 for glyph in shaped_cluster
639 .iter()
640 .filter(|g| g.id.to_u32() != 0)
641 .filter_map(|g| glyphs.get(g.id))
642 {
643 outline_buf.clear();
644 if glyph.draw_unscaled(coords, None, &mut outline_buf).is_err() {
645 continue;
646 }
647 let outline = outline_buf.as_ref();
648 if outline.points.len() <= 2 {
650 continue;
651 }
652 let best_pos = outline
655 .points
656 .iter()
657 .map(|p| if is_horizontal { p.x } else { p.y })
658 .reduce(
659 if (is_horizontal && is_right) || (!is_horizontal && is_top) {
660 |a: i16, c: i16| a.max(c)
661 } else {
662 |a: i16, c: i16| a.min(c)
663 },
664 )
665 .unwrap();
666 if is_fill {
667 fills[n_fills] = best_pos;
668 n_fills += 1;
669 } else {
670 flats[n_flats] = best_pos;
671 n_flats += 1;
672 }
673 }
674 }
675 if n_flats == 0 && n_fills == 0 {
676 continue;
677 }
678 fills[..n_fills].sort_unstable();
681 flats[..n_flats].sort_unstable();
682 let (mut blue_ref, mut blue_shoot) = if n_flats == 0 {
683 let value = fills[n_fills / 2] as i32;
684 (value, value)
685 } else if n_fills == 0 {
686 let value = flats[n_flats / 2] as i32;
687 (value, value)
688 } else {
689 (fills[n_fills / 2] as i32, flats[n_flats / 2] as i32)
690 };
691 if blue_shoot != blue_ref {
694 let under_ref = blue_shoot < blue_ref;
695 if blue_zones.is_top() ^ under_ref {
696 blue_ref = (blue_shoot + blue_ref) / 2;
697 blue_shoot = blue_ref;
698 }
699 }
700 blues.push(UnscaledBlue {
701 position: blue_ref,
702 overshoot: blue_shoot,
703 ascender: 0,
704 descender: 0,
705 zones: *blue_zones & BlueZones::TOP,
706 });
707 }
708 blues
709}
710
711#[inline(always)]
712fn buffers<T: Copy + Default>() -> (
713 UnscaledOutlineBuf<MAX_INLINE_POINTS>,
714 [T; BLUE_STRING_MAX_LEN],
715 [T; BLUE_STRING_MAX_LEN],
716) {
717 (
718 UnscaledOutlineBuf::<MAX_INLINE_POINTS>::new(),
719 [T::default(); BLUE_STRING_MAX_LEN],
720 [T::default(); BLUE_STRING_MAX_LEN],
721 )
722}
723
724#[inline(always)]
726fn things_all_blues_need<'a>(font: &FontRef<'a>) -> (OutlineGlyphCollection<'a>, i32) {
727 (
728 font.outline_glyphs(),
729 font.head()
730 .map(|head| head.units_per_em())
731 .unwrap_or_default() as i32,
732 )
733}
734
735pub(super) fn cycle_forward<T>(items: &[T], start: usize) -> impl Iterator<Item = (usize, &T)> {
738 let len = items.len();
739 let start = start + 1;
740 (0..len).map(move |ix| {
741 let real_ix = (ix + start) % len;
742 (real_ix, &items[real_ix])
743 })
744}
745
746pub(super) fn cycle_backward<T>(items: &[T], start: usize) -> impl Iterator<Item = (usize, &T)> {
749 let len = items.len();
750 (0..len).rev().map(move |ix| {
751 let real_ix = (ix + start) % len;
752 (real_ix, &items[real_ix])
753 })
754}
755
756#[cfg(test)]
757mod tests {
758 use crate::outline::autohint::metrics::BlueZones;
759
760 use super::{
761 super::super::{
762 shape::{Shaper, ShaperMode},
763 style,
764 },
765 satisfies_min_long_segment_len, UnscaledBlue,
766 };
767 use raw::FontRef;
768
769 #[test]
770 fn latin_blues() {
771 let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
772 let shaper = Shaper::new(&font, 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 shaper = Shaper::new(&font, ShaperMode::Nominal);
827 let style = &style::STYLE_CLASSES[super::StyleClass::HEBR];
829 let blues = super::compute_default_blues(&shaper, &[], style);
830 let values = blues.as_slice();
831 assert_eq!(values.len(), 3);
832 let expected = [
833 UnscaledBlue {
834 position: 592,
835 overshoot: 592,
836 ascender: 647,
837 descender: -240,
838 zones: BlueZones::TOP,
839 },
840 UnscaledBlue {
841 position: 0,
842 overshoot: -9,
843 ascender: 647,
844 descender: -9,
845 zones: BlueZones::default(),
846 },
847 UnscaledBlue {
848 position: -240,
849 overshoot: -240,
850 ascender: 647,
851 descender: -240,
852 zones: BlueZones::default(),
853 },
854 ];
855 assert_eq!(values, &expected);
856 }
857
858 #[test]
859 fn cjk_blues() {
860 let font = FontRef::new(font_test_data::NOTOSERIFTC_AUTOHINT_METRICS).unwrap();
861 let shaper = Shaper::new(&font, ShaperMode::Nominal);
862 let style = &style::STYLE_CLASSES[super::StyleClass::HANI];
863 let blues = super::compute_cjk_blues(&shaper, &[], style);
864 let values = blues[1].as_slice();
865 let expected = [
866 UnscaledBlue {
867 position: 837,
868 overshoot: 824,
869 ascender: 0,
870 descender: 0,
871 zones: BlueZones::TOP,
872 },
873 UnscaledBlue {
874 position: -78,
875 overshoot: -66,
876 ascender: 0,
877 descender: 0,
878 zones: BlueZones::default(),
879 },
880 ];
881 assert_eq!(values, &expected);
882 }
883
884 #[test]
885 fn c2sc_shaped_blues() {
886 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
887 let shaper = Shaper::new(&font, ShaperMode::BestEffort);
888 let style = &style::STYLE_CLASSES[super::StyleClass::LATN_C2SC];
889 let blues = super::compute_default_blues(&shaper, &[], style);
890 let values = blues.as_slice();
891 let expected = [
893 UnscaledBlue {
894 position: 571,
895 overshoot: 571,
896 ascender: 571,
897 descender: 0,
898 zones: BlueZones::TOP,
899 },
900 UnscaledBlue {
901 position: 0,
902 overshoot: 0,
903 ascender: 571,
904 descender: 0,
905 zones: BlueZones::default(),
906 },
907 ];
908 assert_eq!(values, &expected);
909 }
910
911 #[test]
914 fn long_segment_len_avoid_overflow() {
915 assert!(satisfies_min_long_segment_len(22, 0, 22));
921 }
922
923 #[test]
924 fn cycle_iter_forward() {
925 let items = [0, 1, 2, 3, 4, 5, 6, 7];
926 let from_5 = super::cycle_forward(&items, 5)
927 .map(|(_, val)| *val)
928 .collect::<Vec<_>>();
929 assert_eq!(from_5, &[6, 7, 0, 1, 2, 3, 4, 5]);
930 let from_last = super::cycle_forward(&items, 7)
931 .map(|(_, val)| *val)
932 .collect::<Vec<_>>();
933 assert_eq!(from_last, &items);
934 let _ = super::cycle_forward::<i32>(&[], 5).count();
936 }
937
938 #[test]
939 fn cycle_iter_backward() {
940 let items = [0, 1, 2, 3, 4, 5, 6, 7];
941 let from_5 = super::cycle_backward(&items, 5)
942 .map(|(_, val)| *val)
943 .collect::<Vec<_>>();
944 assert_eq!(from_5, &[4, 3, 2, 1, 0, 7, 6, 5]);
945 let from_0 = super::cycle_backward(&items, 0)
946 .map(|(_, val)| *val)
947 .collect::<Vec<_>>();
948 assert_eq!(from_0, &[7, 6, 5, 4, 3, 2, 1, 0]);
949 let _ = super::cycle_backward::<i32>(&[], 5).count();
951 }
952}