endbasic_std/gfx/lcd/fonts/mod.rs
1// EndBASIC
2// Copyright 2025 Julio Merino
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License. You may obtain a copy
6// of the License at:
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16//! Support for bitmap fonts directly rendered onto an LCD.
17
18use crate::gfx::lcd::LcdSize;
19use std::collections::HashMap;
20
21mod font_5x8;
22pub(crate) use font_5x8::FONT_5X8;
23
24mod font_16x16;
25pub(crate) use font_16x16::FONT_16X16;
26
27/// Representation of a font.
28pub struct Font {
29 /// The name of the font.
30 pub name: &'static str,
31
32 /// The size of a single glyph, in pixels.
33 pub glyph_size: LcdSize,
34
35 /// The number of bytes in every glyph row.
36 pub stride: usize,
37
38 /// The bitmap data for the font.
39 pub data: &'static [u8],
40}
41
42impl Font {
43 /// Returns the raw font data for `ch`.
44 ///
45 /// Each entry in the array corresponds to a row of pixels and is a bitmask indicating which
46 /// pixels to turn on.
47 pub(crate) fn glyph(&self, mut ch: char) -> &'static [u8] {
48 if !(' '..='~').contains(&ch) {
49 // TODO(jmmv): Would be nicer to draw an empty box, much like how unknown Unicode
50 // characters are typically displayed.
51 ch = '?';
52 }
53 let height = self.glyph_size.height * self.stride;
54 let offset = ((ch as usize) - (' ' as usize)) * height;
55 debug_assert!(offset < (self.data.len() + height));
56 &self.data[offset..offset + height]
57 }
58}
59
60/// Registry of all available fonts.
61pub type Fonts = HashMap<&'static str, &'static Font>;
62
63/// Obtains a mapping of all available fonts.
64pub fn all_fonts() -> Fonts {
65 let mut fonts = Fonts::default();
66 fonts.insert(FONT_5X8.name, &FONT_5X8);
67 fonts.insert(FONT_16X16.name, &FONT_16X16);
68 fonts
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn test_font_glyph_printable() {
77 let font = &FONT_5X8;
78
79 let offset = (usize::from(b'a') - usize::from(b' ')) * 8;
80 let expected = &font.data[offset..offset + 8];
81
82 let data = font.glyph('a');
83 assert_eq!(expected, data);
84 }
85
86 #[test]
87 fn test_font_glyph_non_printable() {
88 let font = &FONT_5X8;
89
90 let offset = (usize::from(b'?') - usize::from(b' ')) * 8;
91 let expected = &font.data[offset..offset + 8];
92
93 let data = font.glyph(char::from(30));
94 assert_eq!(expected, data);
95 }
96}