Skip to main content

rlvgl_core/
bitmap_font.rs

1//! Minimal bitmap font for `no_std` text rendering.
2//!
3//! Provides a fixed-width ASCII font that can render text via any
4//! [`crate::renderer::Renderer`]. The built-in stub font covers ASCII 0x20-0x7E
5//! in a 6x10 pixel grid. The creator tool will later generate optimized font
6//! data to replace this stub.
7
8use crate::font::{FontLineMetrics, FontMetrics, GlyphInfo};
9use crate::renderer::Renderer;
10use crate::widget::{Color, Rect};
11
12/// A fixed-width bitmap font with 1-bit packed glyph data.
13///
14/// Each glyph occupies `glyph_width * glyph_height` bits, stored row-major
15/// with the MSB of each byte mapping to the leftmost pixel. Glyphs cover
16/// ASCII codepoints 0x20 through 0x7E (95 characters).
17pub struct BitmapFont {
18    /// Width of each glyph in pixels.
19    pub glyph_width: u8,
20    /// Height of each glyph in pixels.
21    pub glyph_height: u8,
22    /// Pixel scale factor (1 = native, 2 = double, etc.).
23    pub scale: u8,
24    /// Row-major, 1-bit-per-pixel packed glyph data for ASCII 0x20..=0x7E.
25    pub data: &'static [u8],
26}
27
28impl BitmapFont {
29    /// Scaled glyph width in display pixels.
30    pub fn scaled_width(&self) -> i32 {
31        self.glyph_width as i32 * self.scale as i32
32    }
33
34    /// Scaled glyph height in display pixels.
35    pub fn scaled_height(&self) -> i32 {
36        self.glyph_height as i32 * self.scale as i32
37    }
38
39    /// Render a single character at `(x, y)`.
40    pub fn draw_char(&self, renderer: &mut dyn Renderer, x: i32, y: i32, ch: char, color: Color) {
41        let idx = if (0x20..=0x7E).contains(&(ch as u32)) {
42            (ch as u32 - 0x20) as usize
43        } else {
44            return; // non-printable, skip
45        };
46        let bits_per_glyph = self.glyph_width as usize * self.glyph_height as usize;
47        let bit_offset = idx * bits_per_glyph;
48        let s = self.scale as i32;
49
50        for row in 0..self.glyph_height as usize {
51            for col in 0..self.glyph_width as usize {
52                let bit = bit_offset + row * self.glyph_width as usize + col;
53                let byte_idx = bit / 8;
54                let bit_idx = 7 - (bit % 8); // MSB first
55                if byte_idx < self.data.len() && (self.data[byte_idx] >> bit_idx) & 1 != 0 {
56                    renderer.fill_rect(
57                        Rect {
58                            x: x + col as i32 * s,
59                            y: y + row as i32 * s,
60                            width: s,
61                            height: s,
62                        },
63                        color,
64                    );
65                }
66            }
67        }
68    }
69
70    /// Render a string at `(x, y)` advancing horizontally (increasing x).
71    pub fn draw_str(&self, renderer: &mut dyn Renderer, x: i32, y: i32, text: &str, color: Color) {
72        let advance = self.scaled_width() + self.scale as i32;
73        let mut cx = x;
74        for ch in text.chars() {
75            self.draw_char(renderer, cx, y, ch, color);
76            cx += advance;
77        }
78    }
79
80    /// Render a string advancing along the Y axis (for rotated displays).
81    ///
82    /// Glyphs remain upright but each character is placed at increasing Y,
83    /// allowing horizontal text on a display where fb Y = physical horizontal.
84    pub fn draw_str_y(
85        &self,
86        renderer: &mut dyn Renderer,
87        x: i32,
88        y: i32,
89        text: &str,
90        color: Color,
91    ) {
92        let advance = self.scaled_width() + self.scale as i32;
93        let mut cy = y;
94        for ch in text.chars() {
95            self.draw_char(renderer, x, cy, ch, color);
96            cy += advance;
97        }
98    }
99}
100
101impl FontMetrics for BitmapFont {
102    fn glyph_metrics(&self, ch: char) -> Option<GlyphInfo> {
103        if !(0x20..=0x7e).contains(&(ch as u32)) {
104            return None;
105        }
106        let width = self.scaled_width().max(0) as u16;
107        let height = self.scaled_height().max(0) as u16;
108        let advance_px = self.scaled_width() + self.scale as i32;
109        Some(GlyphInfo {
110            advance_fp16: (advance_px.max(0) * 16).min(u16::MAX as i32) as u16,
111            bearing_x: 0,
112            bearing_y: height.min(i16::MAX as u16) as i16,
113            width,
114            height,
115        })
116    }
117
118    fn line_metrics(&self) -> FontLineMetrics {
119        let height = self.scaled_height().max(0).min(u16::MAX as i32) as u16;
120        FontLineMetrics {
121            line_height: height,
122            ascent: height.min(i16::MAX as u16) as i16,
123            descent: 0,
124        }
125    }
126
127    fn glyph_coverage_row(&self, ch: char, row: u16, x_offset: u16, coverage: &mut [u8]) -> bool {
128        if !(0x20..=0x7e).contains(&(ch as u32)) {
129            return false;
130        }
131
132        let scale = self.scale.max(1) as usize;
133        let source_row = row as usize / scale;
134        if source_row >= self.glyph_height as usize {
135            coverage.fill(0);
136            return true;
137        }
138
139        let glyph_idx = (ch as u32 - 0x20) as usize;
140        let bits_per_glyph = self.glyph_width as usize * self.glyph_height as usize;
141        let bit_offset = glyph_idx * bits_per_glyph;
142
143        for (offset, alpha) in coverage.iter_mut().enumerate() {
144            let source_col = (x_offset as usize + offset) / scale;
145            if source_col >= self.glyph_width as usize {
146                *alpha = 0;
147                continue;
148            }
149            let bit = bit_offset + source_row * self.glyph_width as usize + source_col;
150            let byte_idx = bit / 8;
151            let bit_idx = 7 - (bit % 8);
152            *alpha = if byte_idx < self.data.len() && (self.data[byte_idx] >> bit_idx) & 1 != 0 {
153                255
154            } else {
155                0
156            };
157        }
158        true
159    }
160}
161
162// ── Built-in 6×10 stub font ────────────────────────────────────────────────
163// Covers ASCII 0x20–0x7E (95 glyphs). Each glyph is 6 wide × 10 tall = 60
164// bits. Total: 95 × 60 = 5700 bits = 713 bytes (rounded up).
165//
166// This is a minimal hand-crafted font for bring-up. The creator tool will
167// replace it with properly rasterised glyphs.
168
169/// Built-in 6×10 ASCII bitmap font.
170/// Built-in 6×10 ASCII bitmap font rendered at 2× scale (12×20 display pixels).
171pub static FONT_6X10: BitmapFont = BitmapFont {
172    glyph_width: 6,
173    glyph_height: 10,
174    scale: 2,
175    data: &FONT_6X10_DATA,
176};
177
178// 95 glyphs × 60 bits = 5700 bits → 713 bytes
179// Glyph order: space ! " # $ % & ' ( ) * + , - . / 0-9 : ; < = > ? @ A-Z [ \ ] ^ _ ` a-z { | } ~
180static FONT_6X10_DATA: [u8; 713] = {
181    // We build the font data programmatically from a visual representation.
182    // Each glyph is a 6×10 grid where '#' = 1 and '.' = 0.
183    // For the stub, we pack the glyphs defined below.
184
185    // Helper: this is generated from the glyph patterns below.
186    // For bring-up we provide readable glyphs for key characters and
187    // fill the rest with simple box/dot patterns.
188    *include_bytes!("bitmap_font_6x10.bin")
189};