Skip to main content

kernel/screen/basic/
font.rs

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