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 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(crate) const fn union(self, other: Self) -> Self {
57 Self(self.0 | other.0)
58 }
59
60 pub(crate) 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(crate) fn is_long(self) -> bool {
81 self.contains(Self::LONG)
82 }
83
84 pub(crate) fn is_horizontal(self) -> bool {
85 self.contains(Self::HORIZONTAL)
86 }
87
88 pub(crate) 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)]
141pub struct UnscaledBlue {
142 pub position: i32,
144 pub overshoot: i32,
146 pub ascender: i32,
148 pub descender: i32,
150 pub zones: BlueZones,
152}
153
154pub(crate) type UnscaledBlues = SmallVec<UnscaledBlue, MAX_BLUES>;
155
156#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
158pub struct ScaledBlue {
159 pub position: ScaledWidth,
161 pub overshoot: ScaledWidth,
163 pub zones: BlueZones,
165 pub is_active: bool,
167}
168
169pub(crate) type ScaledBlues = SmallVec<ScaledBlue, MAX_BLUES>;
170
171pub(crate) fn compute_unscaled_blues(
173 shaper: &Shaper,
174 coords: &[F2Dot14],
175 style: &StyleClass,
176) -> [UnscaledBlues; 2] {
177 match style.script.group {
178 ScriptGroup::Default => [
179 Default::default(),
181 compute_default_blues(shaper, coords, style),
182 ],
183 ScriptGroup::Cjk => compute_cjk_blues(shaper, coords, style),
184 ScriptGroup::Indic => Default::default(),
186 }
187}
188
189fn compute_default_blues(shaper: &Shaper, coords: &[F2Dot14], style: &StyleClass) -> UnscaledBlues {
193 let mut blues = UnscaledBlues::new();
194 let (mut outline_buf, mut flats, mut rounds) = buffers();
195 let (glyphs, units_per_em) = things_all_blues_need(shaper.font());
196 let flat_threshold = units_per_em / 14;
197 let mut cluster_shaper = shaper.cluster_shaper(style);
198 let mut shaped_cluster = ShapedCluster::default();
199 for (blue_str, blue_zones) in style.script.blues {
201 let mut ascender = i32::MIN;
202 let mut descender = i32::MAX;
203 let mut n_flats = 0;
204 let mut n_rounds = 0;
205 for cluster in blue_str.split(' ') {
206 let mut best_y_extremum = if blue_zones.is_top() {
207 i32::MIN
208 } else {
209 i32::MAX
210 };
211 let mut best_is_round = false;
212 cluster_shaper.shape(cluster, &mut shaped_cluster);
213 for (glyph, y_offset) in shaped_cluster
214 .iter()
215 .filter(|g| g.id.to_u32() != 0)
216 .filter_map(|g| Some((glyphs.get(g.id)?, g.y_offset)))
217 {
218 outline_buf.clear();
219 if glyph.draw_unscaled(coords, None, &mut outline_buf).is_err() {
220 continue;
221 }
222 let outline = outline_buf.as_ref();
223 if outline.points.len() <= 2 {
225 continue;
226 }
227 let mut best_y: Option<i16> = None;
228 let best_contour_and_point = if blue_zones.is_top_like() {
231 outline.find_last_contour(|point| {
232 if best_y.is_none() || Some(point.y) > best_y {
233 best_y = Some(point.y);
234 ascender = ascender.max(point.y as i32 + y_offset);
235 true
236 } else {
237 descender = descender.min(point.y as i32 + y_offset);
238 false
239 }
240 })
241 } else {
242 outline.find_last_contour(|point| {
243 if best_y.is_none() || Some(point.y) < best_y {
244 best_y = Some(point.y);
245 descender = descender.min(point.y as i32 + y_offset);
246 true
247 } else {
248 ascender = ascender.max(point.y as i32 + y_offset);
249 false
250 }
251 })
252 };
253 let Some((best_contour_range, best_point_ix)) = best_contour_and_point else {
254 continue;
255 };
256 let best_contour = &outline.points[best_contour_range];
257 let mut best_y = best_y.unwrap() as i32;
260 let best_x = best_contour[best_point_ix].x as i32;
261 let [mut on_point_first, mut on_point_last] =
264 if best_contour[best_point_ix].is_on_curve() {
265 [Some(best_point_ix); 2]
266 } else {
267 [None; 2]
268 };
269 let mut segment_first = best_point_ix;
270 let mut segment_last = best_point_ix;
271 for (ix, prev) in cycle_backward(best_contour, best_point_ix) {
275 let dist = (prev.y as i32 - best_y).abs();
276 if dist > 5 && ((prev.x as i32 - best_x).abs() <= (20 * dist)) {
278 break;
279 }
280 segment_first = ix;
281 if prev.is_on_curve() {
282 on_point_first = Some(ix);
283 if on_point_last.is_none() {
284 on_point_last = Some(ix);
285 }
286 }
287 }
288 let mut next_ix = 0;
289 for (ix, next) in cycle_forward(best_contour, best_point_ix) {
290 next_ix = ix;
293 let dist = (next.y as i32 - best_y).abs();
294 if dist > 5 && ((next.x as i32 - best_x).abs() <= (20 * dist)) {
296 break;
297 }
298 segment_last = ix;
299 if next.is_on_curve() {
300 on_point_last = Some(ix);
301 if on_point_first.is_none() {
302 on_point_first = Some(ix);
303 }
304 }
305 }
306 if blue_zones.is_long() {
307 let length_threshold = units_per_em / 25;
326 let dist = (best_contour[segment_last].x as i32
327 - best_contour[segment_first].x as i32)
328 .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 as i32 != 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 as i32) < 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 as i32).abs() > height_threshold {
376 hit = false;
378 continue;
379 }
380 let dist =
381 (best_contour[last].y as i32 - best_contour[first].y as i32).abs();
382 if dist > 5
383 && (best_contour[last].x as i32 - best_contour[first].x as i32)
384 .abs()
385 <= 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 as i32;
400 let last_x = best_contour[last].x as i32;
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 as i32
411 - best_contour[first].y as i32)
412 .abs();
413 if dy > 5
414 && (best_contour[next_ix].x as i32
415 - best_contour[first].x as i32)
416 .abs()
417 <= 20 * dist
418 {
419 if last > 0 {
420 last -= 1;
421 } else {
422 last = best_contour.len() - 1;
423 }
424 break;
425 }
426 p_last = Some(last);
427 if best_contour[last].is_on_curve() {
428 p_last = Some(last);
429 if p_first.is_none() {
430 p_first = Some(last);
431 }
432 }
433 if last == segment_first {
434 break;
435 }
436 }
437 best_y = best_contour[first].y as i32;
438 segment_first = first;
439 segment_last = last;
440 on_point_first = p_first;
441 on_point_last = p_last;
442 break;
443 }
444 if last == segment_first {
445 break;
446 }
447 }
448 }
449 }
450 best_y += y_offset;
451 let is_round = match (on_point_first, on_point_last) {
457 (Some(first), Some(last))
458 if (best_contour[last].x as i32 - best_contour[first].x as i32).abs()
459 > flat_threshold =>
460 {
461 false
462 }
463 _ => {
464 !best_contour[segment_first].is_on_curve()
465 || !best_contour[segment_last].is_on_curve()
466 }
467 };
468 if is_round && blue_zones.is_neutral() {
469 continue;
471 }
472 if blue_zones.is_top() {
474 if best_y > best_y_extremum {
475 best_y_extremum = best_y;
476 best_is_round = is_round;
477 }
478 } else if best_y < best_y_extremum {
479 best_y_extremum = best_y;
480 best_is_round = is_round;
481 }
482 }
483 if best_y_extremum != i32::MIN && best_y_extremum != i32::MAX {
484 if best_is_round {
485 rounds[n_rounds] = best_y_extremum;
486 n_rounds += 1;
487 } else {
488 flats[n_flats] = best_y_extremum;
489 n_flats += 1;
490 }
491 }
492 }
493 if n_flats == 0 && n_rounds == 0 {
494 continue;
495 }
496 rounds[..n_rounds].sort_unstable();
497 flats[..n_flats].sort_unstable();
498 let (mut blue_ref, mut blue_shoot) = if n_flats == 0 {
499 let val = rounds[n_rounds / 2];
500 (val, val)
501 } else if n_rounds == 0 {
502 let val = flats[n_flats / 2];
503 (val, val)
504 } else {
505 (flats[n_flats / 2], rounds[n_rounds / 2])
506 };
507 if blue_shoot != blue_ref {
508 let over_ref = blue_shoot > blue_ref;
509 if blue_zones.is_top_like() ^ over_ref {
510 let val = (blue_shoot + blue_ref) / 2;
511 blue_ref = val;
512 blue_shoot = val;
513 }
514 }
515 let mut blue = UnscaledBlue {
516 position: blue_ref,
517 overshoot: blue_shoot,
518 ascender,
519 descender,
520 zones: blue_zones.retain_top_like_or_neutral(),
521 };
522 if blue_zones.is_x_height() {
523 blue.zones |= BlueZones::ADJUSTMENT;
524 }
525 blues.push(blue);
526 }
527 let mut sorted_indices: [usize; MAX_BLUES] = core::array::from_fn(|ix| ix);
529 let blue_values = blues.as_mut_slice();
530 let len = blue_values.len();
531 if len == 0 {
532 return blues;
533 }
534 for i in 1..len {
536 for j in (1..=i).rev() {
537 let first = &blue_values[sorted_indices[j - 1]];
538 let second = &blue_values[sorted_indices[j]];
539 let a = if first.zones.is_top_like() {
540 first.position
541 } else {
542 first.overshoot
543 };
544 let b = if second.zones.is_top_like() {
545 second.position
546 } else {
547 second.overshoot
548 };
549 if b >= a {
550 break;
551 }
552 sorted_indices.swap(j, j - 1);
553 }
554 }
555 for i in 0..len - 1 {
557 let index1 = sorted_indices[i];
558 let index2 = sorted_indices[i + 1];
559 let first = &blue_values[index1];
560 let second = &blue_values[index2];
561 let a = if first.zones.is_top_like() {
562 first.overshoot
563 } else {
564 first.position
565 };
566 let b = if second.zones.is_top_like() {
567 second.overshoot
568 } else {
569 second.position
570 };
571 if a > b {
572 if first.zones.is_top_like() {
573 blue_values[index1].overshoot = b;
574 } else {
575 blue_values[index1].position = b;
576 }
577 }
578 }
579 blues
580}
581
582fn satisfies_min_long_segment_len(first_ix: usize, last_ix: usize, contour_last: usize) -> bool {
586 let inclusive_diff = if first_ix <= last_ix {
587 last_ix - first_ix
588 } else {
589 contour_last - first_ix + 1 + last_ix
595 };
596 inclusive_diff + 2 <= contour_last
600}
601
602fn compute_cjk_blues(
612 shaper: &Shaper,
613 coords: &[F2Dot14],
614 style: &StyleClass,
615) -> [UnscaledBlues; 2] {
616 let mut blues = [UnscaledBlues::new(), UnscaledBlues::new()];
617 let (mut outline_buf, mut flats, mut fills) = buffers();
618 let (glyphs, _) = things_all_blues_need(shaper.font());
619 let mut cluster_shaper = shaper.cluster_shaper(style);
620 let mut shaped_cluster = ShapedCluster::default();
621 for (blue_str, blue_zones) in style.script.blues {
623 let is_horizontal = blue_zones.is_horizontal();
624 if is_horizontal {
629 continue;
630 }
631 let is_right = blue_zones.is_right();
632 let is_top = blue_zones.is_top();
633 let blues = &mut blues[!is_horizontal as usize];
634 if blues.len() >= MAX_BLUES {
635 continue;
636 }
637 let mut n_flats = 0;
638 let mut n_fills = 0;
639 let mut is_fill = true;
640 for cluster in blue_str.split(' ') {
641 if cluster == "|" {
645 is_fill = false;
646 continue;
647 }
648 cluster_shaper.shape(cluster, &mut shaped_cluster);
649 for glyph in shaped_cluster
650 .iter()
651 .filter(|g| g.id.to_u32() != 0)
652 .filter_map(|g| glyphs.get(g.id))
653 {
654 outline_buf.clear();
655 if glyph.draw_unscaled(coords, None, &mut outline_buf).is_err() {
656 continue;
657 }
658 let outline = outline_buf.as_ref();
659 if outline.points.len() <= 2 {
661 continue;
662 }
663 let best_pos = outline
666 .points
667 .iter()
668 .map(|p| if is_horizontal { p.x } else { p.y })
669 .reduce(
670 if (is_horizontal && is_right) || (!is_horizontal && is_top) {
671 |a: i16, c: i16| a.max(c)
672 } else {
673 |a: i16, c: i16| a.min(c)
674 },
675 )
676 .unwrap();
677 if is_fill {
678 fills[n_fills] = best_pos;
679 n_fills += 1;
680 } else {
681 flats[n_flats] = best_pos;
682 n_flats += 1;
683 }
684 }
685 }
686 if n_flats == 0 && n_fills == 0 {
687 continue;
688 }
689 fills[..n_fills].sort_unstable();
692 flats[..n_flats].sort_unstable();
693 let (mut blue_ref, mut blue_shoot) = if n_flats == 0 {
694 let value = fills[n_fills / 2] as i32;
695 (value, value)
696 } else if n_fills == 0 {
697 let value = flats[n_flats / 2] as i32;
698 (value, value)
699 } else {
700 (fills[n_fills / 2] as i32, flats[n_flats / 2] as i32)
701 };
702 if blue_shoot != blue_ref {
705 let under_ref = blue_shoot < blue_ref;
706 if blue_zones.is_top() ^ under_ref {
707 blue_ref = (blue_shoot + blue_ref) / 2;
708 blue_shoot = blue_ref;
709 }
710 }
711 blues.push(UnscaledBlue {
712 position: blue_ref,
713 overshoot: blue_shoot,
714 ascender: 0,
715 descender: 0,
716 zones: *blue_zones & BlueZones::TOP,
717 });
718 }
719 blues
720}
721
722#[inline(always)]
723fn buffers<T: Copy + Default>() -> (
724 UnscaledOutlineBuf<MAX_INLINE_POINTS>,
725 [T; BLUE_STRING_MAX_LEN],
726 [T; BLUE_STRING_MAX_LEN],
727) {
728 (
729 UnscaledOutlineBuf::<MAX_INLINE_POINTS>::new(),
730 [T::default(); BLUE_STRING_MAX_LEN],
731 [T::default(); BLUE_STRING_MAX_LEN],
732 )
733}
734
735#[inline(always)]
737fn things_all_blues_need<'a>(font: &FontRef<'a>) -> (OutlineGlyphCollection<'a>, i32) {
738 (
739 font.outline_glyphs(),
740 font.head()
741 .map(|head| head.units_per_em())
742 .unwrap_or_default() as i32,
743 )
744}
745
746pub(super) fn cycle_forward<T>(items: &[T], start: usize) -> impl Iterator<Item = (usize, &T)> {
749 let len = items.len();
750 let start = start + 1;
751 (0..len).map(move |ix| {
752 let real_ix = (ix + start) % len;
753 (real_ix, &items[real_ix])
754 })
755}
756
757pub(super) fn cycle_backward<T>(items: &[T], start: usize) -> impl Iterator<Item = (usize, &T)> {
760 let len = items.len();
761 (0..len).rev().map(move |ix| {
762 let real_ix = (ix + start) % len;
763 (real_ix, &items[real_ix])
764 })
765}
766
767#[cfg(test)]
768mod tests {
769 use crate::outline::autohint::metrics::BlueZones;
770
771 use super::{
772 super::super::{
773 shape::{Shaper, ShaperMode},
774 style,
775 },
776 satisfies_min_long_segment_len, UnscaledBlue,
777 };
778 use raw::FontRef;
779
780 #[test]
781 fn latin_blues() {
782 let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
783 let shaper = Shaper::new(&font, ShaperMode::Nominal);
784 let style = &style::STYLE_CLASSES[super::StyleClass::LATN];
785 let blues = super::compute_default_blues(&shaper, &[], style);
786 let values = blues.as_slice();
787 let expected = [
788 UnscaledBlue {
789 position: 714,
790 overshoot: 725,
791 ascender: 725,
792 descender: -230,
793 zones: BlueZones::TOP,
794 },
795 UnscaledBlue {
796 position: 0,
797 overshoot: -10,
798 ascender: 725,
799 descender: -10,
800 zones: BlueZones::default(),
801 },
802 UnscaledBlue {
803 position: 760,
804 overshoot: 760,
805 ascender: 770,
806 descender: -240,
807 zones: BlueZones::TOP,
808 },
809 UnscaledBlue {
810 position: 536,
811 overshoot: 546,
812 ascender: 546,
813 descender: -10,
814 zones: BlueZones::TOP | BlueZones::ADJUSTMENT,
815 },
816 UnscaledBlue {
817 position: 0,
818 overshoot: -10,
819 ascender: 546,
820 descender: -10,
821 zones: BlueZones::default(),
822 },
823 UnscaledBlue {
824 position: -240,
825 overshoot: -240,
826 ascender: 760,
827 descender: -240,
828 zones: BlueZones::default(),
829 },
830 ];
831 assert_eq!(values, &expected);
832 }
833
834 #[test]
835 fn hebrew_long_blues() {
836 let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
837 let shaper = Shaper::new(&font, ShaperMode::Nominal);
838 let style = &style::STYLE_CLASSES[super::StyleClass::HEBR];
840 let blues = super::compute_default_blues(&shaper, &[], style);
841 let values = blues.as_slice();
842 assert_eq!(values.len(), 3);
843 let expected = [
844 UnscaledBlue {
845 position: 592,
846 overshoot: 592,
847 ascender: 647,
848 descender: -240,
849 zones: BlueZones::TOP,
850 },
851 UnscaledBlue {
852 position: 0,
853 overshoot: -9,
854 ascender: 647,
855 descender: -9,
856 zones: BlueZones::default(),
857 },
858 UnscaledBlue {
859 position: -240,
860 overshoot: -240,
861 ascender: 647,
862 descender: -240,
863 zones: BlueZones::default(),
864 },
865 ];
866 assert_eq!(values, &expected);
867 }
868
869 #[test]
870 fn cjk_blues() {
871 let font = FontRef::new(font_test_data::NOTOSERIFTC_AUTOHINT_METRICS).unwrap();
872 let shaper = Shaper::new(&font, ShaperMode::Nominal);
873 let style = &style::STYLE_CLASSES[super::StyleClass::HANI];
874 let blues = super::compute_cjk_blues(&shaper, &[], style);
875 let values = blues[1].as_slice();
876 let expected = [
877 UnscaledBlue {
878 position: 837,
879 overshoot: 824,
880 ascender: 0,
881 descender: 0,
882 zones: BlueZones::TOP,
883 },
884 UnscaledBlue {
885 position: -78,
886 overshoot: -66,
887 ascender: 0,
888 descender: 0,
889 zones: BlueZones::default(),
890 },
891 ];
892 assert_eq!(values, &expected);
893 }
894
895 #[test]
896 fn c2sc_shaped_blues() {
897 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
898 let shaper = Shaper::new(&font, ShaperMode::BestEffort);
899 let style = &style::STYLE_CLASSES[super::StyleClass::LATN_C2SC];
900 let blues = super::compute_default_blues(&shaper, &[], style);
901 let values = blues.as_slice();
902 let expected = [
904 UnscaledBlue {
905 position: 571,
906 overshoot: 571,
907 ascender: 571,
908 descender: 0,
909 zones: BlueZones::TOP,
910 },
911 UnscaledBlue {
912 position: 0,
913 overshoot: 0,
914 ascender: 571,
915 descender: 0,
916 zones: BlueZones::default(),
917 },
918 ];
919 assert_eq!(values, &expected);
920 }
921
922 #[test]
925 fn long_segment_len_avoid_overflow() {
926 assert!(satisfies_min_long_segment_len(22, 0, 22));
932 }
933
934 #[test]
935 fn cycle_iter_forward() {
936 let items = [0, 1, 2, 3, 4, 5, 6, 7];
937 let from_5 = super::cycle_forward(&items, 5)
938 .map(|(_, val)| *val)
939 .collect::<Vec<_>>();
940 assert_eq!(from_5, &[6, 7, 0, 1, 2, 3, 4, 5]);
941 let from_last = super::cycle_forward(&items, 7)
942 .map(|(_, val)| *val)
943 .collect::<Vec<_>>();
944 assert_eq!(from_last, &items);
945 let _ = super::cycle_forward::<i32>(&[], 5).count();
947 }
948
949 #[test]
950 fn cycle_iter_backward() {
951 let items = [0, 1, 2, 3, 4, 5, 6, 7];
952 let from_5 = super::cycle_backward(&items, 5)
953 .map(|(_, val)| *val)
954 .collect::<Vec<_>>();
955 assert_eq!(from_5, &[4, 3, 2, 1, 0, 7, 6, 5]);
956 let from_0 = super::cycle_backward(&items, 0)
957 .map(|(_, val)| *val)
958 .collect::<Vec<_>>();
959 assert_eq!(from_0, &[7, 6, 5, 4, 3, 2, 1, 0]);
960 let _ = super::cycle_backward::<i32>(&[], 5).count();
962 }
963}