Skip to main content

arcane_engine/renderer/
font.rs

1/// Built-in CP437 8×8 bitmap font.
2///
3/// Covers ASCII 32 (space) through 127 (DEL) = 96 glyphs.
4/// Layout: 16 columns × 6 rows, each glyph 8×8 pixels.
5/// Result texture: 128×48 pixels RGBA.
6
7/// 96 glyphs × 8 bytes each. Each byte is one row of 8 pixels (MSB = left).
8/// Glyph order: ASCII 32..127 (space, !, ", #, ..., ~, DEL).
9const FONT_DATA: [u8; 768] = [
10    // 32: space
11    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
12    // 33: !
13    0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00,
14    // 34: "
15    0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00,
16    // 35: #
17    0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00,
18    // 36: $
19    0x18, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x18, 0x00,
20    // 37: %
21    0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00,
22    // 38: &
23    0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0x76, 0x00,
24    // 39: '
25    0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
26    // 40: (
27    0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00,
28    // 41: )
29    0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00,
30    // 42: *
31    0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00,
32    // 43: +
33    0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00,
34    // 44: ,
35    0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30,
36    // 45: -
37    0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00,
38    // 46: .
39    0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00,
40    // 47: /
41    0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00,
42    // 48: 0
43    0x7C, 0xCE, 0xDE, 0xF6, 0xE6, 0xC6, 0x7C, 0x00,
44    // 49: 1
45    0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00,
46    // 50: 2
47    0x7C, 0xC6, 0x06, 0x7C, 0xC0, 0xC0, 0xFE, 0x00,
48    // 51: 3
49    0x7C, 0xC6, 0x06, 0x3C, 0x06, 0xC6, 0x7C, 0x00,
50    // 52: 4
51    0x0C, 0x2C, 0x4C, 0x8C, 0xFE, 0x0C, 0x0C, 0x00,
52    // 53: 5
53    0xFE, 0xC0, 0xFC, 0x06, 0x06, 0xC6, 0x7C, 0x00,
54    // 54: 6
55    0x7C, 0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0x7C, 0x00,
56    // 55: 7
57    0xFE, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00,
58    // 56: 8
59    0x7C, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0x7C, 0x00,
60    // 57: 9
61    0x7C, 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0x7C, 0x00,
62    // 58: :
63    0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00,
64    // 59: ;
65    0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x30, 0x00,
66    // 60: <
67    0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x00,
68    // 61: =
69    0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00,
70    // 62: >
71    0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x00,
72    // 63: ?
73    0x7C, 0xC6, 0x0C, 0x18, 0x18, 0x00, 0x18, 0x00,
74    // 64: @
75    0x7C, 0xC6, 0xDE, 0xDE, 0xDC, 0xC0, 0x7C, 0x00,
76    // 65: A
77    0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x00,
78    // 66: B
79    0xFC, 0xC6, 0xC6, 0xFC, 0xC6, 0xC6, 0xFC, 0x00,
80    // 67: C
81    0x7C, 0xC6, 0xC0, 0xC0, 0xC0, 0xC6, 0x7C, 0x00,
82    // 68: D
83    0xF8, 0xCC, 0xC6, 0xC6, 0xC6, 0xCC, 0xF8, 0x00,
84    // 69: E
85    0xFE, 0xC0, 0xC0, 0xFC, 0xC0, 0xC0, 0xFE, 0x00,
86    // 70: F
87    0xFE, 0xC0, 0xC0, 0xFC, 0xC0, 0xC0, 0xC0, 0x00,
88    // 71: G
89    0x7C, 0xC6, 0xC0, 0xCE, 0xC6, 0xC6, 0x7E, 0x00,
90    // 72: H
91    0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0x00,
92    // 73: I
93    0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00,
94    // 74: J
95    0x06, 0x06, 0x06, 0x06, 0xC6, 0xC6, 0x7C, 0x00,
96    // 75: K
97    0xC6, 0xCC, 0xD8, 0xF0, 0xD8, 0xCC, 0xC6, 0x00,
98    // 76: L
99    0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0x00,
100    // 77: M
101    0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0x00,
102    // 78: N
103    0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00,
104    // 79: O
105    0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00,
106    // 80: P
107    0xFC, 0xC6, 0xC6, 0xFC, 0xC0, 0xC0, 0xC0, 0x00,
108    // 81: Q
109    0x7C, 0xC6, 0xC6, 0xC6, 0xD6, 0xCC, 0x76, 0x00,
110    // 82: R
111    0xFC, 0xC6, 0xC6, 0xFC, 0xD8, 0xCC, 0xC6, 0x00,
112    // 83: S
113    0x7C, 0xC6, 0xC0, 0x7C, 0x06, 0xC6, 0x7C, 0x00,
114    // 84: T
115    0xFE, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00,
116    // 85: U
117    0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00,
118    // 86: V
119    0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00,
120    // 87: W
121    0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00,
122    // 88: X
123    0xC6, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0xC6, 0x00,
124    // 89: Y
125    0xC6, 0xC6, 0x6C, 0x38, 0x18, 0x18, 0x18, 0x00,
126    // 90: Z
127    0xFE, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFE, 0x00,
128    // 91: [
129    0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00,
130    // 92: backslash
131    0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00,
132    // 93: ]
133    0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00,
134    // 94: ^
135    0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00,
136    // 95: _
137    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00,
138    // 96: `
139    0x18, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00,
140    // 97: a
141    0x00, 0x00, 0x7C, 0x06, 0x7E, 0xC6, 0x7E, 0x00,
142    // 98: b
143    0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xFC, 0x00,
144    // 99: c
145    0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC6, 0x7C, 0x00,
146    // 100: d
147    0x06, 0x06, 0x7E, 0xC6, 0xC6, 0xC6, 0x7E, 0x00,
148    // 101: e
149    0x00, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0x7C, 0x00,
150    // 102: f
151    0x1C, 0x36, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x00,
152    // 103: g
153    0x00, 0x00, 0x7E, 0xC6, 0xC6, 0x7E, 0x06, 0x7C,
154    // 104: h
155    0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x00,
156    // 105: i
157    0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00,
158    // 106: j
159    0x06, 0x00, 0x06, 0x06, 0x06, 0xC6, 0xC6, 0x7C,
160    // 107: k
161    0xC0, 0xC0, 0xCC, 0xD8, 0xF0, 0xD8, 0xCC, 0x00,
162    // 108: l
163    0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00,
164    // 109: m
165    0x00, 0x00, 0xCC, 0xFE, 0xD6, 0xC6, 0xC6, 0x00,
166    // 110: n
167    0x00, 0x00, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x00,
168    // 111: o
169    0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00,
170    // 112: p
171    0x00, 0x00, 0xFC, 0xC6, 0xC6, 0xFC, 0xC0, 0xC0,
172    // 113: q
173    0x00, 0x00, 0x7E, 0xC6, 0xC6, 0x7E, 0x06, 0x06,
174    // 114: r
175    0x00, 0x00, 0xDC, 0xE6, 0xC0, 0xC0, 0xC0, 0x00,
176    // 115: s
177    0x00, 0x00, 0x7E, 0xC0, 0x7C, 0x06, 0xFC, 0x00,
178    // 116: t
179    0x30, 0x30, 0x7C, 0x30, 0x30, 0x36, 0x1C, 0x00,
180    // 117: u
181    0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x00,
182    // 118: v
183    0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00,
184    // 119: w
185    0x00, 0x00, 0xC6, 0xC6, 0xD6, 0xFE, 0x6C, 0x00,
186    // 120: x
187    0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00,
188    // 121: y
189    0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x7C,
190    // 122: z
191    0x00, 0x00, 0xFE, 0x0C, 0x38, 0x60, 0xFE, 0x00,
192    // 123: {
193    0x0E, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0E, 0x00,
194    // 124: |
195    0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00,
196    // 125: }
197    0x70, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x70, 0x00,
198    // 126: ~
199    0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
200    // 127: DEL (block)
201    0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x00,
202];
203
204/// Font atlas dimensions.
205const ATLAS_COLUMNS: u32 = 16;
206const ATLAS_ROWS: u32 = 6;
207const GLYPH_W: u32 = 8;
208const GLYPH_H: u32 = 8;
209
210/// Generate the built-in font as an RGBA texture.
211///
212/// Returns `(rgba_pixels, width, height)` where the texture is
213/// 128×48 (16 glyphs wide × 6 glyphs tall, 8×8 each).
214/// White glyphs on transparent background.
215pub fn generate_builtin_font() -> (Vec<u8>, u32, u32) {
216    let width = ATLAS_COLUMNS * GLYPH_W;
217    let height = ATLAS_ROWS * GLYPH_H;
218    let mut pixels = vec![0u8; (width * height * 4) as usize];
219
220    for glyph_idx in 0..96u32 {
221        let col = glyph_idx % ATLAS_COLUMNS;
222        let row = glyph_idx / ATLAS_COLUMNS;
223        let base_x = col * GLYPH_W;
224        let base_y = row * GLYPH_H;
225
226        for py in 0..GLYPH_H {
227            let byte = FONT_DATA[(glyph_idx * 8 + py) as usize];
228            for px in 0..GLYPH_W {
229                let bit = (byte >> (7 - px)) & 1;
230                let x = base_x + px;
231                let y = base_y + py;
232                let offset = ((y * width + x) * 4) as usize;
233                if bit == 1 {
234                    pixels[offset] = 255;     // R
235                    pixels[offset + 1] = 255; // G
236                    pixels[offset + 2] = 255; // B
237                    pixels[offset + 3] = 255; // A
238                } // else: stays 0,0,0,0 (transparent)
239            }
240        }
241    }
242
243    (pixels, width, height)
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn font_texture_dimensions() {
252        let (pixels, w, h) = generate_builtin_font();
253        assert_eq!(w, 128);
254        assert_eq!(h, 48);
255        assert_eq!(pixels.len(), (128 * 48 * 4) as usize);
256    }
257
258    #[test]
259    fn space_is_empty() {
260        let (pixels, w, _) = generate_builtin_font();
261        // Space is glyph 0, at (0,0). All pixels should be transparent.
262        for py in 0..8 {
263            for px in 0..8 {
264                let offset = ((py * w + px) * 4) as usize;
265                assert_eq!(pixels[offset + 3], 0, "space pixel ({px},{py}) should be transparent");
266            }
267        }
268    }
269
270    #[test]
271    fn letter_a_has_pixels() {
272        let (pixels, w, _) = generate_builtin_font();
273        // 'A' = glyph index 33 (65 - 32). col=33%16=1, row=33/16=2
274        let base_x: u32 = 1 * 8;
275        let base_y: u32 = 2 * 8;
276        let mut has_opaque = false;
277        for py in 0..8u32 {
278            for px in 0..8u32 {
279                let offset = (((base_y + py) * w + (base_x + px)) * 4) as usize;
280                if pixels[offset + 3] == 255 {
281                    has_opaque = true;
282                }
283            }
284        }
285        assert!(has_opaque, "'A' glyph should have opaque pixels");
286    }
287}