Skip to main content

strat9_kernel/arch/x86_64/vga/
font.rs

1//! PSF (PC Screen Font) font parsing.
2//!
3//! Supports PSF1 and PSF2 font formats including unicode map decoding.
4
5use alloc::vec::Vec;
6
7pub(crate) const FONT_PSF: &[u8] = include_bytes!("../fonts/zap-ext-light20.psf");
8
9pub(crate) struct FontInfo {
10    pub(crate) glyph_count: usize,
11    pub(crate) bytes_per_glyph: usize,
12    pub(crate) glyph_w: usize,
13    pub(crate) glyph_h: usize,
14    pub(crate) data_offset: usize,
15    pub(crate) unicode_table_offset: Option<usize>,
16}
17
18pub(crate) fn parse_psf(font: &[u8]) -> Option<FontInfo> {
19    // PSF1
20    if font.len() >= 4 && font[0] == 0x36 && font[1] == 0x04 {
21        let mode = font[2];
22        let glyph_count = if (mode & 0x01) != 0 { 512 } else { 256 };
23        let glyph_h = font[3] as usize;
24        let bytes_per_glyph = glyph_h;
25        return Some(FontInfo {
26            glyph_count,
27            bytes_per_glyph,
28            glyph_w: 8,
29            glyph_h,
30            data_offset: 4,
31            unicode_table_offset: None,
32        });
33    }
34
35    // PSF2
36    if font.len() >= 32 && font[0] == 0x72 && font[1] == 0xB5 && font[2] == 0x4A && font[3] == 0x86
37    {
38        let rd_u32 = |off: usize| -> u32 {
39            u32::from_le_bytes([font[off], font[off + 1], font[off + 2], font[off + 3]])
40        };
41        let headersize = rd_u32(8) as usize;
42        let flags = rd_u32(12);
43        let glyph_count = rd_u32(16) as usize;
44        let bytes_per_glyph = rd_u32(20) as usize;
45        let glyph_h = rd_u32(24) as usize;
46        let glyph_w = rd_u32(28) as usize;
47        let glyph_bytes = glyph_count.saturating_mul(bytes_per_glyph);
48        let unicode_table_offset = if (flags & 1) != 0 {
49            Some(headersize.saturating_add(glyph_bytes))
50        } else {
51            None
52        };
53        return Some(FontInfo {
54            glyph_count,
55            bytes_per_glyph,
56            glyph_w,
57            glyph_h,
58            data_offset: headersize,
59            unicode_table_offset,
60        });
61    }
62
63    None
64}
65
66/// Performs the decode utf8 at operation.
67fn decode_utf8_at(bytes: &[u8], pos: usize) -> Option<(u32, usize)> {
68    let b0 = *bytes.get(pos)?;
69    if b0 < 0x80 {
70        return Some((b0 as u32, 1));
71    }
72    if (b0 & 0xE0) == 0xC0 {
73        let b1 = *bytes.get(pos + 1)?;
74        if (b1 & 0xC0) != 0x80 {
75            return None;
76        }
77        let cp = (((b0 & 0x1F) as u32) << 6) | ((b1 & 0x3F) as u32);
78        return Some((cp, 2));
79    }
80    if (b0 & 0xF0) == 0xE0 {
81        let b1 = *bytes.get(pos + 1)?;
82        let b2 = *bytes.get(pos + 2)?;
83        if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 {
84            return None;
85        }
86        let cp = (((b0 & 0x0F) as u32) << 12) | (((b1 & 0x3F) as u32) << 6) | ((b2 & 0x3F) as u32);
87        return Some((cp, 3));
88    }
89    if (b0 & 0xF8) == 0xF0 {
90        let b1 = *bytes.get(pos + 1)?;
91        let b2 = *bytes.get(pos + 2)?;
92        let b3 = *bytes.get(pos + 3)?;
93        if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80 {
94            return None;
95        }
96        let cp = (((b0 & 0x07) as u32) << 18)
97            | (((b1 & 0x3F) as u32) << 12)
98            | (((b2 & 0x3F) as u32) << 6)
99            | ((b3 & 0x3F) as u32);
100        return Some((cp, 4));
101    }
102    None
103}
104
105/// Parses psf2 unicode map.
106pub(crate) fn parse_psf2_unicode_map(font: &[u8], info: &FontInfo) -> Vec<(u32, usize)> {
107    let Some(mut i) = info.unicode_table_offset else {
108        return Vec::new();
109    };
110    if i >= font.len() {
111        return Vec::new();
112    }
113
114    let mut map = Vec::new();
115    for glyph in 0..info.glyph_count {
116        while i < font.len() {
117            let b = font[i];
118            if b == 0xFF {
119                i += 1;
120                break;
121            }
122            if b == 0xFE {
123                // PSF2 sequence marker; skip marker and continue parsing bytes until glyph separator.
124                i += 1;
125                continue;
126            }
127            if let Some((cp, adv)) = decode_utf8_at(font, i) {
128                if !map.iter().any(|(u, _)| *u == cp) {
129                    map.push((cp, glyph));
130                }
131                i += adv;
132            } else {
133                i += 1;
134            }
135        }
136    }
137
138    map
139}