1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct Cell {
9 pub x: u16,
10 pub y: u16,
11 pub glyph: char,
12}
13
14#[derive(Clone, Debug)]
16pub struct Art {
17 width: u16,
18 height: u16,
19 rows: Vec<Vec<char>>,
20}
21
22impl Art {
23 pub fn parse(text: &str) -> Self {
27 let mut rows: Vec<Vec<char>> = text
28 .split('\n')
29 .map(|line| line.strip_suffix('\r').unwrap_or(line).chars().collect())
30 .collect();
31
32 let width = rows.iter().map(|r| r.len()).max().unwrap_or(0);
33 for r in &mut rows {
34 if r.len() < width {
35 r.resize(width, ' ');
36 }
37 }
38
39 let is_blank = |r: &Vec<char>| r.iter().all(|c| c.is_whitespace());
41 let rows: Vec<Vec<char>> = match rows.iter().position(|r| !is_blank(r)) {
42 Some(first) => {
43 let last = rows.iter().rposition(|r| !is_blank(r)).unwrap();
44 rows[first..=last].to_vec()
45 }
46 None => Vec::new(), };
48
49 Art {
50 width: width as u16,
51 height: rows.len() as u16,
52 rows,
53 }
54 }
55
56 pub fn width(&self) -> u16 {
57 self.width
58 }
59
60 pub fn height(&self) -> u16 {
61 self.height
62 }
63
64 pub fn glyph(&self, x: u16, y: u16) -> char {
66 self.rows
67 .get(y as usize)
68 .and_then(|r| r.get(x as usize))
69 .copied()
70 .unwrap_or(' ')
71 }
72
73 pub fn is_ink(&self, x: u16, y: u16) -> bool {
75 !self.glyph(x, y).is_whitespace()
76 }
77
78 #[inline]
80 pub fn index(&self, x: u16, y: u16) -> usize {
81 y as usize * self.width as usize + x as usize
82 }
83
84 pub fn cell_count(&self) -> usize {
86 self.width as usize * self.height as usize
87 }
88
89 pub fn ink_cells(&self) -> impl Iterator<Item = Cell> + '_ {
91 (0..self.height).flat_map(move |y| {
92 (0..self.width).filter_map(move |x| {
93 let glyph = self.glyph(x, y);
94 (!glyph.is_whitespace()).then_some(Cell { x, y, glyph })
95 })
96 })
97 }
98
99 pub fn ink_count(&self) -> usize {
101 self.ink_cells().count()
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn trims_outer_blank_rows_and_pads_width() {
111 let art = Art::parse("\n ab\nc\n\n");
112 assert_eq!(art.height(), 2); assert_eq!(art.width(), 4); assert_eq!(art.glyph(2, 0), 'a');
115 assert_eq!(art.glyph(0, 1), 'c');
116 assert!(art.is_ink(2, 0));
117 assert!(!art.is_ink(0, 0)); }
119
120 #[test]
121 fn ink_count_ignores_whitespace() {
122 assert_eq!(Art::parse("a b\n c ").ink_count(), 3);
123 }
124}