Skip to main content

kernel/drivers/gpu/basic_fb/
font.rs

1// SPDX-License-Identifier: GPL-3.0-only
2//! PSF2 bitmap font loading and character rendering.
3//!
4//! Authors: MarioS271
5
6use super::framebuffer::BasicFramebuffer;
7
8/// A loaded and parsed PSF2 font, ready for rendering into a [`BasicFramebuffer`]
9pub struct Psf2Font {
10    font: &'static [u8],
11    header: Psf2Header,
12}
13impl Psf2Font {
14    const DEFAULT_BACKGROUND_COLOR: u32 = 0x00000000;
15    const DEFAULT_FOREGROUND_COLOR: u32 = 0x00FFFFFF;
16
17    /// Load and parse the built-in PSF2 font embedded in the kernel binary.
18    ///
19    /// # Panics
20    /// Panics if the embedded file's magic number does not match [`MAGIC_NUMBER`].
21    pub fn init() -> Self {
22        let font: &[u8] = include_bytes!("../../../../resources/ter-powerline-v16n.psf");
23        Self {
24            font,
25            header: Psf2Header::parse(font),
26        }
27    }
28
29    /// Return the raw glyph bytes for character `c`, or `None` if `c` has no glyph.
30    fn parse_char(&self, c: char) -> Option<&'static [u8]> {
31        let char_index = c as usize;
32
33        if char_index >= self.header.glyph_count as usize {
34            return None;
35        }
36
37        let start = self.header.header_size as usize + char_index * self.header.bytes_per_glyph as usize;
38
39        Some(&self.font[start..(start + self.header.bytes_per_glyph as usize)])
40    }
41
42    /// Return the height of one glyph in pixels.
43    pub fn glyph_height(&self) -> usize {
44        self.header.height as usize
45    }
46
47    /// Render one character into the framebuffer at pixel position `(x, y)`.
48    ///
49    /// Returns the x position immediately after the glyph, or the original `x` if the
50    /// character has no glyph or falls outside the framebuffer bounds.
51    pub fn draw_char(&self, fb: &BasicFramebuffer, c: char, x: usize, y: usize, font_color: Option<u32>) -> usize {
52        let Some(glyph) = self.parse_char(c) else { return x; };
53
54        if x + self.header.width as usize > fb.width
55            || y + self.header.height as usize > fb.height
56        {
57            return x;
58        }
59
60        let row_stride = (self.header.width + 7) / 8;
61
62        for row in 0..self.header.height as usize {
63            for col in 0..self.header.width as usize {
64                let byte = glyph[row * row_stride as usize + col / 8];
65                let bit = (byte >> (7 - (col % 8))) & 1;
66
67                let color = if bit == 1 {
68                    font_color.unwrap_or(Self::DEFAULT_FOREGROUND_COLOR)
69                } else {
70                    Self::DEFAULT_BACKGROUND_COLOR
71                };
72                let pixel_pos = (y + row) * fb.pixels_per_row + (x + col);
73
74                // Safety: the if statement before the double loop would've returned if we
75                // were in invalid memory
76                unsafe {
77                    fb.fb_pointer.add(pixel_pos).write_volatile(color);
78                }
79            }
80        }
81
82        x + self.header.width as usize
83    }
84
85    /// Render a string into the framebuffer, advancing `*x`/`*y` after each character.
86    /// `'\n'` wraps to the next line.
87    pub fn draw_string(&self, fb: &BasicFramebuffer, string: &str, x: &mut usize, y: &mut usize, font_color: Option<u32>) {
88        for c in string.chars() {
89            if c == '\n' {
90                *x = 0;
91                *y += self.header.height as usize;
92            } else {
93                *x = self.draw_char(fb, c, *x, *y, font_color);
94            }
95        }
96    }
97}
98
99/// Parsed PSF2 file header
100struct Psf2Header {
101    pub header_size: u32,
102    pub glyph_count: u32,
103    pub bytes_per_glyph: u32,
104    pub width: u32,
105    pub height: u32
106}
107impl Psf2Header {
108    const MAGIC_NUMBER: u32 = 0x864ab572;
109
110    /// Extract header fields from a raw PSF2 byte slice.
111    ///
112    /// # Panics
113    /// Panics if the magic number at offset 0 does not match [`MAGIC_NUMBER`].
114    fn parse(psf2_font: &[u8]) -> Self {
115        use crate::lib::panic::kernel_panic;
116        use crate::lib::panic_codes::PanicCode;
117
118        let read_u32 = |offset: usize| -> u32 {
119            u32::from_le_bytes(psf2_font[offset..(offset + 4)].try_into().unwrap())
120        };
121
122        if psf2_font.len() < 32 {
123            kernel_panic(
124                PanicCode::MalformedPsf2Font,
125                "PSF2 font is too short"
126            );
127        }
128
129        if read_u32(0) != Self::MAGIC_NUMBER {
130            kernel_panic(
131                PanicCode::MalformedPsf2Font,
132                "Invalid PSF2 Magic Number",
133            );
134        }
135
136        let header_size = read_u32(8);
137        let glyph_count = read_u32(16);
138        let bytes_per_glyph = read_u32(20);
139        let width = read_u32(28);
140        let height = read_u32(24);
141
142        let end_of_glyphs = (glyph_count as usize)
143            .checked_mul(bytes_per_glyph as usize)
144            .and_then(|glyph_bytes| glyph_bytes.checked_add(header_size as usize));
145
146        match end_of_glyphs {
147            Some(end) if end <= psf2_font.len() => {}
148            _ => {
149                kernel_panic(
150                    PanicCode::MalformedPsf2Font,
151                    "PSF2 header declares more glyph data than the font blob contains",
152                );
153            }
154        }
155
156        Self {
157            header_size,
158            glyph_count,
159            bytes_per_glyph,
160            width,
161            height
162        }
163    }
164}