eclipse_framebuffer 0.1.0

A lightweight no_std framebuffer text renderer with scrolling support for bare-metal Rust and the Limine bootloader
Documentation
#![no_std]

use core::fmt;

#[repr(C, packed)]
struct PSF1Header {
    magic: [u8; 2],
    mode: u8,
    charsize: u8,
}

#[repr(C, packed)]
struct PSF2Header {
    magic: [u8; 4],
    version: u32,
    headersize: u32,
    flags: u32,
    numglyph: u32,
    bytesperglyph: u32,
    height: u32,
    width: u32,
}

static mut RENDERER: Option<ScrollingTextRenderer> = None;

pub struct ScrollingTextRenderer {
    framebuffer: *mut u8,
    width: usize,
    height: usize,
    pitch: usize,
    bpp: usize,
    x: usize,
    y: usize,
    fg_color: u32,
    bg_color: u32,
    font_data: &'static [u8],
    char_width: usize,
    char_height: usize,
    bytes_per_glyph: usize,
}

unsafe impl Send for ScrollingTextRenderer {}
unsafe impl Sync for ScrollingTextRenderer {}

impl ScrollingTextRenderer {
    pub fn init(
        framebuffer: *mut u8,
        width: usize,
        height: usize,
        pitch: usize,
        bpp: usize,
        font_data: &'static [u8],
    ) {
        let (char_width, char_height, bytes_per_glyph) = Self::parse_psf(font_data);
        
        unsafe {
            RENDERER = Some(Self {
                framebuffer,
                width,
                height,
                pitch,
                bpp,
                x: 0,
                y: 0,
                fg_color: 0xFFFFFF,
                bg_color: 0x000000,
                font_data,
                char_width,
                char_height,
                bytes_per_glyph,
            });
        }
    }

    pub fn get() -> &'static mut Self {
        unsafe { RENDERER.as_mut().expect("Renderer not initialized") }
    }

    fn parse_psf(data: &[u8]) -> (usize, usize, usize) {
        if data.len() >= 32 && &data[0..4] == b"\x72\xb5\x4a\x86" {
            let header = unsafe { &*(data.as_ptr() as *const PSF2Header) };
            return (
                header.width as usize,
                header.height as usize,
                header.bytesperglyph as usize,
            );
        }
        
        if data.len() >= 4 && &data[0..2] == b"\x36\x04" {
            let header = unsafe { &*(data.as_ptr() as *const PSF1Header) };
            let height = header.charsize as usize;
            let width = 8;
            let bytes_per_glyph = height;
            return (width, height, bytes_per_glyph);
        }
        
        (8, 16, 16)
    }

    fn get_glyph_offset(&self, ch: char) -> usize {
        let idx = ch as usize;
        let max_glyphs = (self.font_data.len() - self.header_size()) / self.bytes_per_glyph;
        
        let glyph_idx = if idx < max_glyphs { idx } else { 0 };
        self.header_size() + glyph_idx * self.bytes_per_glyph
    }

    fn header_size(&self) -> usize {
        if self.font_data.len() >= 32 && &self.font_data[0..4] == b"\x72\xb5\x4a\x86" {
            let header = unsafe { &*(self.font_data.as_ptr() as *const PSF2Header) };
            header.headersize as usize
        } else {
            4
        }
    }

    pub fn set_colors(&mut self, fg: u32, bg: u32) {
        self.fg_color = fg;
        self.bg_color = bg;
    }

    fn put_pixel(&self, x: usize, y: usize, color: u32) {
        if x >= self.width || y >= self.height {
            return;
        }

        let offset = y * self.pitch + x * (self.bpp / 8);
        unsafe {
            let pixel = self.framebuffer.add(offset) as *mut u32;
            *pixel = color;
        }
    }

    fn draw_char(&self, ch: char, x: usize, y: usize) {
        let glyph_offset = self.get_glyph_offset(ch);
        let glyph_data = &self.font_data[glyph_offset..glyph_offset + self.bytes_per_glyph];
        
        let bytes_per_line = (self.char_width + 7) / 8;
        
        for row in 0..self.char_height {
            let line_offset = row * bytes_per_line;
            
            for col in 0..self.char_width {
                let byte_idx = line_offset + (col / 8);
                let bit_idx = 7 - (col % 8);
                
                if byte_idx < glyph_data.len() {
                    let bit = (glyph_data[byte_idx] >> bit_idx) & 1;
                    let color = if bit == 1 { self.fg_color } else { self.bg_color };
                    self.put_pixel(x + col, y + row, color);
                }
            }
        }
    }

    fn scroll(&mut self) {
        let line_height = self.char_height;
        let bytes_per_pixel = self.bpp / 8;
        
        unsafe {
            for y in line_height..self.height {
                for x in 0..self.width {
                    let src_offset = y * self.pitch + x * bytes_per_pixel;
                    let dst_offset = (y - line_height) * self.pitch + x * bytes_per_pixel;
                    
                    let src = self.framebuffer.add(src_offset) as *const u32;
                    let dst = self.framebuffer.add(dst_offset) as *mut u32;
                    *dst = *src;
                }
            }
            
            let start_y = self.height - line_height;
            for y in start_y..self.height {
                for x in 0..self.width {
                    self.put_pixel(x, y, self.bg_color);
                }
            }
        }
        
        self.y -= line_height;
    }

    pub fn write_char(&mut self, ch: char) {
        match ch {
            '\n' => {
                self.x = 0;
                self.y += self.char_height;
            }
            '\r' => {
                self.x = 0;
            }
            '\t' => {
                let tab_width = self.char_width * 4;
                self.x = ((self.x + tab_width) / tab_width) * tab_width;
                if self.x >= self.width {
                    self.x = 0;
                    self.y += self.char_height;
                }
            }
            _ => {
                if self.x + self.char_width > self.width {
                    self.x = 0;
                    self.y += self.char_height;
                }
                
                if self.y + self.char_height > self.height {
                    self.scroll();
                }
                
                self.draw_char(ch, self.x, self.y);
                self.x += self.char_width;
            }
        }
    }

    pub fn write_str(&mut self, s: &str) {
        for ch in s.chars() {
            self.write_char(ch);
        }
    }

    pub fn clear(&mut self) {
        for y in 0..self.height {
            for x in 0..self.width {
                self.put_pixel(x, y, self.bg_color);
            }
        }
        self.x = 0;
        self.y = 0;
    }
}

impl fmt::Write for ScrollingTextRenderer {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.write_str(s);
        Ok(())
    }
}

#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => {{
        use core::fmt::Write;
        let _ = write!($crate::ScrollingTextRenderer::get(), $($arg)*);
    }};
}

#[macro_export]
macro_rules! println {
    () => {
        $crate::ScrollingTextRenderer::get().write_char('\n')
    };
    ($($arg:tt)*) => {{
        use core::fmt::Write;
        let _ = write!($crate::ScrollingTextRenderer::get(), $($arg)*);
        $crate::ScrollingTextRenderer::get().write_char('\n');
    }};
}