1use std::collections::VecDeque;
9
10use crate::{art::Art, rank::RankMap};
11
12pub trait Ordering {
14 fn rank(&self, art: &Art) -> RankMap;
15}
16
17#[inline]
20fn even_step(count: usize) -> f32 {
21 count.saturating_sub(1).max(1) as f32
22}
23
24#[derive(Clone, Copy, Debug, Default)]
33pub struct Scanline;
34
35impl Ordering for Scanline {
36 fn rank(&self, art: &Art) -> RankMap {
37 let mut map = RankMap::new(art.width(), art.height());
38 let denom = even_step(art.ink_count());
39 for (i, cell) in art.ink_cells().enumerate() {
40 map.set(cell.x, cell.y, i as f32 / denom);
41 }
42 map
43 }
44}
45
46#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
52pub enum Direction {
53 #[default]
55 TopToBottom,
56 BottomToTop,
58 LeftToRight,
60 RightToLeft,
62 Auto,
64}
65
66#[derive(Clone, Copy, Debug)]
71pub struct Directional(pub Direction);
72
73impl Default for Directional {
74 fn default() -> Self {
76 Directional(Direction::Auto)
77 }
78}
79
80impl Directional {
81 pub fn ltr() -> Self {
83 Directional(Direction::LeftToRight)
84 }
85
86 pub fn rtl() -> Self {
88 Directional(Direction::RightToLeft)
89 }
90
91 pub fn reading() -> Self {
100 if locale_is_rtl() {
101 Self::rtl()
102 } else {
103 Self::ltr()
104 }
105 }
106}
107
108const RTL_LANGS: [&str; 4] = ["ar", "he", "fa", "ur"];
110
111fn locale_is_rtl() -> bool {
112 let tagged = |l: &str| {
113 let l = l.to_ascii_lowercase();
114 RTL_LANGS.iter().any(|p| l.starts_with(p))
115 };
116 if let Ok(l) = std::env::var("LC_ALL").or_else(|_| std::env::var("LANG")) {
117 return tagged(&l);
118 }
119 system_locale().map(|l| tagged(&l)).unwrap_or(false)
120}
121
122#[cfg(windows)]
126fn system_locale() -> Option<String> {
127 #[link(name = "kernel32")]
131 extern "system" {
132 fn GetUserDefaultLocaleName(name: *mut u16, capacity: i32) -> i32;
133 }
134
135 let mut buf = [0u16; 85];
137 let len = unsafe { GetUserDefaultLocaleName(buf.as_mut_ptr(), buf.len() as i32) };
139 if len <= 1 {
140 return None; }
142 String::from_utf16(&buf[..len as usize - 1]).ok()
143}
144
145#[cfg(not(windows))]
146fn system_locale() -> Option<String> {
147 None
148}
149
150impl Ordering for Directional {
151 fn rank(&self, art: &Art) -> RankMap {
152 let (w, h) = (art.width(), art.height());
153 let dir = match self.0 {
158 Direction::Auto if is_wide(w, h) => Direction::LeftToRight,
159 Direction::Auto => Direction::TopToBottom,
160 other => other,
161 };
162 let dx = even_step(w as usize);
163 let dy = even_step(h as usize);
164 let mut map = RankMap::new(w, h);
165 for cell in art.ink_cells() {
166 let rank = match dir {
167 Direction::BottomToTop => (h - 1 - cell.y) as f32 / dy,
168 Direction::LeftToRight => cell.x as f32 / dx,
169 Direction::RightToLeft => (w - 1 - cell.x) as f32 / dx,
170 _ => cell.y as f32 / dy, };
172 map.set(cell.x, cell.y, rank);
173 }
174 map
175 }
176}
177
178#[inline]
181fn is_wide(w: u16, h: u16) -> bool {
182 w as u32 > 2 * h as u32
183}
184
185#[derive(Clone, Copy, Debug)]
208pub struct Geodesic {
209 pub start: StartHint,
211 pub bridge: u16,
217}
218
219impl Default for Geodesic {
220 fn default() -> Self {
223 Geodesic {
224 start: StartHint::default(),
225 bridge: 1,
226 }
227 }
228}
229
230#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
232pub enum StartHint {
233 #[default]
235 TopLeft,
236 Bottom,
238 Topological,
240}
241
242#[derive(Clone, Copy, Debug, PartialEq, Eq)]
249pub struct GeodesicReport {
250 pub ink_cells: usize,
252 pub connected_cells: usize,
254 pub skeleton_cells: usize,
256 pub pieces: usize,
259 pub spine_length: u32,
262}
263
264impl Geodesic {
265 pub fn diagnose(&self, art: &Art) -> GeodesicReport {
267 let (w, h) = (art.width(), art.height());
268 let ink = ink_mask(art);
269 let ink_cells = ink.iter().filter(|&&m| m).count();
270 if ink_cells == 0 {
271 return GeodesicReport {
272 ink_cells: 0,
273 connected_cells: 0,
274 skeleton_cells: 0,
275 pieces: 0,
276 spine_length: 0,
277 };
278 }
279
280 let connected_cells = largest_component(&ink, w, h, 0).map_or(0, |(size, _)| size);
281 let skel = skeletonize(art);
282 let skeleton_cells = skel.iter().filter(|&&m| m).count();
283 let bridge = adaptive_bridge(&skel, w, h, self.bridge);
284
285 GeodesicReport {
286 ink_cells,
287 connected_cells,
288 skeleton_cells,
289 pieces: components(&skel, w, h, bridge).len(),
290 spine_length: spine(&skel, w, h, self.start, self.bridge)
291 .map_or(0, |trace| trace.diameter),
292 }
293 }
294}
295
296impl Ordering for Geodesic {
297 fn rank(&self, art: &Art) -> RankMap {
298 let (w, h) = (art.width(), art.height());
299 let mut map = RankMap::new(w, h);
300 if art.ink_count() == 0 {
301 return map;
302 }
303
304 let skel = skeletonize(art);
307 let value = skeleton_values(&skel, w, h, self.start, self.bridge);
308
309 let mut val = value;
315 let mut depth = vec![0u32; val.len()];
316 let mut queue: VecDeque<usize> = (0..val.len()).filter(|&i| !val[i].is_nan()).collect();
317 while let Some(cur) = queue.pop_front() {
318 for ni in neighbours(cur, w, h) {
319 if val[ni].is_nan() {
320 val[ni] = val[cur];
321 depth[ni] = depth[cur] + 1;
322 queue.push_back(ni);
323 }
324 }
325 }
326
327 let mut order: Vec<(u16, u16, f32, u32)> = art
331 .ink_cells()
332 .map(|c| {
333 let i = art.index(c.x, c.y);
334 (c.x, c.y, val[i], depth[i])
335 })
336 .collect();
337 order.sort_by(|a, b| a.2.total_cmp(&b.2).then(a.3.cmp(&b.3)));
338 let denom = even_step(order.len());
339 for (i, &(x, y, _, _)) in order.iter().enumerate() {
340 map.set(x, y, i as f32 / denom);
341 }
342 map
343 }
344}
345
346pub const STRICT_CONNECTED_MIN: f32 = 0.6;
349
350struct Trace {
359 dist: Vec<Option<u32>>,
361 diameter: u32,
363}
364
365fn trace(mask: &[bool], w: u16, h: u16, seed: usize, hint: StartHint, bridge: u16) -> Trace {
369 let (_, far_a) = bfs(mask, w, h, seed, bridge);
370 let (dist_a, far_b) = bfs(mask, w, h, far_a, bridge);
371 let (dist_b, _) = bfs(mask, w, h, far_b, bridge);
372
373 let coord = |i: usize| ((i % w as usize) as u16, (i / w as usize) as u16);
374 let (ax, ay) = coord(far_a);
375 let (bx, by) = coord(far_b);
376 let start_is_a = match hint {
377 StartHint::Topological => true,
378 StartHint::TopLeft => (ay, ax) <= (by, bx),
379 StartHint::Bottom => ay >= by,
380 };
381
382 let dist = if start_is_a { dist_a } else { dist_b };
383 let diameter = dist.iter().flatten().copied().max().unwrap_or(0);
384 Trace { dist, diameter }
385}
386
387fn spine(mask: &[bool], w: u16, h: u16, hint: StartHint, bridge: u16) -> Option<Trace> {
390 let bridge = adaptive_bridge(mask, w, h, bridge);
391 let (_, seed) = largest_component(mask, w, h, bridge)?;
392 Some(trace(mask, w, h, seed, hint, bridge))
393}
394
395fn adaptive_bridge(mask: &[bool], w: u16, h: u16, bridge: u16) -> u16 {
399 if bridge == 0 {
400 return 0;
401 }
402 let count = mask.iter().filter(|&&m| m).count();
403 match largest_component(mask, w, h, 0) {
404 Some((strict, _)) if strict as f32 >= STRICT_CONNECTED_MIN * count.max(1) as f32 => 0,
405 _ => bridge,
406 }
407}
408
409fn neighbours(index: usize, w: u16, h: u16) -> impl Iterator<Item = usize> {
415 offsets(index, w, h, 0)
416}
417
418fn offsets(index: usize, w: u16, h: u16, bridge: u16) -> impl Iterator<Item = usize> {
423 let (wi, hi) = (w as i32, h as i32);
424 let r = bridge as i32 + 1;
425 let (cx, cy) = (index as i32 % wi.max(1), index as i32 / wi.max(1));
426 (-r..=r)
427 .flat_map(move |dy| (-r..=r).map(move |dx| (dx, dy)))
428 .filter_map(move |(dx, dy)| {
429 if dx == 0 && dy == 0 {
430 return None;
431 }
432 let (nx, ny) = (cx + dx, cy + dy);
433 (nx >= 0 && ny >= 0 && nx < wi && ny < hi).then_some((ny * wi + nx) as usize)
434 })
435}
436
437#[inline]
439fn bridged_neighbours(
440 mask: &[bool],
441 w: u16,
442 h: u16,
443 index: usize,
444 bridge: u16,
445) -> impl Iterator<Item = usize> + '_ {
446 offsets(index, w, h, bridge).filter(move |&ni| mask[ni])
447}
448
449fn ink_mask(art: &Art) -> Vec<bool> {
451 let (w, h) = (art.width() as usize, art.height() as usize);
452 (0..w * h)
453 .map(|i| art.is_ink((i % w.max(1)) as u16, (i / w.max(1)) as u16))
454 .collect()
455}
456
457fn components(mask: &[bool], w: u16, h: u16, bridge: u16) -> Vec<Vec<usize>> {
461 let mut seen = vec![false; mask.len()];
462 let mut queue = VecDeque::new();
463 let mut out = Vec::new();
464
465 for seed in 0..mask.len() {
466 if !mask[seed] || seen[seed] {
467 continue;
468 }
469 let mut cells = Vec::new();
470 seen[seed] = true;
471 queue.push_back(seed);
472 while let Some(cur) = queue.pop_front() {
473 cells.push(cur);
474 for ni in bridged_neighbours(mask, w, h, cur, bridge) {
475 if !seen[ni] {
476 seen[ni] = true;
477 queue.push_back(ni);
478 }
479 }
480 }
481 out.push(cells);
482 }
483 out
484}
485
486fn largest_component(mask: &[bool], w: u16, h: u16, bridge: u16) -> Option<(usize, usize)> {
488 components(mask, w, h, bridge)
489 .into_iter()
490 .map(|c| (c.len(), c[0]))
491 .max_by_key(|&(size, _)| size)
492}
493
494fn bfs(mask: &[bool], w: u16, h: u16, source: usize, bridge: u16) -> (Vec<Option<u32>>, usize) {
498 let mut dist = vec![None; mask.len()];
499 let mut queue = VecDeque::new();
500
501 dist[source] = Some(0);
502 queue.push_back(source);
503 let (mut farthest, mut far_d) = (source, 0u32);
504
505 while let Some(cur) = queue.pop_front() {
506 let d = dist[cur].unwrap();
507 if d > far_d {
508 far_d = d;
509 farthest = cur;
510 }
511 for ni in bridged_neighbours(mask, w, h, cur, bridge) {
512 if dist[ni].is_none() {
513 dist[ni] = Some(d + 1);
514 queue.push_back(ni);
515 }
516 }
517 }
518 (dist, farthest)
519}
520
521fn skeletonize(art: &Art) -> Vec<bool> {
525 let (w, h) = (art.width() as i32, art.height() as i32);
526 let idx = |x: i32, y: i32| (y * w + x) as usize;
527 let mut g = ink_mask(art);
528 let val = |g: &[bool], x: i32, y: i32| -> u8 {
529 (x >= 0 && y >= 0 && x < w && y < h && g[idx(x, y)]) as u8
530 };
531 loop {
532 let mut removed = false;
533 for step in 0..2 {
534 let mut marks = Vec::new();
535 for y in 0..h {
536 for x in 0..w {
537 if !g[idx(x, y)] {
538 continue;
539 }
540 let p = [
542 val(&g, x, y - 1),
543 val(&g, x + 1, y - 1),
544 val(&g, x + 1, y),
545 val(&g, x + 1, y + 1),
546 val(&g, x, y + 1),
547 val(&g, x - 1, y + 1),
548 val(&g, x - 1, y),
549 val(&g, x - 1, y - 1),
550 ];
551 let b: u8 = p.iter().sum();
552 if !(2..=6).contains(&b) {
553 continue;
554 }
555 let a = (0..8).filter(|&i| p[i] == 0 && p[(i + 1) % 8] == 1).count();
556 if a != 1 {
557 continue;
558 }
559 let (c1, c2) = if step == 0 {
560 (p[0] * p[2] * p[4], p[2] * p[4] * p[6])
561 } else {
562 (p[0] * p[2] * p[6], p[0] * p[4] * p[6])
563 };
564 if c1 == 0 && c2 == 0 {
565 marks.push(idx(x, y));
566 }
567 }
568 }
569 if !marks.is_empty() {
570 removed = true;
571 for i in marks {
572 g[i] = false;
573 }
574 }
575 }
576 if !removed {
577 break;
578 }
579 }
580 g
581}
582
583fn skeleton_values(skel: &[bool], w: u16, h: u16, hint: StartHint, bridge: u16) -> Vec<f32> {
588 let mut value = vec![f32::NAN; skel.len()];
589 if !skel.iter().any(|&m| m) {
590 return value;
591 }
592
593 let bridge = adaptive_bridge(skel, w, h, bridge);
594 let horizontal = is_wide(w, h);
595 let axis = |i: usize| -> u16 {
596 if horizontal {
597 (i % w as usize) as u16
598 } else {
599 (i / w as usize) as u16
600 }
601 };
602
603 let mut pieces: Vec<(u16, Vec<usize>, Trace)> = components(skel, w, h, bridge)
605 .into_iter()
606 .map(|comp| {
607 let lead = comp.iter().map(|&c| axis(c)).min().unwrap_or(0);
608 let traced = trace(skel, w, h, comp[0], hint, bridge);
609 (lead, comp, traced)
610 })
611 .collect();
612
613 pieces.sort_by_key(|(lead, _, _)| *lead);
614 for (index, (_, comp, traced)) in pieces.iter().enumerate() {
615 let span = traced.diameter.max(1) as f32;
616 for &cell in comp {
617 let within = traced.dist[cell].map_or(0.0, |d| d as f32 / span);
618 value[cell] = index as f32 + within;
619 }
620 }
621 value
622}
623
624#[cfg(test)]
625mod tests {
626 use super::*;
627
628 #[test]
631 fn straight_line_reveals_along_itself() {
632 let art = Art::parse("=========");
633 let ranks = Geodesic::default().rank(&art);
634 let row: Vec<f32> = (0..art.width())
635 .map(|x| ranks.rank_at(x, 0).unwrap())
636 .collect();
637 let increasing = row.windows(2).all(|w| w[0] <= w[1]);
638 let decreasing = row.windows(2).all(|w| w[0] >= w[1]);
639 assert!(
640 increasing || decreasing,
641 "spine reveal was not monotone: {row:?}"
642 );
643 assert!((row.iter().cloned().fold(0.0_f32, f32::max) - 1.0).abs() < 1e-6);
644 }
645
646 #[test]
648 fn spine_traces_largest_component() {
649 let art = Art::parse(".\n\n ========");
650 let report = Geodesic::default().diagnose(&art);
651 assert_eq!(report.ink_cells, 9);
652 assert_eq!(report.connected_cells, 8); }
654
655 #[test]
658 fn islands_inherit_nearest_spine_rank() {
659 let art = Art::parse(". ====== .");
660 let ranks = Geodesic::default().rank(&art);
661 let left = ranks.rank_at(0, 0).unwrap();
662 let right = ranks.rank_at(11, 0).unwrap();
663 assert!(left < right, "left {left} should precede right {right}");
664 assert!(left < 0.25 && right > 0.75, "left={left} right={right}");
665 }
666
667 #[test]
668 fn diagnose_counts_connectivity() {
669 let report = Geodesic::default().diagnose(&Art::parse("========== ."));
670 assert_eq!(report.ink_cells, 11);
671 assert_eq!(report.connected_cells, 10); }
673
674 #[test]
678 fn diagnose_reports_the_traced_skeleton() {
679 let art = Art::parse(&"##########\n".repeat(3));
680 let report = Geodesic::default().diagnose(&art);
681 assert_eq!(report.ink_cells, 30);
682 assert_eq!(report.connected_cells, 30);
683 assert!(
684 report.skeleton_cells < report.ink_cells,
685 "thinning should shrink the ink: {report:?}"
686 );
687 assert_eq!(report.pieces, 1);
688 assert!(
689 (report.spine_length as usize) < report.ink_cells,
690 "spine must be the centerline, not the ink: {report:?}"
691 );
692 }
693
694 #[test]
695 fn diagnose_counts_pieces() {
696 let art = Art::parse("## ## ##");
697 let report = Geodesic::default().diagnose(&art);
698 assert_eq!(report.pieces, 3);
699 }
700
701 #[test]
702 fn diagnose_of_empty_art_is_all_zero() {
703 let report = Geodesic::default().diagnose(&Art::parse(" \n "));
704 assert_eq!(report.ink_cells, 0);
705 assert_eq!(report.spine_length, 0);
706 assert_eq!(report.pieces, 0);
707 }
708
709 #[test]
712 fn bridges_small_gaps_when_fragmented() {
713 let art = Art::parse("== ==");
714 let strict = Geodesic {
715 start: StartHint::TopLeft,
716 bridge: 0,
717 };
718 assert_eq!(strict.diagnose(&art).pieces, 2);
719 assert_eq!(Geodesic::default().diagnose(&art).pieces, 1);
720 }
721
722 #[test]
725 fn connected_art_is_not_bridged() {
726 let art = Art::parse("####\n #\n####\n#\n####");
729 let report = Geodesic::default().diagnose(&art);
730 assert_eq!(report.connected_cells, report.ink_cells);
731 assert_eq!(report.pieces, 1);
732 assert!(
733 report.spine_length >= 9,
734 "spine was {}",
735 report.spine_length
736 );
737 }
738
739 #[test]
742 fn solid_block_reveals_across_the_whole_bar() {
743 let art = Art::parse(&"########\n".repeat(8));
744 let r = Geodesic::default().rank(&art);
745 let ranks: Vec<f32> = (0..8)
746 .flat_map(|y| (0..8u16).map(move |x| (x, y)))
747 .map(|(x, y)| r.rank_at(x, y).unwrap())
748 .collect();
749 let lo = ranks.iter().cloned().fold(f32::MAX, f32::min);
750 let hi = ranks.iter().cloned().fold(f32::MIN, f32::max);
751 assert!(
752 lo < 0.02 && hi > 0.98,
753 "block did not use the whole bar: {lo}..{hi}"
754 );
755 }
756
757 #[test]
760 fn separate_pieces_reveal_in_reading_order() {
761 let art = Art::parse("## ##\n## ##\n## ##");
762 let r = Geodesic::default().rank(&art);
763 let left = r.rank_at(0, 1).unwrap();
764 let right = r.rank_at(11, 1).unwrap();
765 assert!(
766 left < right,
767 "left piece {left} should precede right {right}"
768 );
769 assert!(
770 left < 0.5 && right > 0.5,
771 "pieces out of order: {left} {right}"
772 );
773 }
774
775 #[test]
778 fn thin_line_stays_a_trace() {
779 let art = Art::parse("==============");
780 let r = Geodesic::default().rank(&art);
781 let row: Vec<f32> = (0..art.width()).map(|x| r.rank_at(x, 0).unwrap()).collect();
782 let lo = row.iter().cloned().fold(f32::MAX, f32::min);
783 let hi = row.iter().cloned().fold(f32::MIN, f32::max);
784 assert!(
785 lo < 0.01 && hi > 0.99,
786 "line did not trace end to end: {row:?}"
787 );
788 }
789
790 #[test]
794 fn directional_auto_accounts_for_cell_aspect() {
795 let tall = Art::parse("#####\n#####\n#####\n#####");
797 let r = Directional(Direction::Auto).rank(&tall);
798 assert!(
799 r.rank_at(0, 0).unwrap() < r.rank_at(0, 3).unwrap(),
800 "top first"
801 );
802 assert_eq!(
803 r.rank_at(0, 0),
804 r.rank_at(4, 0),
805 "same row reveals together"
806 );
807
808 let wide = Art::parse("##########\n##########");
810 let rw = Directional(Direction::Auto).rank(&wide);
811 assert!(
812 rw.rank_at(0, 0).unwrap() < rw.rank_at(9, 0).unwrap(),
813 "left first"
814 );
815 assert_eq!(
816 rw.rank_at(0, 0),
817 rw.rank_at(0, 1),
818 "same column reveals together"
819 );
820 }
821
822 #[test]
826 fn padding_does_not_steer_auto() {
827 let padded = Directional(Direction::Auto).rank(&Art::parse(" #\n #"));
828 let bare = Directional(Direction::Auto).rank(&Art::parse("#\n#"));
829 assert_eq!(padded.rank_at(0, 0), bare.rank_at(0, 0));
830 assert_eq!(padded.rank_at(0, 0), Some(0.0));
831 assert_eq!(padded.rank_at(0, 1), Some(1.0));
832 }
833
834 #[test]
835 fn explicit_direction_beats_locale_sniffing() {
836 let art = Art::parse("abcd");
837 let ltr = Directional::ltr().rank(&art);
838 let rtl = Directional::rtl().rank(&art);
839 assert_eq!(ltr.rank_at(0, 0), Some(0.0));
840 assert_eq!(rtl.rank_at(3, 0), Some(0.0));
841 }
842
843 #[test]
844 fn scanline_spans_the_whole_bar() {
845 let art = Art::parse("ab\ncd");
846 let r = Scanline.rank(&art);
847 assert_eq!(r.rank_at(0, 0), Some(0.0));
848 assert_eq!(r.rank_at(1, 1), Some(1.0));
849 }
850
851 fn revealed_share(art: &Art, ranks: &RankMap, progress: f32) -> f32 {
853 let mut seen = 0usize;
854 for y in 0..art.height() {
855 for x in 0..art.width() {
856 if art.is_ink(x, y) && ranks.visible_at(x, y, progress) {
857 seen += 1;
858 }
859 }
860 }
861 seen as f32 / art.ink_count().max(1) as f32
862 }
863
864 #[test]
871 fn revealed_share_tracks_progress_on_the_bundled_art() {
872 let art = [
873 ("dragon", Art::parse(include_str!("../assets/dragon.txt"))),
874 ("serpent", Art::parse(include_str!("../assets/serpent.txt"))),
875 ("inkling", Art::parse(include_str!("../assets/inkling.txt"))),
876 ];
877 for (name, art) in &art {
878 let maps: [(&str, RankMap, f32); 3] = [
885 ("directional", Directional::default().rank(art), 0.2),
886 ("geodesic", Geodesic::default().rank(art), 0.02),
887 ("scanline", Scanline.rank(art), 0.02),
888 ];
889 for (ordering, ranks, tolerance) in maps {
890 let at = |p| revealed_share(art, &ranks, p);
891 assert!(
892 at(0.0) < 0.02,
893 "{name}/{ordering}: {:.0}% of the ink is already showing at zero",
894 at(0.0) * 100.0
895 );
896 for p in [0.25f32, 0.5, 0.75] {
897 let share = at(p);
898 assert!(
899 (share - p).abs() < tolerance,
900 "{name}/{ordering}: {:.0}% of the ink revealed at {:.0}% progress",
901 share * 100.0,
902 p * 100.0
903 );
904 }
905 assert!(
906 at(1.0) > 0.999,
907 "{name}/{ordering}: the art never finishes filling"
908 );
909 }
910 }
911 }
912
913 #[test]
914 fn orderings_tolerate_empty_art() {
915 let art = Art::parse("");
916 for map in [
917 Scanline.rank(&art),
918 Directional::default().rank(&art),
919 Geodesic::default().rank(&art),
920 ] {
921 assert_eq!(map.ink_count(), 0);
922 }
923 }
924}