rustscii/rendering/
terminal.rs

1use image::DynamicImage;
2use image::GenericImageView;
3use image::ImageBuffer;
4use std::cell::RefCell;
5use std::collections::HashMap;
6use std::path::Path;
7use std::rc::Rc;
8
9/// Terminal represents a simple terminal-like renderer.
10pub struct Terminal {
11    char_width: usize,
12    char_height: usize,
13    font: DynamicImage,
14    characters: HashMap<char, DynamicImage>,
15    grid: Vec<Vec<char>>,
16}
17
18impl Terminal {
19    /// Create a new Terminal instance.
20    pub fn new(font_path: &str, char_width: usize, char_height: usize) -> Self {
21        let font = image::open(Path::new(font_path)).expect("Failed to load font image");
22        let characters = HashMap::new();
23        let grid = vec![vec![' '; 80]; 25]; // Initialize a 80x25 character grid with spaces.
24
25        Terminal {
26            char_width,
27            char_height,
28            font,
29            characters,
30            grid,
31        }
32    }
33
34    /// Load characters from the font image and store them in the characters map.
35    pub fn load_characters(&mut self) {
36        for ascii_code in 32..=127 {
37            let character = char::from(ascii_code);
38            let char_x = (ascii_code - 32) % 16 * self.char_width as u8;
39            let char_y = (ascii_code - 32) / 16 * self.char_height as u8;
40
41            let char_img = self
42                .font
43                .crop_imm(char_x as u32, char_y as u32, self.char_width as u32, self.char_height as u32);
44            self.characters.insert(character, char_img);
45        }
46    }
47
48    /// Set a character in the grid at the specified position.
49    pub fn set_char(&mut self, x: usize, y: usize, character: char) {
50        if x < self.grid[0].len() && y < self.grid.len() {
51            self.grid[y][x] = character;
52        }
53    }
54
55    /// Render the grid to an image.
56    pub fn render(&self) -> DynamicImage {
57        let mut img = ImageBuffer::new(
58            self.char_width as u32 * self.grid[0].len() as u32,
59            self.char_height as u32 * self.grid.len() as u32,
60        );
61
62        for (row, line) in self.grid.iter().enumerate() {
63            for (col, character) in line.iter().enumerate() {
64                if let Some(char_img) = self.characters.get(character) {
65                    for (x, y, pixel) in char_img.pixels() {
66                            img.put_pixel(
67                                (col as u32 * self.char_width as u32 + x) as u32,
68                                (row as u32 * self.char_height as u32 + y) as u32,
69                                pixel,
70                            );
71                    }
72                }
73            }
74        }
75
76        DynamicImage::ImageRgba8(img)
77    }
78
79    /// Get an iterator over the raw grid data.
80    pub fn get_raw_grid(&self) -> impl Iterator<Item = &Vec<char>> {
81        self.grid.iter()
82    }
83
84    /// Get an Rc<RefCell<Terminal>> reference to itself.
85    pub fn get_rc(self) -> Rc<RefCell<Terminal>> {
86        Rc::new(RefCell::new(self))
87    }
88}