unicode_canvas/
canvas.rs

1use crate::fragment::arc;
2use crate::fragment::line;
3use crate::fragment::thick;
4use crate::fragment::Cell;
5use crate::fragment::Fragment;
6use crate::string_buffer::StringBuffer;
7use crate::unicode_map::FRAGMENT_CHAR;
8pub use border::Border;
9use std::collections::HashMap;
10
11mod border;
12
13#[derive(Debug)]
14pub struct Canvas {
15    cells: HashMap<(usize, usize), Vec<Fragment>>,
16}
17
18impl Canvas {
19    pub fn new() -> Self {
20        Canvas {
21            cells: HashMap::new(),
22        }
23    }
24
25    pub fn draw_horizontal_line(
26        &mut self,
27        start: (usize, usize),
28        end: (usize, usize),
29        use_thick: bool,
30    ) {
31        let (x1, y1) = start;
32        let (x2, y2) = end;
33        assert_eq!(y1, y2, "horizontal line must have the same y1 and y2");
34        //swap the points if x1 is greater than x2
35        let (x1, x2) = if x1 > x2 { (x2, x1) } else { (x1, x2) };
36        let k = Cell::K;
37        let m = Cell::M;
38        let o = Cell::O;
39        let mo = if use_thick { thick(m, o) } else { line(m, o) };
40        let km = if use_thick { thick(k, m) } else { line(k, m) };
41
42        let width = x2 - x1 + 1;
43        for i in 0..width {
44            if let Some(existing) = self.cells.get_mut(&(x1 + i, y1)) {
45                if i == 0 {
46                    existing.push(mo);
47                } else if i == width - 1 {
48                    existing.push(km);
49                } else {
50                    existing.push(km);
51                    existing.push(mo);
52                }
53            } else {
54                if i == 0 {
55                    self.cells.insert((x1 + i, y1), vec![mo]);
56                } else if i == width - 1 {
57                    self.cells.insert((x1 + i, y1), vec![km]);
58                } else {
59                    self.cells.insert((x1 + i, y1), vec![mo, km]);
60                }
61            }
62        }
63    }
64
65    /// erase all lines that overlaps this line
66    pub fn eraser_horizontal_line(
67        &mut self,
68        start: (usize, usize),
69        end: (usize, usize),
70        use_thick: bool,
71    ) {
72        let (x1, y1) = start;
73        let (x2, y2) = end;
74        assert_eq!(y1, y2, "horizontal line must have the same y1 and y2");
75        //swap the points if x1 is greater than x2
76        let (x1, x2) = if x1 > x2 { (x2, x1) } else { (x1, x2) };
77        let k = Cell::K;
78        let m = Cell::M;
79        let o = Cell::O;
80        let mo = if use_thick { thick(m, o) } else { line(m, o) };
81        let km = if use_thick { thick(k, m) } else { line(k, m) };
82
83        let width = x2 - x1 + 1;
84        for i in 0..width {
85            if let Some(existing) = self.cells.get_mut(&(x1 + i, y1)) {
86                if i == 0 {
87                    existing.retain(|line| *line != mo);
88                } else if i == width - 1 {
89                    existing.retain(|line| *line != km);
90                } else {
91                    existing.retain(|line| *line != km);
92                    existing.retain(|line| *line != mo);
93                }
94            }
95        }
96    }
97
98    pub fn draw_vertical_line(
99        &mut self,
100        start: (usize, usize),
101        end: (usize, usize),
102        use_thick: bool,
103    ) {
104        let (x1, y1) = start;
105        let (x2, y2) = end;
106        assert_eq!(x1, x2, "veritcal line must have the same x1 and x2");
107        //swap the points if y1 is greater than y2
108        let (y1, y2) = if y1 > y2 { (y2, y1) } else { (y1, y2) };
109
110        let c = Cell::C;
111        let m = Cell::M;
112        let w = Cell::W;
113
114        let mw = if use_thick { thick(m, w) } else { line(m, w) };
115        let cm = if use_thick { thick(c, m) } else { line(c, m) };
116
117        let height = y2 - y1 + 1;
118        for j in 0..height {
119            if let Some(existing) = self.cells.get_mut(&(x1, y1 + j)) {
120                if j == 0 {
121                    existing.push(mw);
122                } else if j == height - 1 {
123                    existing.push(cm);
124                } else {
125                    existing.push(cm);
126                    existing.push(mw);
127                }
128            } else {
129                if j == 0 {
130                    self.cells.insert((x1, y1 + j), vec![mw]);
131                } else if j == height - 1 {
132                    self.cells.insert((x1, y1 + j), vec![cm]);
133                } else {
134                    self.cells.insert((x1, y1 + j), vec![mw, cm]);
135                }
136            }
137        }
138    }
139
140    pub fn draw_rect(
141        &mut self,
142        start: (usize, usize),
143        end: (usize, usize),
144        border: Border,
145    ) {
146        let (x1, y1) = start;
147        let (x2, y2) = end;
148        if border.has_top {
149            self.draw_horizontal_line(
150                (x1, y1),
151                (x2, y1),
152                border.use_thick_border,
153            );
154        }
155        if border.has_bottom {
156            self.draw_horizontal_line(
157                (x1, y2),
158                (x2, y2),
159                border.use_thick_border,
160            );
161        }
162
163        if border.has_left {
164            self.draw_vertical_line(
165                (x1, y1),
166                (x1, y2),
167                border.use_thick_border,
168            );
169        }
170        if border.has_right {
171            self.draw_vertical_line(
172                (x2, y1),
173                (x2, y2),
174                border.use_thick_border,
175            );
176        }
177
178        if !border.use_thick_border {
179            let o = Cell::O;
180            let w = Cell::W;
181            let k = Cell::K;
182            let c = Cell::C;
183            if border.is_top_left_rounded {
184                self.cells.insert((x1, y1), vec![arc(o, w)]);
185            }
186            if border.is_top_right_rounded {
187                self.cells.insert((x2, y1), vec![arc(w, k)]);
188            }
189            if border.is_bottom_left_rounded {
190                self.cells.insert((x1, y2), vec![arc(c, o)]);
191            }
192            if border.is_bottom_right_rounded {
193                self.cells.insert((x2, y2), vec![arc(k, c)]);
194            }
195        }
196    }
197
198    pub fn draw_text(&mut self, start: (usize, usize), text: &str) {
199        let (x, y) = start;
200        for (i, ch) in text.chars().enumerate() {
201            self.cells.insert((x + i, y), vec![Fragment::Char(ch)]);
202        }
203    }
204
205    fn resolve(fragments: &[Fragment]) -> Option<char> {
206        let mut fragments = fragments.to_owned();
207        fragments.sort();
208        fragments.dedup();
209        FRAGMENT_CHAR.get(&fragments).map(|c| *c)
210    }
211
212    pub fn get_cells<'a>(
213        &'a self,
214    ) -> Box<dyn Iterator<Item = (usize, usize, char)> + 'a> {
215        let mut cells = self.cells.iter().collect::<Vec<_>>();
216        cells.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
217        Box::new(cells.into_iter().flat_map(|((x, y), frags)| {
218            Self::resolve(frags).map(|ch| (*x, *y, ch))
219        }))
220    }
221
222    pub fn dump(&self) -> String {
223        let mut sb = StringBuffer::new();
224        let cells = self.get_cells();
225        cells.for_each(|(x, y, ch)| sb.add_char(x as i32, y as i32, ch));
226        sb.to_string()
227    }
228}
229
230#[cfg(test)]
231mod test {
232    use super::*;
233
234    #[test]
235    fn rect1() {
236        let mut canvas = Canvas::new();
237        canvas.draw_rect((0, 0), (2, 2), Border::thin());
238        let mut cells = canvas
239            .cells
240            .iter()
241            .map(|((x, y), frag)| (x, y, frag))
242            .collect::<Vec<_>>();
243        cells.sort_by(|a, b| a.1.cmp(b.1).then(a.0.cmp(b.0)));
244        println!("cells: {:#?}", cells);
245        assert_eq!(cells.len(), 8);
246        let char_cells: Vec<(usize, usize, char)> =
247            canvas.get_cells().collect();
248        println!("char cells: {:#?}", char_cells);
249        println!("dump: \n{}", canvas.dump());
250        assert_eq!(
251            "┌─┐\n\
252             │ │\n\
253             └─┘",
254            canvas.dump()
255        );
256    }
257    #[test]
258    fn rect2() {
259        let mut canvas = Canvas::new();
260        canvas.draw_rect((0, 0), (4, 2), Border::thick());
261        let mut cells = canvas
262            .cells
263            .iter()
264            .map(|((x, y), frag)| (x, y, frag))
265            .collect::<Vec<_>>();
266        cells.sort_by(|a, b| a.1.cmp(b.1).then(a.0.cmp(b.0)));
267        println!("cells: {:#?}", cells);
268        assert_eq!(cells.len(), 12);
269        let char_cells: Vec<(usize, usize, char)> =
270            canvas.get_cells().collect();
271        println!("char cells: {:#?}", char_cells);
272        println!("dump: \n{}", canvas.dump());
273        assert_eq!(
274            "┏━━━┓\n\
275             ┃   ┃\n\
276             ┗━━━┛",
277            canvas.dump()
278        );
279    }
280
281    #[test]
282    fn rect3() {
283        let mut canvas = Canvas::new();
284        canvas.draw_rect((0, 0), (6, 2), Border::rounded());
285        let mut cells = canvas
286            .cells
287            .iter()
288            .map(|((x, y), frag)| (x, y, frag))
289            .collect::<Vec<_>>();
290        cells.sort_by(|a, b| a.1.cmp(b.1).then(a.0.cmp(b.0)));
291        println!("cells: {:#?}", cells);
292        assert_eq!(cells.len(), 16);
293        let char_cells: Vec<(usize, usize, char)> =
294            canvas.get_cells().collect();
295        println!("char cells: {:#?}", char_cells);
296        println!("dump: \n{}", canvas.dump());
297        assert_eq!(
298            "╭─────╮\n\
299             │     │\n\
300             ╰─────╯",
301            canvas.dump()
302        );
303    }
304
305    #[test]
306    fn crossing() {
307        let mut canvas = Canvas::new();
308
309        canvas.draw_rect((0, 0), (8, 4), Border::rounded());
310        canvas.draw_horizontal_line((0, 2), (8, 2), true);
311        canvas.draw_vertical_line((4, 0), (4, 4), false);
312        let mut cells = canvas
313            .cells
314            .iter()
315            .map(|((x, y), frag)| (x, y, frag))
316            .collect::<Vec<_>>();
317        cells.sort_by(|a, b| a.1.cmp(b.1).then(a.0.cmp(b.0)));
318        println!("dump: \n{}", canvas.dump());
319        assert_eq!(
320            "╭───┬───╮\n\
321             │   │   │\n\
322             ┝━━━┿━━━┥\n\
323             │   │   │\n\
324             ╰───┴───╯",
325            canvas.dump()
326        );
327    }
328
329    #[test]
330    fn test_horizontal_line() {
331        let mut canvas = Canvas::new();
332        canvas.draw_horizontal_line((0, 0), (2, 0), false);
333        let mut cells = canvas
334            .cells
335            .iter()
336            .map(|((x, y), frag)| (x, y, frag))
337            .collect::<Vec<_>>();
338        cells.sort_by(|a, b| a.1.cmp(b.1).then(a.0.cmp(b.0)));
339        println!("cells: {:#?}", cells);
340        assert_eq!(cells.len(), 3);
341    }
342
343    #[test]
344    fn test_vertical_line() {
345        let mut canvas = Canvas::new();
346        canvas.draw_vertical_line((0, 0), (0, 2), false);
347        let mut cells = canvas
348            .cells
349            .iter()
350            .map(|((x, y), frag)| (x, y, frag))
351            .collect::<Vec<_>>();
352        cells.sort_by(|a, b| a.1.cmp(b.1).then(a.0.cmp(b.0)));
353        println!("cells: {:#?}", cells);
354        assert_eq!(cells.len(), 3);
355    }
356}