Skip to main content

khora_infra/renderer/custom/
pixel_font.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Pixel font data for built-in 8x8 bitmap font (ASCII 32-126).
16
17/// Get the 8x8 bitmap data for a character.
18/// Returns None if the character is not in the printable ASCII range (32-126).
19pub fn get_font_bits(c: char) -> Option<&'static [u8; 8]> {
20    let code = c as usize;
21    if !(32..=126).contains(&code) {
22        return None;
23    }
24
25    Some(match c {
26        ' ' => &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
27        '!' => &[0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x00],
28        '"' => &[0x6C, 0x6C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00],
29        '#' => &[0x24, 0x7E, 0x24, 0x24, 0x7E, 0x24, 0x00, 0x00],
30        '$' => &[0x18, 0x3C, 0x66, 0x3C, 0x18, 0x66, 0x3C, 0x18],
31        '%' => &[0x62, 0x66, 0x0C, 0x18, 0x30, 0x66, 0x46, 0x00],
32        '&' => &[0x3C, 0x66, 0x66, 0x3C, 0x5C, 0x66, 0x3B, 0x00],
33        '\'' => &[0x18, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00],
34        '(' => &[0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00],
35        ')' => &[0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00],
36        '*' => &[0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00],
37        '+' => &[0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00],
38        ',' => &[0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x0C, 0x00],
39        '-' => &[0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00],
40        '.' => &[0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00],
41        '/' => &[0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00],
42        '0' => &[0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00],
43        '1' => &[0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00],
44        '2' => &[0x3C, 0x66, 0x06, 0x0C, 0x18, 0x30, 0x7E, 0x00],
45        '3' => &[0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00],
46        '4' => &[0x06, 0x0E, 0x1E, 0x36, 0x7E, 0x06, 0x06, 0x00],
47        '5' => &[0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00],
48        '6' => &[0x1C, 0x30, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00],
49        '7' => &[0x7E, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00],
50        '8' => &[0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00],
51        '9' => &[0x3C, 0x66, 0x66, 0x3E, 0x06, 0x0C, 0x38, 0x00],
52        ':' => &[0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00],
53        ';' => &[0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x0C, 0x00],
54        '<' => &[0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x00],
55        '=' => &[0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00],
56        '>' => &[0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x00],
57        '?' => &[0x3C, 0x66, 0x06, 0x0C, 0x18, 0x00, 0x18, 0x00],
58        '@' => &[0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x66, 0x3E, 0x00],
59        'A' => &[0x18, 0x3C, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00],
60        'B' => &[0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00],
61        'C' => &[0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00],
62        'D' => &[0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00],
63        'E' => &[0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x7E, 0x00],
64        'F' => &[0x7E, 0x60, 0x60, 0x7C, 0x60, 0x60, 0x60, 0x00],
65        'G' => &[0x3E, 0x60, 0x60, 0x6E, 0x66, 0x66, 0x3E, 0x00],
66        'H' => &[0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00],
67        'I' => &[0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00],
68        'J' => &[0x1E, 0x0C, 0x0C, 0x0C, 0x6C, 0x6C, 0x38, 0x00],
69        'K' => &[0x66, 0x6c, 0x78, 0x70, 0x78, 0x6c, 0x66, 0x00],
70        'L' => &[0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00],
71        'M' => &[0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x00],
72        'N' => &[0x66, 0x76, 0x7E, 0x7E, 0x7E, 0x6E, 0x66, 0x00],
73        'O' => &[0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00],
74        'P' => &[0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00],
75        'Q' => &[0x3C, 0x66, 0x66, 0x66, 0x6E, 0x76, 0x3E, 0x06],
76        'R' => &[0x7C, 0x66, 0x66, 0x7C, 0x6c, 0x66, 0x66, 0x00],
77        'S' => &[0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00],
78        'T' => &[0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00],
79        'U' => &[0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00],
80        'V' => &[0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00],
81        'W' => &[0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00],
82        'X' => &[0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00],
83        'Y' => &[0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00],
84        'Z' => &[0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E, 0x00],
85        '[' => &[0x1C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1C, 0x00],
86        '\\' => &[0x40, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00],
87        ']' => &[0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x38, 0x00],
88        '^' => &[0x10, 0x38, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00],
89        '_' => &[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00],
90        '`' => &[0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
91        'a' => &[0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3E, 0x00],
92        'b' => &[0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x00],
93        'c' => &[0x00, 0x00, 0x3C, 0x66, 0x60, 0x66, 0x3C, 0x00],
94        'd' => &[0x06, 0x06, 0x3E, 0x66, 0x66, 0x66, 0x3E, 0x00],
95        'e' => &[0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00],
96        'f' => &[0x0C, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x18, 0x00],
97        'g' => &[0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x3C],
98        'h' => &[0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00],
99        'i' => &[0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00],
100        'j' => &[0x0C, 0x00, 0x1C, 0x0C, 0x0C, 0x6C, 0x6C, 0x38],
101        'k' => &[0x60, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0x00],
102        'l' => &[0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00],
103        'm' => &[0x00, 0x00, 0x7E, 0x7E, 0x7E, 0x66, 0x66, 0x00],
104        'n' => &[0x00, 0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00],
105        'o' => &[0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00],
106        'p' => &[0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60],
107        'q' => &[0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x06],
108        'r' => &[0x00, 0x00, 0x7C, 0x66, 0x60, 0x60, 0x60, 0x00],
109        's' => &[0x00, 0x00, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x00],
110        't' => &[0x18, 0x18, 0x7E, 0x18, 0x18, 0x18, 0x0C, 0x00],
111        'u' => &[0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x00],
112        'v' => &[0x00, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00],
113        'w' => &[0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00],
114        'x' => &[0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00],
115        'y' => &[0x00, 0x00, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x3C],
116        'z' => &[0x00, 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x7E, 0x00],
117        '{' => &[0x0E, 0x18, 0x18, 0x30, 0x18, 0x18, 0x0E, 0x00],
118        '|' => &[0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00],
119        '}' => &[0x70, 0x18, 0x18, 0x0C, 0x18, 0x18, 0x70, 0x00],
120        '~' => &[0x00, 0x00, 0x76, 0x4E, 0x00, 0x00, 0x00, 0x00],
121        _ => &[0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF, 0x00],
122    })
123}
124
125/// Rasterize a glyph into pixel data.
126/// Returns the width, height, and pixel data (RGBA).
127pub fn rasterize_glyph(c: char) -> Option<(u32, u32, Vec<u8>)> {
128    let bits = get_font_bits(c)?;
129
130    let width = 8u32;
131    let height = 8u32;
132    let mut pixels = vec![0u8; 64];
133
134    for y in 0..8 {
135        let row = bits[y];
136        for x in 0..8 {
137            if (row & (0x80 >> x)) != 0 {
138                pixels[y * 8 + x] = 255;
139            }
140        }
141    }
142
143    Some((width, height, pixels))
144}