Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Embedding Fonts (BDF)

stdgba embeds bitmap fonts at compile time from BDF files through gba::embed::bdf in <gba/embed>.

BDF format reference: Glyph Bitmap Distribution Format (Wikipedia).

This gives you a typed font object with:

  • per-glyph metrics and offsets,
  • packed 1bpp glyph bitmap data,
  • helpers for BIOS BitUnPack parameters,
  • lookup with fallback to DEFAULT_CHAR.

Quick start

#include <array>
#include <gba/embed>

static constexpr auto font = gba::embed::bdf([] {
    return std::to_array<unsigned char>({
#embed "9x18.bdf"
    });
});

static_assert(font.glyph_count > 0);

The returned type is gba::embed::bdf_font_result<GlyphCount, BitmapBytes>.

Demo

The demo below embeds multiple BDF files and renders them in one text layer.

Demo fonts used:

  • 6x13B.bdf
  • HaxorMedium-12.bdf

Font source: IT-Studio-Rech/bdf-fonts.

The demo applies with_shadow<1, 1> to both embedded fonts and uses the two_plane_three_color profile so the shadow pass is visible.


#include <gba/bios>
#include <gba/embed>
#include <gba/interrupt>
#include <gba/text>

#include <array>

int main() {
    using namespace gba::literals;

    static constexpr auto base_font_ui = gba::embed::bdf([] {
        return std::to_array<unsigned char>({
#embed "6x13B.bdf"
        });
    });

    static constexpr auto base_font_haxor = gba::embed::bdf([] {
        return std::to_array<unsigned char>({
#embed "HaxorMedium-12.bdf"
        });
    });

    static constexpr auto font_ui = gba::text::with_shadow<1, 1>(base_font_ui);
    static constexpr auto font_haxor = gba::text::with_shadow<1, 1>(base_font_haxor);

    gba::irq_handler = {};
    gba::reg_dispstat = {.enable_irq_vblank = true};
    gba::reg_ie = {.vblank = true};
    gba::reg_ime = true;

    gba::reg_dispcnt = {.video_mode = 0, .enable_bg0 = true};
    gba::reg_bgcnt[0] = {.screenblock = 31};

    constexpr auto config = gba::text::bitplane_config{
        .profile = gba::text::bitplane_profile::two_plane_three_color,
        .palbank_0 = 1,
        .palbank_1 = 2,
        .start_index = 1,
    };

    constexpr auto theme = gba::text::bitplane_theme{
        .background = "#1A2238"_clr,
        .foreground = "#F6F7FB"_clr,
        .shadow = "#0A1020"_clr,
    };

    gba::text::set_theme(config, theme);
    gba::pal_bg_mem[0] = theme.background;

    gba::text::linear_tile_allocator alloc{.next_tile = 1, .end_tile = 512};
    using layer_type = gba::text::bg4bpp_text_layer<240, 160>;
    static layer_type::cell_state_map cell_state{};
    layer_type layer{31, config, alloc, cell_state};

    // Stream metrics for layout
    gba::text::stream_metrics title_metrics{
        .letter_spacing_px = 0,
        .line_spacing_px = 0,
        .tab_width_px = 32,
        .wrap_width_px = 224,
    };
    gba::text::stream_metrics body_metrics{
        .letter_spacing_px = 1,
        .line_spacing_px = 1,
        .tab_width_px = 32,
        .wrap_width_px = 224,
    };

    layer.draw_stream(font_haxor, "Embedded BDF fonts", 4, 8, title_metrics);

    layer.draw_stream(font_haxor, "HaxorMedium-12: ABC abc 0123", 4, 34, body_metrics);

    layer.draw_stream(font_ui, "6x13B: GBA text layer sample", 4, 64, body_metrics);

    layer.draw_stream(font_ui, "glyph_or_default + BitUnPack-ready rows", 4, 84, body_metrics);

    layer.flush_cache();

    while (true) {
        gba::VBlankIntrWait();
    }
}

Embedded fonts demo

What embed::bdf(...) parses

The parser expects standard text BDF structure and reads these fields:

  • font-level:
    • FONTBOUNDINGBOX
    • CHARS
    • FONT_ASCENT and FONT_DESCENT (from STARTPROPERTIES block)
    • DEFAULT_CHAR (optional, from STARTPROPERTIES)
  • per-glyph:
    • STARTCHAR / ENDCHAR
    • ENCODING
    • DWIDTH
    • BBX
    • BITMAP

It validates glyph counts and bitmap row sizes at compile time.

BDF to GBA bitmap packing

Each BITMAP row is packed to 1bpp bytes in a BIOS-friendly way:

  • leftmost source pixel is written to bit 0 (LSB),
  • rows are stored in row-major order,
  • byte width is (glyph_width + 7) / 8.

This layout is designed so BitUnPack can expand glyph rows directly.

Using glyph metadata

const auto& g = font.glyph_or_default(static_cast<unsigned int>('A'));

auto width_px = g.width;
auto height_px = g.height;
auto advance_px = g.dwidth;

Useful members on glyph:

  • encoding
  • dwidth
  • width, height
  • x_offset, y_offset
  • bitmap_offset
  • bitmap_byte_width
  • bitmap_bytes()

Accessing bitmap data and BitUnPack headers

#include <gba/bios>

const auto& g = font.glyph_or_default(static_cast<unsigned int>('A'));
const unsigned char* src = font.bitmap_data(g);

auto unpack = g.bitunpack_header(
    /*dst_bpp=*/4,
    /*dst_ofs=*/1,
    /*offset_zero=*/false
);

// Example destination buffer for expanded glyph data
unsigned int expanded[128]{};

gba::BitUnPack(src, expanded, unpack);

You can also fetch by encoding directly:

const unsigned char* src = font.bitmap_data(static_cast<unsigned int>('A'));
auto unpack = font.bitunpack_header(static_cast<unsigned int>('A'));

Fallback behaviour

glyph_or_default(encoding) resolves in this order:

  1. exact glyph encoding,
  2. DEFAULT_CHAR (if present and found),
  3. glyph index 0.

This makes rendering robust when text includes characters not present in your BDF.

Font variants for text rendering

After embedding, you can generate compile-time variants for the text renderer:

#include <gba/text>

static constexpr auto font_shadow = gba::text::with_shadow<1, 1>(font);
static constexpr auto font_outline = gba::text::with_outline<1>(font);

These variants keep the same font-style API but add pre-baked decoration masks.

See also