use std::sync::OnceLock;
static VGA16: &[u8; 4096] = include_bytes!("../fonts/vga_8x16.bin");
static VGA14: &[u8; 3584] = include_bytes!("../fonts/vga_8x14.bin");
static VGA8: &[u8; 2048] = include_bytes!("../fonts/vga_8x8.bin");
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Font {
height: u8,
data: Vec<u8>,
}
impl Font {
pub fn from_raw(height: u8, data: &[u8]) -> Option<Self> {
(height > 0 && height <= 32 && data.len() == 256 * height as usize).then(|| Self {
height,
data: data.to_vec(),
})
}
pub fn vga() -> &'static Font {
static F: OnceLock<Font> = OnceLock::new();
F.get_or_init(|| Font {
height: 16,
data: VGA16.to_vec(),
})
}
pub fn ega() -> &'static Font {
static F: OnceLock<Font> = OnceLock::new();
F.get_or_init(|| Font {
height: 14,
data: VGA14.to_vec(),
})
}
pub fn cga() -> &'static Font {
static F: OnceLock<Font> = OnceLock::new();
F.get_or_init(|| Font {
height: 8,
data: VGA8.to_vec(),
})
}
pub fn sga() -> &'static Font {
static F: OnceLock<Font> = OnceLock::new();
F.get_or_init(|| Font {
height: 16,
data: sga16().to_vec(),
})
}
pub fn sga_ega() -> &'static Font {
static F: OnceLock<Font> = OnceLock::new();
F.get_or_init(|| with_sga_letters(Font::ega(), derive14(&sga16())))
}
pub fn sga_cga() -> &'static Font {
static F: OnceLock<Font> = OnceLock::new();
F.get_or_init(|| with_sga_letters(Font::cga(), derive8(&sga16())))
}
pub fn height(&self) -> u8 {
self.height
}
pub fn glyph(&self, ch: u8) -> &[u8] {
let h = self.height as usize;
&self.data[ch as usize * h..(ch as usize + 1) * h]
}
pub fn raw(&self) -> &[u8] {
&self.data
}
pub fn text_width(&self, s: &str) -> u32 {
s.chars().count() as u32 * 8
}
}
fn with_sga_letters(base: &Font, sga: Font) -> Font {
let mut f = base.clone();
let h = f.height as usize;
for ch in (b'A'..=b'Z').chain(b'a'..=b'z') {
let r = ch as usize * h..(ch as usize + 1) * h;
f.data[r.clone()].copy_from_slice(&sga.data[r]);
}
f
}
fn derive14(src: &[u8; 4096]) -> Font {
let mut data = Vec::with_capacity(256 * 14);
for g in src.as_chunks::<16>().0 {
let (a, b) = if g[0] == 0 && g[15] == 0 {
(1, 15)
} else {
(0, 14)
};
data.extend_from_slice(&g[a..b]);
}
Font { height: 14, data }
}
fn derive8(src: &[u8; 4096]) -> Font {
let mut data = Vec::with_capacity(256 * 8);
for (code, g) in src.as_chunks::<16>().0.iter().enumerate() {
data.extend_from_slice(&squash_glyph(code as u8, g));
}
Font { height: 8, data }
}
const SGA: [[&str; 10]; 26] = [
[
"....###.", "...##.##", "...##...", "...##...", "...##...", "...##...", "...##...",
"...##...", "##.##...", ".###....",
],
[
"..##....", "..##....", "..##....", "..##....", "..###...", "...###..", "....##..",
"....##..", "..####..", "..##....",
],
[
".##.....", ".##.....", ".##.....", ".###....", "..####..", "....###.", ".....##.",
".....##.", ".....##.", ".....##.",
],
[
"########", "........", "#####...", "##......", "#.#.....", "#..#....", "....#...",
".....#..", "......#.", ".......#",
],
[
"##......", "##......", "##......", "##......", "##...##.", "##...##.", "##......",
"##......", "##......", "#######.",
],
[
"........", "........", "........", "########", "........", "........", "##.##.##",
"........", "........", "........",
],
[
"......##", "......##", "......##", "......##", "########", "......##", "......##",
"......##", "......##", "......##",
],
[
"........", "########", "........", "########", "...##...", "...##...", "...##...",
"...##...", "...##...", "...##...",
],
[
"...##...", "...##...", "...##...", "...##...", "........", "........", "...##...",
"...##...", "...##...", "...##...",
],
[
"........", "...##...", "...##...", "........", "...##...", "...##...", "........",
"...##...", "...##...", "........",
],
[
"...##...", "...##...", "...##...", "...##...", "##.##.##", "##.##.##", "...##...",
"...##...", "...##...", "...##...",
],
[
"..##....", "..##....", "..##.##.", "..##.##.", "..##....", "..##....", "..##.##.",
"..##.##.", "..##....", "..##....",
],
[
"......##", "......##", "......##", "......##", ".##...##", ".##...##", "......##",
"......##", "......##", ".#######",
],
[
".##..##.", ".##..##.", ".##..##.", ".##..##.", ".##..##.", ".....##.", ".....##.",
"....##..", "...##...", "..##....",
],
[
"########", "....##..", "....##..", "....##..", "....##..", "....##..", "....##..",
"##..##..", "##..##..", ".####...",
],
[
".##..##.", ".##..##.", ".##.....", ".##..##.", ".##..##.", ".##..##.", ".##..##.",
".....##.", ".##..##.", ".##..##.",
],
[
"#####...", "######..", "....##..", ".....##.", ".....##.", ".....##.", ".....##.",
"....##..", "######..", "#####...",
],
[
"........", ".##..##.", ".##..##.", "........", "........", ".##..##.", ".##..##.",
"........", "........", "........",
],
[
".##.....", ".##.....", ".###....", "..#####.", "....###.", ".....##.", ".....##.",
".....##.", ".....##.", ".....##.",
],
[
"########", "......##", "......##", "......##", "......##", "......##", "......##",
"......##", "##....##", "##......",
],
[
"........", "........", "###..###", "###..###", "........", "........", "########",
"########", "........", "........",
],
[
"...##...", "...##...", "...##...", "...##...", "...##...", "...##...", "########",
"........", "########", "........",
],
[
"........", "...##...", "...##...", "........", "........", "##....##", "##....##",
"........", "........", "........",
],
[
".......#", "......##", "......#.", ".....##.", "##...#..", "##..##..", "....#...",
"...##...", "...#....", "..##....",
],
[
".##..##.", ".##..##.", ".##..##.", ".##..##.", ".##..##.", ".##..##.", ".##..##.",
".##..##.", ".##..##.", ".##..##.",
],
[
"########", "##....##", "##....##", "##....##", "##....##", "##....##", "##....##",
"##....##", "##....##", "##....##",
],
];
fn sga16() -> [u8; 4096] {
let mut data = *VGA16;
for (i, rows) in SGA.iter().enumerate() {
let mut g = [0u8; 16];
for (r, row) in rows.iter().enumerate() {
g[r + 2] =
row.bytes().enumerate().fold(
0,
|acc, (x, c)| if c == b'#' { acc | (0x80 >> x) } else { acc },
);
}
for base in *b"Aa" {
let at = (base as usize + i) * 16;
data[at..at + 16].copy_from_slice(&g);
}
}
data
}
fn squash_glyph(code: u8, g: &[u8]) -> [u8; 8] {
let mut out = [0u8; 8];
let full_height = g[0] != 0 || g[15] != 0;
if full_height {
for (r, o) in out.iter_mut().enumerate() {
*o = g[r * 2] | g[r * 2 + 1] & g[(r * 2 + 2).min(15)];
if r == 7 {
*o = g[14] | g[15];
}
}
if (0xB3..=0xDA).contains(&code) {
for i in 0..16 {
if g[i] == 0xFF || g[i] == 0xF8 || g[i] == 0x1F {
out[(i / 2).min(7)] |= g[i];
}
}
}
return out;
}
const MAP: [(usize, usize); 7] = [(2, 3), (4, 4), (5, 6), (7, 7), (8, 9), (10, 10), (11, 12)];
for (r, (a, b)) in MAP.iter().enumerate() {
out[r] = g[*a] | g[*b];
}
out[7] = g[13] | g[14];
out
}
pub fn cp437_from_char(c: char) -> u8 {
if (c as u32) < 0x80 && c as u32 >= 0x20 {
return c as u8;
}
CP437_HIGH
.iter()
.position(|&h| h == c)
.map(|i| (i + 0x80) as u8)
.or_else(|| CP437_LOW.iter().position(|&h| h == c).map(|i| i as u8))
.unwrap_or(b'?')
}
pub fn cp437(s: &str) -> Vec<u8> {
s.chars().map(cp437_from_char).collect()
}
const CP437_LOW: [char; 32] = [
'\0', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '◙', '♂', '♀', '♪', '♫', '☼', '►', '◄', '↕',
'‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼',
];
const CP437_HIGH: [char; 128] = [
'Ç', 'ü', 'é', 'â', 'ä', 'à', 'å', 'ç', 'ê', 'ë', 'è', 'ï', 'î', 'ì', 'Ä', 'Å', 'É', 'æ', 'Æ',
'ô', 'ö', 'ò', 'û', 'ù', 'ÿ', 'Ö', 'Ü', '¢', '£', '¥', '₧', 'ƒ', 'á', 'í', 'ó', 'ú', 'ñ', 'Ñ',
'ª', 'º', '¿', '⌐', '¬', '½', '¼', '¡', '«', '»', '░', '▒', '▓', '│', '┤', '╡', '╢', '╖', '╕',
'╣', '║', '╗', '╝', '╜', '╛', '┐', '└', '┴', '┬', '├', '─', '┼', '╞', '╟', '╚', '╔', '╩', '╦',
'╠', '═', '╬', '╧', '╨', '╤', '╥', '╙', '╘', '╒', '╓', '╫', '╪', '┘', '┌', '█', '▄', '▌', '▐',
'▀', 'α', 'ß', 'Γ', 'π', 'Σ', 'σ', 'µ', 'τ', 'Φ', 'Θ', 'Ω', 'δ', '∞', 'φ', 'ε', '∩', '≡', '±',
'≥', '≤', '⌠', '⌡', '÷', '≈', '°', '∙', '·', '√', 'ⁿ', '²', '■', '\u{a0}',
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sga_fonts_keep_the_rom_glyphs_outside_the_letters() {
for (rom, sga) in [
(Font::ega(), Font::sga_ega()),
(Font::cga(), Font::sga_cga()),
] {
assert_eq!(rom.glyph(b'0'), sga.glyph(b'0'));
assert_eq!(rom.glyph(0xC9), sga.glyph(0xC9));
assert_ne!(rom.glyph(b'A'), sga.glyph(b'A'));
}
}
#[test]
fn fonts_have_right_sizes() {
assert_eq!(Font::vga().raw().len(), 4096);
assert_eq!(Font::ega().raw().len(), 256 * 14);
assert_eq!(Font::cga().raw().len(), 256 * 8);
assert!(Font::vga().glyph(b'A').iter().any(|&r| r != 0));
assert!(Font::cga().glyph(0xDB).iter().all(|&r| r == 0xFF));
assert_eq!(Font::cga().glyph(0xDE)[0], 0x0F);
}
#[test]
fn sga_replaces_letters_only() {
let s = Font::sga();
assert_ne!(s.glyph(b'A'), Font::vga().glyph(b'A'));
assert_eq!(s.glyph(b'A'), s.glyph(b'a'));
assert_eq!(s.glyph(b'7'), Font::vga().glyph(b'7'));
assert_eq!(s.glyph(0xC9), Font::vga().glyph(0xC9));
assert_eq!(Font::sga_cga().height(), 8);
assert_eq!(Font::sga_ega().height(), 14);
}
#[test]
fn cp437_round_trip() {
assert_eq!(cp437("A╔═╗☺"), vec![b'A', 0xC9, 0xCD, 0xBB, 0x01]);
}
}