gba_agb_font_renderer 0.3.0

Bitmap font renderer for GBA/AGB
use crate::prelude::{FullFont, PrintableFont};

pub mod full;
pub mod printable;

/// 4bpp font for gba
pub trait AgbFont {
    fn char_widths(&self) -> &[u8];

    #[inline]
    fn char_width(&self, c: u8) -> u8 {
        self.char_widths()[c as usize - self.char_offset()]
    }

    fn char_offset(&self) -> usize;

    fn data(&self) -> &[u32];

    fn glyph_height(&self) -> u32;

    fn glyph_size(&self) -> usize;

    fn row_u32s(&self) -> usize;

    /// Return the pixel data for the glyph corresponding to `c`.
    ///
    /// Panics in debug builds if `c` is outside the font's character range.
    /// In release builds, out-of-range values produce undefined pixel data.
    fn glyph(&self, c: u8) -> &[u32] {
        if cfg!(debug_assertions) && self.char_offset() != 0 && !(32..=126).contains(&c) {
            panic!("glyph {c} out of printable bounds");
        }
        let idx = c as usize - self.char_offset();
        let offset = idx * self.glyph_size();
        unsafe {
            self.data()
                .get_unchecked(offset..offset + self.glyph_size())
        }
    }

    /// Measures one line of text, returning `(line_width_px, bytes_consumed)`.
    ///
    /// `bytes_consumed` includes the terminating `\n` if present, so
    /// `&text[bytes_consumed..]` always starts the next line. Wrapping
    /// never splits off a zero-width line: the first character always fits.
    fn measure_line(&self, text: &[u8], wrap_at: Option<u32>) -> (u32, usize) {
        let mut width: u32 = 0;
        for (i, &c) in text.iter().enumerate() {
            if c == b'\n' {
                return (width, i + 1);
            }
            let char_w = self.char_width(c) as u32;
            if let Some(max) = wrap_at
                && i > 0
                && width + char_w > max
            {
                return (width, i);
            }
            width += char_w;
        }
        (width, text.len())
    }

    /// Size of text in pixels if rendered with this font.
    fn size_of(&self, text: &[u8], wrap_at: Option<u8>) -> (u8, u8) {
        if text.is_empty() {
            return (0, 0);
        }
        let max_width = wrap_at.map(|v| v as u32);
        let mut max_w: u32 = 0;
        let mut total_h: u32 = 0;
        let mut remaining = text;
        loop {
            let (line_w, consumed) = self.measure_line(remaining, max_width);
            if line_w > max_w {
                max_w = line_w;
            }
            total_h += self.glyph_height();
            remaining = &remaining[consumed..];
            if remaining.is_empty() {
                break;
            }
        }
        (max_w as u8, total_h as u8)
    }
}

macro_rules! impl_agb_font {
    ($font_class:ident, $offset: expr) => {
        impl AgbFont for $font_class {
            #[inline]
            fn char_widths(&self) -> &[u8] {
                &self.char_widths
            }

            #[inline]
            fn char_offset(&self) -> usize {
                $offset
            }

            #[inline]
            fn data(&self) -> &[u32] {
                &self.data
            }

            #[inline]
            fn glyph_height(&self) -> u32 {
                self.glyph_height
            }

            #[inline]
            fn glyph_size(&self) -> usize {
                self.glyph_size
            }

            #[inline]
            fn row_u32s(&self) -> usize {
                self.row_u32s
            }
        }
    };
}

impl_agb_font!(PrintableFont, 32);
impl_agb_font!(FullFont, 0);