termcanvas 0.1.0

A lightweight Rust library for rendering pixels directly in the terminal.
Documentation
#![allow(dead_code)]

use crate::canvas::{Canvas, font::Font};

pub struct DrawBuilder<'a> {
    canvas: &'a mut Canvas,
    font: &'a Font,

    x: u32,
    y: u32,
    color: u32,
    align: Align,
}

impl<'a> DrawBuilder<'a> {
    pub fn at(mut self, x: u32, y: u32) -> Self {
        self.x = x;
        self.y = y;
        self
    }

    pub fn x(mut self, x: u32) -> Self {
        self.x = x;
        self
    }

    pub fn y(mut self, y: u32) -> Self {
        self.y = y;
        self
    }

    pub fn color(mut self, color: u32) -> Self {
        self.color = color;
        self
    }

    pub fn align(mut self, align: Align) -> Self {
        self.align = align;
        self
    }

    pub fn text(self, text: impl Into<String>) {
        self.canvas.draw_text(
            &text.into(),
            self.x,
            self.y,
            self.color,
            self.align,
            self.font,
        );
    }

    pub fn uint(self, value: u32) {
        self.canvas
            .draw_uint(value, self.x, self.y, self.color, self.align, self.font);
    }

    pub fn int(self, value: i32, always_sign: bool) {
        self.canvas.draw_int(
            value,
            self.x,
            self.y,
            self.color,
            self.align,
            self.font,
            always_sign,
        );
    }

    pub fn float(self, value: f32, decimals: usize, always_sign: bool) {
        self.canvas.draw_float(
            value,
            self.x,
            self.y,
            self.color,
            self.align,
            self.font,
            decimals,
            always_sign,
        );
    }
}

pub enum Align {
    Left,
    Right,
    Middle,
}

impl Canvas {
    pub fn draw<'a>(&'a mut self, font: &'a Font) -> DrawBuilder<'a> {
        DrawBuilder {
            canvas: self,
            font,
            x: 0,
            y: 0,
            color: 0xFFFFFFFF,
            align: Align::Left,
        }
    }

    fn draw_text(&mut self, text: &str, x: u32, y: u32, color: u32, align: Align, font: &Font) {
        let glyph_height = font.glyph_height() as u32;
        let tab_size = 4;

        let mut cursor_y = y;

        for line in text.split('\n') {
            let mut line_width = 0i32;
            let mut chars = line.chars().peekable();

            while let Some(c) = chars.next() {
                match c {
                    '\t' => {
                        let tab_w = (glyph_height * tab_size) as i32;
                        line_width += tab_w;
                    }
                    '\\' => match chars.peek() {
                        Some('n') | Some('t') | Some('\\') => {
                            let c2 = chars.next().unwrap();
                            let actual = match c2 {
                                'n' => '\n',
                                't' => '\t',
                                '\\' => '\\',
                                _ => c2,
                            };

                            if let Some(g) = font.glyphs.get(&actual) {
                                line_width += g.len() as i32 / glyph_height as i32;
                            }
                        }
                        _ => {
                            if let Some(g) = font.glyphs.get(&'\\') {
                                line_width += g.len() as i32 / glyph_height as i32;
                            }
                        }
                    },
                    _ => {
                        if let Some(g) = font.glyphs.get(&c) {
                            line_width += g.len() as i32 / glyph_height as i32;
                        }
                    }
                }
            }

            let mut cursor_x = match align {
                Align::Left => x as i32,
                Align::Right => x as i32 - line_width,
                Align::Middle => x as i32 - line_width / 2,
            };

            let mut chars = line.chars().peekable();

            while let Some(c) = chars.next() {
                match c {
                    '\t' => {
                        let tab_w = (glyph_height * tab_size) as i32;
                        let offset = cursor_x - x as i32;
                        cursor_x = x as i32 + ((offset / tab_w) + 1) * tab_w;
                    }

                    '\\' => {
                        let draw_char = match chars.peek() {
                            Some('n') => {
                                chars.next();
                                '\n'
                            }
                            Some('t') => {
                                chars.next();
                                '\t'
                            }
                            Some('\\') => {
                                chars.next();
                                '\\'
                            }
                            _ => '\\',
                        };

                        if draw_char == '\n' {
                            break;
                        }

                        if draw_char == '\t' {
                            let tab_w = (glyph_height * tab_size) as i32;
                            let offset = cursor_x - x as i32;
                            cursor_x = x as i32 + ((offset / tab_w) + 1) * tab_w;
                            continue;
                        }

                        if let Some(g) = font.glyphs.get(&draw_char) {
                            let w = g.len() as i32 / glyph_height as i32;
                            self.draw_character(
                                draw_char,
                                cursor_x as u32,
                                cursor_y,
                                color,
                                Align::Left,
                                font,
                            );
                            cursor_x += w;
                        }
                    }

                    _ => {
                        if let Some(g) = font.glyphs.get(&c) {
                            let w = g.len() as i32 / glyph_height as i32;
                            self.draw_character(
                                c,
                                cursor_x as u32,
                                cursor_y,
                                color,
                                Align::Left,
                                font,
                            );
                            cursor_x += w;
                        }
                    }
                }
            }

            cursor_y += glyph_height + 2;
        }
    }

    fn draw_character(&mut self, c: char, x: u32, y: u32, color: u32, align: Align, font: &Font) {
        let Some(glyph) = font.glyphs.get(&c) else {
            return;
        };

        let height = font.glyph_height() as i32;
        let width = glyph.len() as i32 / height;

        let x = x as i32;
        let y = y as i32;

        let start_x = match align {
            Align::Left => x,
            Align::Right => x as i32 - width,
            Align::Middle => x as i32 - width / 2, // neu
        };

        for gy in 0..height {
            for gx in 0..width {
                let idx = (gy * width + gx) as usize;

                if glyph[idx] == 1 {
                    let dx = start_x + gx;
                    let dy = y + gy;

                    if dx < 0 || dy < 0 || dx >= self.width as i32 || dy >= self.height as i32 {
                        continue;
                    }

                    let draw_y = self.height as i32 - 1 - dy;
                    self.set_pixel(dx as u32, draw_y as u32, color);
                }
            }
        }
    }

    fn draw_uint(&mut self, value: u32, x: u32, y: u32, color: u32, align: Align, font: &Font) {
        let glyph_height = font.glyph_height() as i32;
        let digits: Vec<char> = value.to_string().chars().collect();

        let mut total_width = 0i32;
        for &c in &digits {
            if let Some(glyph) = font.glyphs.get(&c) {
                total_width += glyph.len() as i32 / glyph_height;
            }
        }

        let mut cursor_x = match align {
            Align::Left => x as i32,
            Align::Right => x as i32 - total_width,
            Align::Middle => x as i32 - total_width / 2,
        };

        for &c in &digits {
            if let Some(glyph) = font.glyphs.get(&c) {
                let w = glyph.len() as i32 / glyph_height;
                self.draw_character(c, cursor_x as u32, y, color, Align::Left, font);
                cursor_x += w;
            }
        }
    }

    fn draw_int(
        &mut self,
        value: i32,
        x: u32,
        y: u32,
        color: u32,
        align: Align,
        font: &Font,
        always_show_sign: bool,
    ) {
        let glyph_height = font.glyph_height() as i32;

        let (sign_char, abs_val): (char, u32) = if value < 0 {
            ('-', value.wrapping_abs() as u32)
        } else {
            ('+', value as u32)
        };

        let show_sign = value < 0 || always_show_sign;
        let digits: Vec<char> = abs_val.to_string().chars().collect();

        let mut total_width = 0i32;

        if show_sign {
            if let Some(glyph) = font.glyphs.get(&sign_char) {
                total_width += glyph.len() as i32 / glyph_height;
            }
        }

        for &c in &digits {
            if let Some(glyph) = font.glyphs.get(&c) {
                total_width += glyph.len() as i32 / glyph_height;
            }
        }

        let mut cursor_x = match align {
            Align::Left => x as i32,
            Align::Right => x as i32 - total_width,
            Align::Middle => x as i32 - total_width / 2,
        };

        if show_sign {
            if let Some(glyph) = font.glyphs.get(&sign_char) {
                let w = glyph.len() as i32 / glyph_height;
                self.draw_character(sign_char, cursor_x as u32, y, color, Align::Left, font);
                cursor_x += w;
            }
        }

        for &c in &digits {
            if let Some(glyph) = font.glyphs.get(&c) {
                let w = glyph.len() as i32 / glyph_height;
                self.draw_character(c, cursor_x as u32, y, color, Align::Left, font);
                cursor_x += w;
            }
        }
    }

    fn draw_float(
        &mut self,
        value: f32,
        x: u32,
        y: u32,
        color: u32,
        align: Align,
        font: &Font,
        decimals: usize,
        always_show_sign: bool,
    ) {
        let glyph_height = font.glyph_height() as i32;

        let mut s = format!("{:+.*}", decimals + 5, value);

        if let Some(dot) = s.find('.') {
            let end = dot + 1 + decimals;
            if s.len() > end {
                s.truncate(end);
            }
        }

        if !always_show_sign && s.starts_with('+') {
            s.remove(0);
        }

        let chars: Vec<char> = s.chars().collect();

        let mut total_width = 0i32;
        for &c in &chars {
            if let Some(glyph) = font.glyphs.get(&c) {
                total_width += glyph.len() as i32 / glyph_height;
            }
        }

        let mut cursor_x = match align {
            Align::Left => x as i32,
            Align::Right => x as i32 - total_width,
            Align::Middle => x as i32 - total_width / 2,
        };

        for &c in &chars {
            if let Some(glyph) = font.glyphs.get(&c) {
                let w = glyph.len() as i32 / glyph_height;
                self.draw_character(c, cursor_x as u32, y, color, Align::Left, font);
                cursor_x += w;
            }
        }
    }
}