pub const FONT_5X7: [[u8; 7]; 95] = [
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x04],
[0x0A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x0A, 0x1F, 0x0A, 0x0A, 0x1F, 0x0A, 0x00],
[0x04, 0x0F, 0x14, 0x0E, 0x05, 0x1E, 0x04],
[0x18, 0x19, 0x02, 0x04, 0x08, 0x13, 0x03],
[0x0C, 0x12, 0x14, 0x08, 0x15, 0x12, 0x0D],
[0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02],
[0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08],
[0x00, 0x04, 0x15, 0x0E, 0x15, 0x04, 0x00],
[0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x08],
[0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06],
[0x01, 0x02, 0x02, 0x04, 0x08, 0x08, 0x10],
[0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E],
[0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E],
[0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F],
[0x1F, 0x02, 0x04, 0x02, 0x01, 0x11, 0x0E],
[0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02],
[0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E],
[0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E],
[0x1F, 0x01, 0x02, 0x04, 0x04, 0x04, 0x04],
[0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E],
[0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C],
[0x00, 0x06, 0x06, 0x00, 0x06, 0x06, 0x00],
[0x00, 0x06, 0x06, 0x00, 0x06, 0x04, 0x08],
[0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02],
[0x00, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x00],
[0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08],
[0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04],
[0x0E, 0x11, 0x01, 0x0D, 0x15, 0x15, 0x0E],
[0x04, 0x0A, 0x11, 0x11, 0x1F, 0x11, 0x11],
[0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E],
[0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E],
[0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C],
[0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F],
[0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10],
[0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F],
[0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11],
[0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E],
[0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C],
[0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11],
[0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F],
[0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11],
[0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11],
[0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
[0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10],
[0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D],
[0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11],
[0x0F, 0x10, 0x10, 0x0E, 0x01, 0x01, 0x1E],
[0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
[0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
[0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04],
[0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11],
[0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11],
[0x11, 0x11, 0x11, 0x0A, 0x04, 0x04, 0x04],
[0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F],
[0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0E],
[0x10, 0x08, 0x08, 0x04, 0x02, 0x02, 0x01],
[0x0E, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0E],
[0x04, 0x0A, 0x11, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F],
[0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x0E, 0x01, 0x0F, 0x11, 0x0F],
[0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x1E],
[0x00, 0x00, 0x0E, 0x10, 0x10, 0x11, 0x0E],
[0x01, 0x01, 0x0F, 0x11, 0x11, 0x11, 0x0F],
[0x00, 0x00, 0x0E, 0x11, 0x1F, 0x10, 0x0E],
[0x06, 0x09, 0x08, 0x1C, 0x08, 0x08, 0x08],
[0x00, 0x00, 0x0F, 0x11, 0x11, 0x0F, 0x01],
[0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x11],
[0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x0E],
[0x02, 0x00, 0x06, 0x02, 0x02, 0x02, 0x12],
[0x10, 0x10, 0x12, 0x14, 0x18, 0x14, 0x12],
[0x0C, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E],
[0x00, 0x00, 0x1A, 0x15, 0x15, 0x15, 0x11],
[0x00, 0x00, 0x1E, 0x11, 0x11, 0x11, 0x11],
[0x00, 0x00, 0x0E, 0x11, 0x11, 0x11, 0x0E],
[0x00, 0x00, 0x1E, 0x11, 0x11, 0x1E, 0x10],
[0x00, 0x00, 0x0F, 0x11, 0x11, 0x0F, 0x01],
[0x00, 0x00, 0x16, 0x19, 0x10, 0x10, 0x10],
[0x00, 0x00, 0x0F, 0x10, 0x0E, 0x01, 0x1E],
[0x08, 0x08, 0x1E, 0x08, 0x08, 0x09, 0x06],
[0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0F],
[0x00, 0x00, 0x11, 0x11, 0x11, 0x0A, 0x04],
[0x00, 0x00, 0x11, 0x15, 0x15, 0x15, 0x0A],
[0x00, 0x00, 0x11, 0x0A, 0x04, 0x0A, 0x11],
[0x00, 0x00, 0x11, 0x11, 0x0F, 0x01, 0x0E],
[0x00, 0x00, 0x1F, 0x02, 0x04, 0x08, 0x1F],
[0x02, 0x04, 0x04, 0x08, 0x04, 0x04, 0x02],
[0x04, 0x04, 0x04, 0x00, 0x04, 0x04, 0x04],
[0x08, 0x04, 0x04, 0x02, 0x04, 0x04, 0x08],
[0x00, 0x08, 0x15, 0x02, 0x00, 0x00, 0x00],
];
#[derive(Debug, Clone)]
pub struct GlyphBitmap {
pub width: u8,
pub height: u8,
pub rows: Vec<u8>,
}
impl GlyphBitmap {
pub fn pixel(&self, row: u8, col: u8) -> bool {
if row >= self.height || col >= self.width {
return false;
}
let shift = self.width - 1 - col;
(self.rows[row as usize] >> shift) & 1 == 1
}
}
pub struct FontRenderer;
impl FontRenderer {
fn glyph_data(c: char) -> &'static [u8; 7] {
let idx = c as usize;
if idx >= 32 && idx <= 126 {
&FONT_5X7[idx - 32]
} else {
&FONT_5X7[0] }
}
pub fn render_char(c: char, _color: [u8; 3]) -> GlyphBitmap {
GlyphBitmap {
width: 5,
height: 7,
rows: Self::glyph_data(c).to_vec(),
}
}
pub fn render_text(text: &str, color: [u8; 3], bg: Option<[u8; 3]>) -> Vec<Vec<[u8; 3]>> {
let bg_color = bg.unwrap_or([0, 0, 0]);
let (w, h) = Self::text_size(text);
if w == 0 {
return vec![];
}
let mut img = vec![vec![bg_color; w]; h];
let mut x_off = 0usize;
for c in text.chars() {
let glyph = Self::render_char(c, color);
for row in 0..7usize {
for col in 0..5usize {
if glyph.pixel(row as u8, col as u8) {
if x_off + col < w {
img[row][x_off + col] = color;
}
}
}
}
x_off += 6; }
img
}
pub fn blit(
image: &mut Vec<Vec<[u8; 3]>>,
text_bitmap: &Vec<Vec<[u8; 3]>>,
x: usize,
y: usize,
) {
let img_h = image.len();
let img_w = if img_h > 0 { image[0].len() } else { return };
for (row_idx, row) in text_bitmap.iter().enumerate() {
let dest_row = y + row_idx;
if dest_row >= img_h {
break;
}
for (col_idx, &px) in row.iter().enumerate() {
let dest_col = x + col_idx;
if dest_col >= img_w {
break;
}
image[dest_row][dest_col] = px;
}
}
}
pub fn text_size(text: &str) -> (usize, usize) {
let len = text.chars().count();
if len == 0 {
return (0, 0);
}
let w = len * 5 + (len - 1);
(w, 7)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_font_array_size() {
assert_eq!(FONT_5X7.len(), 95);
}
#[test]
fn test_glyph_bitmap_pixel_a() {
let glyph = FontRenderer::render_char('A', [255, 255, 255]);
assert_eq!(glyph.width, 5);
assert_eq!(glyph.height, 7);
assert!(glyph.pixel(0, 2));
assert!(!glyph.pixel(0, 0));
}
#[test]
fn test_glyph_pixel_out_of_bounds() {
let glyph = FontRenderer::render_char('A', [255, 0, 0]);
assert!(!glyph.pixel(10, 0));
assert!(!glyph.pixel(0, 10));
}
#[test]
fn test_text_size_single() {
let (w, h) = FontRenderer::text_size("A");
assert_eq!(w, 5);
assert_eq!(h, 7);
}
#[test]
fn test_text_size_multi() {
let (w, h) = FontRenderer::text_size("AB");
assert_eq!(w, 11); assert_eq!(h, 7);
}
#[test]
fn test_text_size_empty() {
assert_eq!(FontRenderer::text_size(""), (0, 0));
}
#[test]
fn test_render_text_dimensions() {
let bmp = FontRenderer::render_text("Hi", [255, 0, 0], None);
assert_eq!(bmp.len(), 7);
assert_eq!(bmp[0].len(), 11);
}
#[test]
fn test_blit_basic() {
let mut img = vec![vec![[0u8; 3]; 20]; 20];
let text = FontRenderer::render_text("A", [255, 255, 255], None);
FontRenderer::blit(&mut img, &text, 0, 0);
assert_eq!(img[0][2], [255, 255, 255]);
}
#[test]
fn test_blit_clipped_at_boundary() {
let mut img = vec![vec![[0u8; 3]; 4]; 4];
let text = FontRenderer::render_text("HELLO", [255, 0, 0], None);
FontRenderer::blit(&mut img, &text, 0, 0);
}
#[test]
fn test_blit_offset() {
let mut img = vec![vec![[0u8; 3]; 30]; 20];
let text = FontRenderer::render_text("Z", [0, 255, 0], Some([0, 0, 0]));
FontRenderer::blit(&mut img, &text, 10, 5);
assert_eq!(img.len(), 20);
assert_eq!(img[0].len(), 30);
}
#[test]
fn test_render_char_space() {
let glyph = FontRenderer::render_char(' ', [255, 255, 255]);
for r in 0..7 {
for c in 0..5 {
assert!(!glyph.pixel(r, c));
}
}
}
}