use kernel_macros::include_bitmap_image; /// 24-bit RGB color used by the framebuffer. #[repr(transparent)] #[derive(Clone, Copy)] pub struct Color(u32); #[allow(unused)] impl Color { pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { Self((r as u32) << 16 | (g as u32) << 8 | b as u32) } pub fn as_bytes(&self) -> [u8; 4] { self.0.to_ne_bytes() } pub const WHITE: Color = Color::from_rgb(255, 255, 255); pub const GRAY: Color = Color::from_rgb(128, 128, 128); pub const LIGHT_GRAY: Color = Color::from_rgb(128, 64, 64); pub const BLACK: Color = Color::from_rgb(0, 0, 0); pub const RED: Color = Color::from_rgb(255, 0, 0); pub const GREEN: Color = Color::from_rgb(0, 255, 0); pub const BLUE: Color = Color::from_rgb(0, 0, 255); } pub trait Draw { /// # Safety /// `x` must be less than `WIDTH` and `y` must be less than `HEIGHT` /// Write a single pixel into the framebuffer (unsafe). /// /// Caller must ensure `x < WIDTH` and `y < HEIGHT`. unsafe fn write_pixel_unsafe(&mut self, x: u16, y: u16, color: Color); fn get_width(&self) -> usize; fn get_height(&self) -> usize; /// Draw a single character with a background color at (x,y). /// /// Uses the embedded font plate to render glyphs into the framebuffer. unsafe fn draw_char_bg(&mut self, x: u16, y: u16, c: char, color: Color, bg_color: Color) { // Get char position within font plate let char_x = (c as usize % 32) * FONT_WIDTH; let char_y = (c as usize / 32) * FONT_HEIGHT; for i in 0..(FONT_WIDTH as u16) { for j in 0..(FONT_HEIGHT as u16) { let xx = x + i; let yy = y + j; if xx < (self.get_width() as u16) && yy < (self.get_height() as u16) { if unsafe { Self::font_plate_index(char_x as u16 + i, char_y as u16 + j) } { unsafe { self.write_pixel_unsafe(xx, yy, color) } } else { unsafe { self.write_pixel_unsafe(xx, yy, bg_color) } } } } } } /// # Safety /// The text must have a length that can fit within a `u16` /// Draw a UTF-8 string at the given position. /// /// Newlines (`\n`) advance `y` by `FONT_HEIGHT` and carriage return (`\r`) /// resets to the starting `x` position. unsafe fn draw_string>( &mut self, x: u16, mut y: u16, str: T, color: Color, bg_color: Color, ) { let mut current_x = x; str.as_ref().chars().for_each(|c| unsafe { match c { '\n' => { current_x = x; y += FONT_HEIGHT as u16; } '\r' => { current_x = x; } c => { self.draw_char_bg(current_x, y, c, color, bg_color); current_x += FONT_WIDTH as u16; } } }); } /// Fill the entire framebuffer with a single color. fn clear_screen(&mut self, color: Color) { for y in 0..self.get_height() { for x in 0..self.get_width() { unsafe { self.write_pixel_unsafe(x as u16, y as u16, color) }; } } } /// Return whether a pixel inside the embedded font plate is set. unsafe fn font_plate_index(x: u16, y: u16) -> bool { let pixel_index = (y as usize) * FONTPLATE_WIDTH + (x as usize); let byte_index = pixel_index / 8; let bit_index = pixel_index % 8; (FONTPLATE[byte_index] >> bit_index) & 0b1 == 0b1 } } pub const FONT_WIDTH: usize = 6; pub const FONT_HEIGHT: usize = 13; pub const FONTPLATE_WIDTH: usize = 32 * FONT_WIDTH; pub const FONTPLATE_HEIGHT: usize = 42 * FONT_HEIGHT; pub const FONTPLATE_SIZE: usize = FONTPLATE_WIDTH * FONTPLATE_HEIGHT / 8; pub static FONTPLATE: [u8; FONTPLATE_SIZE] = include_bitmap_image! {"assets/fontplate.png"};