Skip to main content

unicode_plot/
graphics.rs

1use crate::canvas::Canvas;
2use crate::color::CanvasColor;
3
4/// A single cell in a rendered row: a character glyph and its canvas color.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct RowCell {
7    /// The Unicode character for this cell.
8    pub glyph: char,
9    /// The composited canvas color for this cell.
10    pub color: CanvasColor,
11}
12
13/// A buffer of row cells used during row-by-row rendering.
14pub type RowBuffer = Vec<RowCell>;
15
16/// Anything that can be rendered row by row into a [`Plot`](crate::Plot).
17///
18/// All [`Canvas`] types implement this trait automatically via a blanket impl
19/// that delegates to [`Canvas::glyph_at`] and [`Canvas::color_at`].
20pub trait GraphicsArea {
21    /// The number of character rows in the graphics area.
22    fn nrows(&self) -> usize;
23
24    /// The number of character columns in the graphics area.
25    fn ncols(&self) -> usize;
26
27    /// The blank character used to fill empty cells (default: space).
28    fn blank_char(&self) -> char {
29        ' '
30    }
31
32    /// Called before rendering begins. Override for pre-render setup.
33    fn prepare_render(&mut self) {}
34
35    /// Fills `out` with the cells for the given row index.
36    fn render_row(&self, row: usize, out: &mut RowBuffer);
37
38    /// Called after rendering completes. Override for post-render cleanup.
39    fn finish_render(&mut self) {}
40}
41
42impl<C: Canvas> GraphicsArea for C {
43    fn nrows(&self) -> usize {
44        self.char_height()
45    }
46
47    fn ncols(&self) -> usize {
48        self.char_width()
49    }
50
51    fn render_row(&self, row: usize, out: &mut RowBuffer) {
52        out.clear();
53        out.extend(
54            self.row_cells(row)
55                .map(|(glyph, color)| RowCell { glyph, color }),
56        );
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::{GraphicsArea, RowBuffer};
63    use crate::canvas::{
64        AsciiCanvas, BlockCanvas, BrailleCanvas, Canvas, DensityCanvas, DotCanvas,
65    };
66    use crate::color::CanvasColor;
67
68    #[test]
69    fn canvas_graphics_area_render_row_matches_glyph_and_color_cells() {
70        let mut canvas = BrailleCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0);
71        canvas.pixel(0, 0, CanvasColor::BLUE);
72        canvas.pixel(1, 3, CanvasColor::RED);
73
74        let mut row = RowBuffer::new();
75        canvas.render_row(0, &mut row);
76
77        assert_eq!(row.len(), 1);
78        assert_eq!(row[0].glyph, '⢁');
79        assert_eq!(row[0].color, CanvasColor::MAGENTA);
80    }
81
82    #[test]
83    fn canvas_graphics_area_reports_dimensions() {
84        let canvas = BrailleCanvas::new(4, 3, 0.0, 0.0, 1.0, 1.0);
85        assert_eq!(canvas.ncols(), 4);
86        assert_eq!(canvas.nrows(), 3);
87    }
88
89    #[test]
90    fn all_canvas_types_render_rows_through_graphics_area() {
91        fn assert_single_cell<C: Canvas + GraphicsArea>(mut canvas: C) {
92            canvas.pixel(0, 0, CanvasColor::GREEN);
93            let mut row = RowBuffer::new();
94            canvas.render_row(0, &mut row);
95            assert_eq!(row.len(), 1);
96            assert_eq!(row[0].color, CanvasColor::GREEN);
97        }
98
99        assert_single_cell(BrailleCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0));
100        assert_single_cell(BlockCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0));
101        assert_single_cell(AsciiCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0));
102        assert_single_cell(DotCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0));
103        assert_single_cell(DensityCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0));
104    }
105
106    #[test]
107    fn render_row_reuses_buffer_by_replacing_previous_contents() {
108        let mut canvas = DotCanvas::new(2, 1, 0.0, 0.0, 1.0, 1.0);
109        canvas.pixel(0, 0, CanvasColor::YELLOW);
110        canvas.pixel(1, 0, CanvasColor::CYAN);
111
112        let mut row = RowBuffer::new();
113        row.push(super::RowCell {
114            glyph: 'x',
115            color: CanvasColor::RED,
116        });
117
118        canvas.render_row(0, &mut row);
119
120        assert_eq!(row.len(), 2);
121        assert_eq!(row[0].glyph, '\'');
122        assert_eq!(row[0].color, CanvasColor::YELLOW);
123        assert_eq!(row[1].glyph, '\'');
124        assert_eq!(row[1].color, CanvasColor::CYAN);
125    }
126
127    #[test]
128    fn graphics_area_default_hooks_are_noops() {
129        let mut canvas = BlockCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0);
130        assert_eq!(canvas.blank_char(), ' ');
131
132        canvas.prepare_render();
133        canvas.finish_render();
134
135        let mut row = RowBuffer::new();
136        canvas.render_row(0, &mut row);
137        assert_eq!(row.len(), 1);
138    }
139}