unicode-plot 0.1.0

unicode-plot-rs: Unicode terminal plotting library for Rust
Documentation
use crate::canvas::Canvas;
use crate::color::CanvasColor;

/// A single cell in a rendered row: a character glyph and its canvas color.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RowCell {
    /// The Unicode character for this cell.
    pub glyph: char,
    /// The composited canvas color for this cell.
    pub color: CanvasColor,
}

/// A buffer of row cells used during row-by-row rendering.
pub type RowBuffer = Vec<RowCell>;

/// Anything that can be rendered row by row into a [`Plot`](crate::Plot).
///
/// All [`Canvas`] types implement this trait automatically via a blanket impl
/// that delegates to [`Canvas::glyph_at`] and [`Canvas::color_at`].
pub trait GraphicsArea {
    /// The number of character rows in the graphics area.
    fn nrows(&self) -> usize;

    /// The number of character columns in the graphics area.
    fn ncols(&self) -> usize;

    /// The blank character used to fill empty cells (default: space).
    fn blank_char(&self) -> char {
        ' '
    }

    /// Called before rendering begins. Override for pre-render setup.
    fn prepare_render(&mut self) {}

    /// Fills `out` with the cells for the given row index.
    fn render_row(&self, row: usize, out: &mut RowBuffer);

    /// Called after rendering completes. Override for post-render cleanup.
    fn finish_render(&mut self) {}
}

impl<C: Canvas> GraphicsArea for C {
    fn nrows(&self) -> usize {
        self.char_height()
    }

    fn ncols(&self) -> usize {
        self.char_width()
    }

    fn render_row(&self, row: usize, out: &mut RowBuffer) {
        out.clear();
        out.extend(
            self.row_cells(row)
                .map(|(glyph, color)| RowCell { glyph, color }),
        );
    }
}

#[cfg(test)]
mod tests {
    use super::{GraphicsArea, RowBuffer};
    use crate::canvas::{
        AsciiCanvas, BlockCanvas, BrailleCanvas, Canvas, DensityCanvas, DotCanvas,
    };
    use crate::color::CanvasColor;

    #[test]
    fn canvas_graphics_area_render_row_matches_glyph_and_color_cells() {
        let mut canvas = BrailleCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0);
        canvas.pixel(0, 0, CanvasColor::BLUE);
        canvas.pixel(1, 3, CanvasColor::RED);

        let mut row = RowBuffer::new();
        canvas.render_row(0, &mut row);

        assert_eq!(row.len(), 1);
        assert_eq!(row[0].glyph, '');
        assert_eq!(row[0].color, CanvasColor::MAGENTA);
    }

    #[test]
    fn canvas_graphics_area_reports_dimensions() {
        let canvas = BrailleCanvas::new(4, 3, 0.0, 0.0, 1.0, 1.0);
        assert_eq!(canvas.ncols(), 4);
        assert_eq!(canvas.nrows(), 3);
    }

    #[test]
    fn all_canvas_types_render_rows_through_graphics_area() {
        fn assert_single_cell<C: Canvas + GraphicsArea>(mut canvas: C) {
            canvas.pixel(0, 0, CanvasColor::GREEN);
            let mut row = RowBuffer::new();
            canvas.render_row(0, &mut row);
            assert_eq!(row.len(), 1);
            assert_eq!(row[0].color, CanvasColor::GREEN);
        }

        assert_single_cell(BrailleCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0));
        assert_single_cell(BlockCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0));
        assert_single_cell(AsciiCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0));
        assert_single_cell(DotCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0));
        assert_single_cell(DensityCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0));
    }

    #[test]
    fn render_row_reuses_buffer_by_replacing_previous_contents() {
        let mut canvas = DotCanvas::new(2, 1, 0.0, 0.0, 1.0, 1.0);
        canvas.pixel(0, 0, CanvasColor::YELLOW);
        canvas.pixel(1, 0, CanvasColor::CYAN);

        let mut row = RowBuffer::new();
        row.push(super::RowCell {
            glyph: 'x',
            color: CanvasColor::RED,
        });

        canvas.render_row(0, &mut row);

        assert_eq!(row.len(), 2);
        assert_eq!(row[0].glyph, '\'');
        assert_eq!(row[0].color, CanvasColor::YELLOW);
        assert_eq!(row[1].glyph, '\'');
        assert_eq!(row[1].color, CanvasColor::CYAN);
    }

    #[test]
    fn graphics_area_default_hooks_are_noops() {
        let mut canvas = BlockCanvas::new(1, 1, 0.0, 0.0, 1.0, 1.0);
        assert_eq!(canvas.blank_char(), ' ');

        canvas.prepare_render();
        canvas.finish_render();

        let mut row = RowBuffer::new();
        canvas.render_row(0, &mut row);
        assert_eq!(row.len(), 1);
    }
}