314 lines
12 KiB
Rust
314 lines
12 KiB
Rust
/// Bitmap font atlas for ASCII 32-126.
|
|
/// 8x12 pixel glyphs arranged in 16 columns x 6 rows = 128x72 texture.
|
|
pub struct FontAtlas {
|
|
pub width: u32,
|
|
pub height: u32,
|
|
pub glyph_width: u32,
|
|
pub glyph_height: u32,
|
|
pub pixels: Vec<u8>, // R8 grayscale
|
|
}
|
|
|
|
/// Classic 8x12 PC BIOS bitmap font data for ASCII 32-126 (95 characters).
|
|
/// Each character is 12 bytes (one byte per row, MSB = leftmost pixel).
|
|
#[rustfmt::skip]
|
|
const FONT_DATA: [u8; 95 * 12] = [
|
|
// 32: Space
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 33: !
|
|
0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00,
|
|
// 34: "
|
|
0x00, 0x6C, 0x6C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 35: #
|
|
0x00, 0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00,
|
|
// 36: $
|
|
0x00, 0x18, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x18, 0x00, 0x00, 0x00, 0x00,
|
|
// 37: %
|
|
0x00, 0x00, 0x62, 0x64, 0x08, 0x10, 0x26, 0x46, 0x00, 0x00, 0x00, 0x00,
|
|
// 38: &
|
|
0x00, 0x30, 0x48, 0x48, 0x30, 0x4A, 0x44, 0x3A, 0x00, 0x00, 0x00, 0x00,
|
|
// 39: '
|
|
0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 40: (
|
|
0x00, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00,
|
|
// 41: )
|
|
0x00, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
|
|
// 42: *
|
|
0x00, 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 43: +
|
|
0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 44: ,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00,
|
|
// 45: -
|
|
0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 46: .
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
|
// 47: /
|
|
0x00, 0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00,
|
|
// 48: 0
|
|
0x00, 0x3C, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 49: 1
|
|
0x00, 0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
|
// 50: 2
|
|
0x00, 0x3C, 0x66, 0x06, 0x0C, 0x18, 0x30, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
|
// 51: 3
|
|
0x00, 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 52: 4
|
|
0x00, 0x0C, 0x1C, 0x2C, 0x4C, 0x7E, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00,
|
|
// 53: 5
|
|
0x00, 0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 54: 6
|
|
0x00, 0x1C, 0x30, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 55: 7
|
|
0x00, 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
|
|
// 56: 8
|
|
0x00, 0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 57: 9
|
|
0x00, 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x0C, 0x38, 0x00, 0x00, 0x00, 0x00,
|
|
// 58: :
|
|
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 59: ;
|
|
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
|
|
// 60: <
|
|
0x00, 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00,
|
|
// 61: =
|
|
0x00, 0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 62: >
|
|
0x00, 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00,
|
|
// 63: ?
|
|
0x00, 0x3C, 0x66, 0x06, 0x0C, 0x18, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00,
|
|
// 64: @
|
|
0x00, 0x3C, 0x66, 0x6E, 0x6A, 0x6E, 0x60, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
|
// 65: A
|
|
0x00, 0x18, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
|
// 66: B
|
|
0x00, 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x00,
|
|
// 67: C
|
|
0x00, 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 68: D
|
|
0x00, 0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00, 0x00, 0x00, 0x00,
|
|
// 69: E
|
|
0x00, 0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
|
// 70: F
|
|
0x00, 0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00,
|
|
// 71: G
|
|
0x00, 0x3C, 0x66, 0x60, 0x6E, 0x66, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
|
// 72: H
|
|
0x00, 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
|
// 73: I
|
|
0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
|
// 74: J
|
|
0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 75: K
|
|
0x00, 0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00, 0x00, 0x00, 0x00,
|
|
// 76: L
|
|
0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
|
// 77: M
|
|
0x00, 0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
|
|
// 78: N
|
|
0x00, 0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
|
// 79: O
|
|
0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 80: P
|
|
0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00,
|
|
// 81: Q
|
|
0x00, 0x3C, 0x66, 0x66, 0x66, 0x6A, 0x6C, 0x36, 0x00, 0x00, 0x00, 0x00,
|
|
// 82: R
|
|
0x00, 0x7C, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
|
// 83: S
|
|
0x00, 0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 84: T
|
|
0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
|
// 85: U
|
|
0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 86: V
|
|
0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
|
|
// 87: W
|
|
0x00, 0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00, 0x00, 0x00, 0x00,
|
|
// 88: X
|
|
0x00, 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
|
// 89: Y
|
|
0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
|
// 90: Z
|
|
0x00, 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
|
// 91: [
|
|
0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 92: backslash
|
|
0x00, 0x40, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00,
|
|
// 93: ]
|
|
0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 94: ^
|
|
0x00, 0x18, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 95: _
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
|
|
// 96: `
|
|
0x00, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 97: a
|
|
0x00, 0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
|
// 98: b
|
|
0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x00,
|
|
// 99: c
|
|
0x00, 0x00, 0x00, 0x3C, 0x66, 0x60, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 100: d
|
|
0x00, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
|
// 101: e
|
|
0x00, 0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 102: f
|
|
0x00, 0x1C, 0x30, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
|
|
// 103: g
|
|
0x00, 0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x3C, 0x00, 0x00, 0x00,
|
|
// 104: h
|
|
0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
|
// 105: i
|
|
0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 106: j
|
|
0x00, 0x0C, 0x00, 0x1C, 0x0C, 0x0C, 0x0C, 0x0C, 0x78, 0x00, 0x00, 0x00,
|
|
// 107: k
|
|
0x00, 0x60, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0x00, 0x00, 0x00, 0x00,
|
|
// 108: l
|
|
0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 109: m
|
|
0x00, 0x00, 0x00, 0xEC, 0xFE, 0xD6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
|
|
// 110: n
|
|
0x00, 0x00, 0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
|
// 111: o
|
|
0x00, 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
|
|
// 112: p
|
|
0x00, 0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x00, 0x00, 0x00,
|
|
// 113: q
|
|
0x00, 0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x00, 0x00, 0x00,
|
|
// 114: r
|
|
0x00, 0x00, 0x00, 0x7C, 0x66, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00,
|
|
// 115: s
|
|
0x00, 0x00, 0x00, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x00, 0x00, 0x00, 0x00,
|
|
// 116: t
|
|
0x00, 0x30, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x1C, 0x00, 0x00, 0x00, 0x00,
|
|
// 117: u
|
|
0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
|
// 118: v
|
|
0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
|
|
// 119: w
|
|
0x00, 0x00, 0x00, 0xC6, 0xC6, 0xD6, 0xFE, 0x6C, 0x00, 0x00, 0x00, 0x00,
|
|
// 120: x
|
|
0x00, 0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00,
|
|
// 121: y
|
|
0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x3C, 0x00, 0x00, 0x00,
|
|
// 122: z
|
|
0x00, 0x00, 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
|
// 123: {
|
|
0x00, 0x0E, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00, 0x00,
|
|
// 124: |
|
|
0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
|
// 125: }
|
|
0x00, 0x70, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00, 0x00,
|
|
// 126: ~
|
|
0x00, 0x32, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
];
|
|
|
|
impl FontAtlas {
|
|
/// Generate a bitmap font atlas with real 8x12 PC BIOS-style glyphs.
|
|
/// Each glyph is 8x12 pixels. Atlas is 16 cols x 6 rows = 128x72.
|
|
pub fn generate() -> Self {
|
|
let glyph_width: u32 = 8;
|
|
let glyph_height: u32 = 12;
|
|
let cols: u32 = 16;
|
|
let rows: u32 = 6;
|
|
let width = cols * glyph_width; // 128
|
|
let height = rows * glyph_height; // 72
|
|
|
|
let mut pixels = vec![0u8; (width * height) as usize];
|
|
|
|
// Unpack 1-bit-per-pixel FONT_DATA into R8 pixel atlas
|
|
for code in 32u8..=126u8 {
|
|
let index = (code - 32) as u32;
|
|
let col = index % cols;
|
|
let row = index / cols;
|
|
let base_x = col * glyph_width;
|
|
let base_y = row * glyph_height;
|
|
let data_offset = (index as usize) * 12;
|
|
|
|
for py in 0..12u32 {
|
|
let row_byte = FONT_DATA[data_offset + py as usize];
|
|
for px in 0..8u32 {
|
|
if (row_byte >> (7 - px)) & 1 != 0 {
|
|
let pixel_idx = ((base_y + py) * width + (base_x + px)) as usize;
|
|
pixels[pixel_idx] = 255;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure pixel (0,0) is white (255) so solid-color rects can sample
|
|
// UV (0,0) and get full brightness for tinting.
|
|
pixels[0] = 255;
|
|
|
|
FontAtlas {
|
|
width,
|
|
height,
|
|
glyph_width,
|
|
glyph_height,
|
|
pixels,
|
|
}
|
|
}
|
|
|
|
/// Returns (u0, v0, u1, v1) UV coordinates for a given character.
|
|
/// Returns coordinates for space if character is out of ASCII range.
|
|
pub fn glyph_uv(&self, ch: char) -> (f32, f32, f32, f32) {
|
|
let code = ch as u32;
|
|
let index = if code >= 32 && code <= 126 {
|
|
code - 32
|
|
} else {
|
|
0 // space
|
|
};
|
|
|
|
let cols = self.width / self.glyph_width;
|
|
let col = index % cols;
|
|
let row = index / cols;
|
|
|
|
let u0 = (col * self.glyph_width) as f32 / self.width as f32;
|
|
let v0 = (row * self.glyph_height) as f32 / self.height as f32;
|
|
let u1 = u0 + self.glyph_width as f32 / self.width as f32;
|
|
let v1 = v0 + self.glyph_height as f32 / self.height as f32;
|
|
|
|
(u0, v0, u1, v1)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_generate_size() {
|
|
let atlas = FontAtlas::generate();
|
|
assert_eq!(atlas.width, 128);
|
|
assert_eq!(atlas.height, 72);
|
|
assert_eq!(atlas.pixels.len(), (128 * 72) as usize);
|
|
}
|
|
|
|
#[test]
|
|
fn test_glyph_uv_space() {
|
|
let atlas = FontAtlas::generate();
|
|
let (u0, v0, u1, v1) = atlas.glyph_uv(' ');
|
|
assert!((u0 - 0.0).abs() < 1e-6);
|
|
assert!((v0 - 0.0).abs() < 1e-6);
|
|
assert!((u1 - 8.0 / 128.0).abs() < 1e-6);
|
|
assert!((v1 - 12.0 / 72.0).abs() < 1e-6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_glyph_uv_a() {
|
|
let atlas = FontAtlas::generate();
|
|
// 'A' = ASCII 65, index = 65 - 32 = 33
|
|
// col = 33 % 16 = 1, row = 33 / 16 = 2
|
|
let (u0, v0, u1, v1) = atlas.glyph_uv('A');
|
|
let expected_u0 = 1.0 * 8.0 / 128.0;
|
|
let expected_v0 = 2.0 * 12.0 / 72.0;
|
|
let expected_u1 = expected_u0 + 8.0 / 128.0;
|
|
let expected_v1 = expected_v0 + 12.0 / 72.0;
|
|
assert!((u0 - expected_u0).abs() < 1e-6, "u0 mismatch: {} vs {}", u0, expected_u0);
|
|
assert!((v0 - expected_v0).abs() < 1e-6, "v0 mismatch: {} vs {}", v0, expected_v0);
|
|
assert!((u1 - expected_u1).abs() < 1e-6);
|
|
assert!((v1 - expected_v1).abs() < 1e-6);
|
|
}
|
|
}
|