embedded_bitmap_fonts/
lib.rs

1//! # embedded-bitmap-fonts
2//!
3//! A comprehensive collection of bitmap fonts for the [`embedded-graphics`] crate.
4//!
5//! This crate provides high-quality bitmap fonts from the [Tecate bitmap-fonts collection](https://github.com/Tecate/bitmap-fonts)
6//! for use with embedded-graphics. Key features include:
7//!
8//! - **Pixel-doubling**: Scale fonts by 2x, 3x, or more with no additional flash cost
9//! - **Multiple font families**: Tamzen, Cherry, Terminus, Spleen, and many more
10//! - **Feature flags**: Include only the fonts you need to minimize binary size
11//! - **No-std compatible**: Works on embedded systems without an allocator
12//!
13//! ## Usage
14//!
15//! ```rust,no_run
16//! use embedded_bitmap_fonts::{BitmapFont, TextStyle};
17//! # #[cfg(feature = "tamzen")]
18//! use embedded_bitmap_fonts::tamzen::FONT_8x15;
19//! use embedded_graphics::{pixelcolor::BinaryColor, prelude::*, text::Text};
20//!
21//! # struct Display;
22//! # impl OriginDimensions for Display { fn size(&self) -> Size { Size::zero() } }
23//! # impl DrawTarget for Display {
24//! #     type Color = BinaryColor;
25//! #     type Error = core::convert::Infallible;
26//! #     fn draw_iter<I>(&mut self, _: I) -> Result<(), Self::Error>
27//! #     where I: IntoIterator<Item = Pixel<BinaryColor>> { Ok(()) }
28//! # }
29//! # fn main() -> Result<(), core::convert::Infallible> {
30//! # let mut display = Display;
31//! # #[cfg(feature = "tamzen")]
32//! # {
33//! // Draw text with a bitmap font
34//! let text = Text::new(
35//!     "Hello World!",
36//!     Point::zero(),
37//!     TextStyle::new(&FONT_8x15, BinaryColor::On)
38//! );
39//! text.draw(&mut display)?;
40//!
41//! // Use pixel-doubled font for larger text (same flash cost!)
42//! let doubled = FONT_8x15.pixel_double();
43//! let large_text = Text::new(
44//!     "Big Text!",
45//!     Point::new(0, 20),
46//!     TextStyle::new(&doubled, BinaryColor::On)
47//! );
48//! large_text.draw(&mut display)?;
49//! # }
50//! # Ok(())
51//! # }
52//! ```
53//!
54//! ## Available Font Families
55//!
56//! Enable fonts using feature flags in your `Cargo.toml`:
57//!
58//! ```toml
59//! [dependencies]
60//! embedded-bitmap-fonts = { version = "0.1", features = ["tamzen", "cherry"] }
61//! ```
62//!
63//! Or enable all fonts:
64//!
65//! ```toml
66//! [dependencies]
67//! embedded-bitmap-fonts = { version = "0.1", features = ["all-fonts"] }
68//! ```
69//!
70//! [`embedded-graphics`]: embedded_graphics
71
72#![cfg_attr(not(test), no_std)]
73#![warn(rust_2018_idioms)]
74
75use core::num::NonZeroU8;
76use embedded_graphics::{
77    draw_target::DrawTarget,
78    geometry::{Dimensions, OriginDimensions, Point, Size},
79    image::{ImageDrawable, ImageRaw},
80    mono_font::mapping::GlyphMapping,
81    pixelcolor::BinaryColor,
82    primitives::Rectangle,
83    text::{
84        renderer::{TextMetrics, TextRenderer},
85        Baseline,
86    },
87    Pixel,
88};
89
90// Font modules - conditionally compiled based on features
91#[cfg(feature = "artwiz")]
92pub mod artwiz;
93
94#[cfg(feature = "bitocra")]
95pub mod bitocra;
96
97#[cfg(feature = "cherry")]
98pub mod cherry;
99
100#[cfg(feature = "creep")]
101pub mod creep;
102
103#[cfg(feature = "ctrld")]
104pub mod ctrld;
105
106#[cfg(feature = "dina")]
107pub mod dina;
108
109#[cfg(feature = "dylex")]
110pub mod dylex;
111
112#[cfg(feature = "envypn")]
113pub mod envypn;
114
115#[cfg(feature = "gohufont")]
116pub mod gohufont;
117
118#[cfg(feature = "gomme")]
119pub mod gomme;
120
121#[cfg(feature = "haxor")]
122pub mod haxor;
123
124#[cfg(feature = "jmk")]
125pub mod jmk;
126
127#[cfg(feature = "kakwa")]
128pub mod kakwa;
129
130#[cfg(feature = "knxt")]
131pub mod knxt;
132
133#[cfg(feature = "lokaltog")]
134pub mod lokaltog;
135
136#[cfg(feature = "mplus")]
137pub mod mplus;
138
139#[cfg(feature = "orp")]
140pub mod orp;
141
142#[cfg(feature = "peep")]
143pub mod peep;
144
145#[cfg(feature = "phallus")]
146pub mod phallus;
147
148#[cfg(feature = "progsole")]
149pub mod progsole;
150
151#[cfg(feature = "scientifica")]
152pub mod scientifica;
153
154#[cfg(feature = "spleen")]
155pub mod spleen;
156
157#[cfg(feature = "tamzen")]
158pub mod tamzen;
159
160#[cfg(feature = "terminus")]
161pub mod terminus;
162
163#[cfg(feature = "tewi")]
164pub mod tewi;
165
166#[cfg(feature = "trisk")]
167pub mod trisk;
168
169#[cfg(feature = "xbmicons")]
170pub mod xbmicons;
171
172/// Constant 1 as NonZeroU8 for initialization
173const ONE: NonZeroU8 = match NonZeroU8::new(1) {
174    Some(one) => one,
175    None => unreachable!(),
176};
177
178/// Stores the font bitmap and metadata for rendering.
179///
180/// Each `BitmapFont` contains the raw bitmap data for all glyphs arranged in a
181/// sprite sheet, along with information about glyph dimensions and character mapping.
182///
183/// # Pixel Doubling
184///
185/// The key feature of this crate is pixel-doubling support. You can call
186/// [`pixel_double()`](BitmapFont::pixel_double) to get a font that renders at 2x size
187/// without any additional memory cost. This works by drawing each pixel as a 2x2 block.
188///
189/// ```rust
190/// # #[cfg(feature = "tamzen")]
191/// # {
192/// use embedded_bitmap_fonts::tamzen::FONT_8x15;
193///
194/// // Original 8x15 font
195/// let small = &FONT_8x15;
196/// assert_eq!(small.width(), 8);
197/// assert_eq!(small.height(), 15);
198///
199/// // Pixel-doubled 16x30 font (same bitmap data!)
200/// let large = FONT_8x15.pixel_double();
201/// assert_eq!(large.width(), 16);
202/// assert_eq!(large.height(), 30);
203/// # }
204/// ```
205#[derive(Clone, Copy)]
206pub struct BitmapFont<'a> {
207    /// The raw bitmap data for the font sprite sheet.
208    pub bitmap: ImageRaw<'a, BinaryColor>,
209
210    /// Maps characters to glyph indices in the sprite sheet.
211    pub glyph_mapping: &'a dyn GlyphMapping,
212
213    /// The size of each glyph in the raw bitmap (before pixel multiplication).
214    pub size: Size,
215
216    /// Pixel multiplier for scaling. 1 = normal, 2 = double, etc.
217    pub pixels: NonZeroU8,
218}
219
220impl<'a> BitmapFont<'a> {
221    /// Creates a new BitmapFont from raw bitmap data.
222    ///
223    /// This is typically called by the generated font modules, not directly by users.
224    #[inline]
225    pub const fn new(
226        bitmap: ImageRaw<'a, BinaryColor>,
227        glyph_mapping: &'a dyn GlyphMapping,
228        width: u32,
229        height: u32,
230    ) -> Self {
231        Self {
232            bitmap,
233            glyph_mapping,
234            size: Size::new(width, height),
235            pixels: ONE,
236        }
237    }
238
239    /// Returns the width of each character in pixels (after pixel multiplication).
240    #[inline]
241    pub const fn width(&self) -> u32 {
242        self.size.width * self.pixels.get() as u32
243    }
244
245    /// Returns the height of each character in pixels (after pixel multiplication).
246    #[inline]
247    pub const fn height(&self) -> u32 {
248        self.size.height * self.pixels.get() as u32
249    }
250
251    /// Returns the base (unmultiplied) width of each character.
252    #[inline]
253    pub const fn base_width(&self) -> u32 {
254        self.size.width
255    }
256
257    /// Returns the base (unmultiplied) height of each character.
258    #[inline]
259    pub const fn base_height(&self) -> u32 {
260        self.size.height
261    }
262
263    /// Returns the current pixel multiplier.
264    #[inline]
265    pub const fn pixel_multiplier(&self) -> u8 {
266        self.pixels.get()
267    }
268
269    /// Draw a single glyph at the specified position.
270    pub fn draw_glyph<D>(
271        &self,
272        idx: u32,
273        target: &mut D,
274        color: BinaryColor,
275        pos: Point,
276    ) -> Result<(), D::Error>
277    where
278        D: DrawTarget<Color = BinaryColor>,
279    {
280        let bitmap_size = self.bitmap.size();
281        let chars_per_row = bitmap_size.width / self.size.width;
282        let row = idx / chars_per_row;
283
284        // Calculate position in the sprite sheet
285        let char_x = (idx - (row * chars_per_row)) * self.size.width;
286        let char_y = row * self.size.height;
287        let area = Rectangle::new(Point::new(char_x as _, char_y as _), self.size);
288
289        // Draw with pixel multiplication
290        let mut pixel_target = PixelMultiplyDrawTarget {
291            target,
292            color,
293            offset: pos,
294            pixels: self.pixels,
295        };
296        self.bitmap.draw_sub_image(&mut pixel_target, &area)?;
297
298        Ok(())
299    }
300
301    /// Returns a pixel-doubled version of this font.
302    ///
303    /// The returned font renders at 2x the original size by drawing each pixel
304    /// as a 2x2 block. This incurs no additional memory cost since it uses the
305    /// same underlying bitmap data.
306    ///
307    /// # Example
308    ///
309    /// ```rust
310    /// # #[cfg(feature = "tamzen")]
311    /// # {
312    /// use embedded_bitmap_fonts::tamzen::FONT_8x15;
313    ///
314    /// let doubled = FONT_8x15.pixel_double();
315    /// assert_eq!(doubled.width(), 16);
316    /// assert_eq!(doubled.height(), 30);
317    /// # }
318    /// ```
319    #[must_use]
320    pub const fn pixel_double(self) -> Self {
321        self.pixel_multiply(2)
322    }
323
324    /// Returns a pixel-tripled version of this font.
325    ///
326    /// The returned font renders at 3x the original size.
327    #[must_use]
328    pub const fn pixel_triple(self) -> Self {
329        self.pixel_multiply(3)
330    }
331
332    /// Returns a version of this font with custom pixel multiplication.
333    ///
334    /// # Panics
335    ///
336    /// Panics if `multiplier` is 0.
337    #[must_use]
338    pub const fn pixel_multiply(mut self, multiplier: u8) -> Self {
339        // Multiply the existing multiplier
340        let new_multiplier = self.pixels.get() * multiplier;
341        self.pixels = match NonZeroU8::new(new_multiplier) {
342            Some(px) => px,
343            None => panic!("pixel multiplier cannot be zero"),
344        };
345        self
346    }
347}
348
349/// Text style for rendering with a [`BitmapFont`].
350///
351/// This is the equivalent of [`MonoTextStyle`](embedded_graphics::mono_font::MonoTextStyle)
352/// for bitmap fonts.
353///
354/// # Example
355///
356/// ```rust,no_run
357/// # #[cfg(feature = "tamzen")]
358/// # {
359/// use embedded_bitmap_fonts::{TextStyle, tamzen::FONT_8x15};
360/// use embedded_graphics::{pixelcolor::BinaryColor, prelude::*, text::Text};
361///
362/// # struct Display;
363/// # impl OriginDimensions for Display { fn size(&self) -> Size { Size::zero() } }
364/// # impl DrawTarget for Display {
365/// #     type Color = BinaryColor;
366/// #     type Error = core::convert::Infallible;
367/// #     fn draw_iter<I>(&mut self, _: I) -> Result<(), Self::Error>
368/// #     where I: IntoIterator<Item = Pixel<BinaryColor>> { Ok(()) }
369/// # }
370/// # fn main() -> Result<(), core::convert::Infallible> {
371/// # let mut display = Display;
372/// let style = TextStyle::new(&FONT_8x15, BinaryColor::On);
373/// let text = Text::new("Hello!", Point::zero(), style);
374/// text.draw(&mut display)?;
375/// # Ok(())
376/// # }
377/// # }
378/// ```
379#[derive(Clone, Copy)]
380#[non_exhaustive]
381pub struct TextStyle<'a> {
382    /// The font to use for rendering.
383    pub font: &'a BitmapFont<'a>,
384    /// The foreground color for text pixels.
385    pub color: BinaryColor,
386}
387
388impl<'a> TextStyle<'a> {
389    /// Creates a new text style with the given font and color.
390    #[inline]
391    pub const fn new(font: &'a BitmapFont<'a>, color: BinaryColor) -> Self {
392        Self { font, color }
393    }
394}
395
396impl TextRenderer for TextStyle<'_> {
397    type Color = BinaryColor;
398
399    fn draw_string<D>(
400        &self,
401        text: &str,
402        mut pos: Point,
403        _baseline: Baseline,
404        target: &mut D,
405    ) -> Result<Point, D::Error>
406    where
407        D: DrawTarget<Color = Self::Color>,
408    {
409        for c in text.chars() {
410            let glyph_idx = self.font.glyph_mapping.index(c) as u32;
411            self.font.draw_glyph(glyph_idx, target, self.color, pos)?;
412            pos += Size::new(self.font.width(), 0);
413        }
414        Ok(pos)
415    }
416
417    fn draw_whitespace<D>(
418        &self,
419        width: u32,
420        pos: Point,
421        _baseline: Baseline,
422        _target: &mut D,
423    ) -> Result<Point, D::Error>
424    where
425        D: DrawTarget<Color = Self::Color>,
426    {
427        // No background drawing, just advance position
428        Ok(pos + Size::new(width * self.font.width(), 0))
429    }
430
431    fn measure_string(&self, text: &str, pos: Point, _baseline: Baseline) -> TextMetrics {
432        let width = self.font.width() * text.len() as u32;
433        let height = self.font.height();
434        TextMetrics {
435            bounding_box: Rectangle::new(pos, Size::new(width, height)),
436            next_position: pos + Size::new(width, 0),
437        }
438    }
439
440    fn line_height(&self) -> u32 {
441        self.font.height()
442    }
443}
444
445/// Internal draw target that handles pixel multiplication.
446///
447/// This wraps another draw target and multiplies each pixel by the specified
448/// factor, drawing NxN blocks for each source pixel.
449struct PixelMultiplyDrawTarget<'a, D: DrawTarget<Color = BinaryColor>> {
450    target: &'a mut D,
451    color: BinaryColor,
452    offset: Point,
453    pixels: NonZeroU8,
454}
455
456impl<D> Dimensions for PixelMultiplyDrawTarget<'_, D>
457where
458    D: DrawTarget<Color = BinaryColor>,
459{
460    fn bounding_box(&self) -> Rectangle {
461        let mut bb = self.target.bounding_box();
462        bb.top_left -= self.offset;
463        bb.top_left /= self.pixels.get().into();
464        bb.size /= self.pixels.get().into();
465        bb
466    }
467}
468
469impl<D> DrawTarget for PixelMultiplyDrawTarget<'_, D>
470where
471    D: DrawTarget<Color = BinaryColor>,
472{
473    type Color = BinaryColor;
474    type Error = D::Error;
475
476    fn draw_iter<I>(&mut self, pixel_iter: I) -> Result<(), Self::Error>
477    where
478        I: IntoIterator<Item = Pixel<BinaryColor>>,
479    {
480        let color = self.color;
481        let offset = self.offset;
482        let multiplier = self.pixels.get();
483
484        self.target.draw_iter(
485            pixel_iter
486                .into_iter()
487                .filter(|pixel| pixel.1 == BinaryColor::On)
488                .flat_map(|pixel| {
489                    PixelMultiplyIterator::new(pixel, multiplier)
490                        .map(move |p| Pixel(p.0 + offset, color))
491                }),
492        )
493    }
494}
495
496/// Iterator that expands a single pixel into an NxN block.
497struct PixelMultiplyIterator {
498    color: BinaryColor,
499    base_pos: Point,
500    x: u8,
501    y: u8,
502    multiplier: u8,
503}
504
505impl PixelMultiplyIterator {
506    fn new(pixel: Pixel<BinaryColor>, multiplier: u8) -> Self {
507        Self {
508            color: pixel.1,
509            base_pos: pixel.0 * multiplier as i32,
510            x: 0,
511            y: 0,
512            multiplier,
513        }
514    }
515}
516
517impl Iterator for PixelMultiplyIterator {
518    type Item = Pixel<BinaryColor>;
519
520    fn next(&mut self) -> Option<Pixel<BinaryColor>> {
521        if self.y >= self.multiplier {
522            return None;
523        }
524
525        let pixel = Pixel(
526            Point::new(
527                self.base_pos.x + self.x as i32,
528                self.base_pos.y + self.y as i32,
529            ),
530            self.color,
531        );
532
533        self.x += 1;
534        if self.x >= self.multiplier {
535            self.x = 0;
536            self.y += 1;
537        }
538
539        Some(pixel)
540    }
541
542    fn size_hint(&self) -> (usize, Option<usize>) {
543        let remaining =
544            (self.multiplier - self.y) as usize * self.multiplier as usize - self.x as usize;
545        (remaining, Some(remaining))
546    }
547}
548
549impl ExactSizeIterator for PixelMultiplyIterator {}
550
551#[cfg(test)]
552mod tests {
553    use super::*;
554
555    fn px(x: i32, y: i32) -> Pixel<BinaryColor> {
556        Pixel(Point::new(x, y), BinaryColor::On)
557    }
558
559    #[test]
560    fn pixel_multiply_iterator_1x() {
561        let pixels: Vec<_> = PixelMultiplyIterator::new(px(0, 0), 1).collect();
562        assert_eq!(pixels, vec![px(0, 0)]);
563
564        let pixels: Vec<_> = PixelMultiplyIterator::new(px(5, 3), 1).collect();
565        assert_eq!(pixels, vec![px(5, 3)]);
566    }
567
568    #[test]
569    fn pixel_multiply_iterator_2x() {
570        let pixels: Vec<_> = PixelMultiplyIterator::new(px(0, 0), 2).collect();
571        assert_eq!(pixels, vec![px(0, 0), px(1, 0), px(0, 1), px(1, 1)]);
572
573        let pixels: Vec<_> = PixelMultiplyIterator::new(px(1, 2), 2).collect();
574        assert_eq!(pixels, vec![px(2, 4), px(3, 4), px(2, 5), px(3, 5)]);
575    }
576
577    #[test]
578    fn pixel_multiply_iterator_3x() {
579        let pixels: Vec<_> = PixelMultiplyIterator::new(px(0, 0), 3).collect();
580        assert_eq!(
581            pixels,
582            vec![
583                px(0, 0),
584                px(1, 0),
585                px(2, 0),
586                px(0, 1),
587                px(1, 1),
588                px(2, 1),
589                px(0, 2),
590                px(1, 2),
591                px(2, 2)
592            ]
593        );
594    }
595
596    #[test]
597    fn pixel_multiply_iterator_exact_size() {
598        let iter = PixelMultiplyIterator::new(px(0, 0), 3);
599        assert_eq!(iter.len(), 9);
600
601        let mut iter = PixelMultiplyIterator::new(px(0, 0), 2);
602        assert_eq!(iter.len(), 4);
603        iter.next();
604        assert_eq!(iter.len(), 3);
605        iter.next();
606        assert_eq!(iter.len(), 2);
607    }
608}