o
odin.langpkg.dev
packages / library / odin-bmfont

odin-bmfont

d70a626library

Bitmap fonts parser for Odin.

MIT · updated 7 days ago

Odin BMFont

Bitmap font parser and render helpers for Odin.

Odin BMFont example preview

Currently supports parsing following BMFont formats:

  • XML — Standard BMFont XML (.xml, .fnt), produced by virtually every exporter. Requires a separate atlas texture. Check ./fonts/WhitePeaberry.xml for example.
  • Text — AngelCode's text format (.txt, .fnt) — one tag per line followed by key=value pairs. Check ./fonts/WhitePeaberry.txt for example. Requires a separate atlas texture.
  • JSON byte stream — Each character's pixels are encoded directly as bit-pattern entries in a JSON map. The parser packs them into a virtual atlas for you. Check ./fonts/monogram-bitfontmaker.json for example.

Usage

Parsing

import "bmfont"

// Load and parse your XML font to bmfont.Font
font, err := bmfont.load_bmfont_xml(#load("fonts/WhitePeaberry.xml"))
defer bmfont.destroy_font(font) // Remember to free it after use

// Text format is also supported
font, err := bmfont.load_bmfont_text(#load("fonts/WhitePeaberry.txt"),
                                     allocator=context.temp_allocator)

// JSON bytestream — returns the `Font` and `Atlas :: struct {pixels: [][4]u8, size: [2]int}`
font, atlas, err := bmfont.load_json_bytestream(
    #load("fonts/monogram-bitfontmaker.json"),
    include     = "", // string of codepoints to keep in atlas/font; empty keeps everything
    space_width = 4,  // space glyph is made from empty bytes, so you need to set it's width explicitly
    atlas_cols  = 10, // how many glyphs should be in the atlas horizontally
    atlas_gap   = 1,  // gap in pixels between glyphs in atlas
    allocator   = context.temp_allocator,
)
defer delete(atlas.pixels) // atlas pixels are allocated with `allocator` param

find_glyph(font, codepoint) resolves a rune to a Glyph in O(runs of consecutive codepoints) via the per-font ranges table.

Drawing

draw_text is backend-agnostic.
Each glyph turn calls the callback with the source rect (in atlas pixels)
and the destination rect (in screen pixels)
so the caller can blit however it likes:

import "bmfont"

bmfont.draw_text("Hello", font,
    my_draw_callback,  // proc (src, dst: Rect)
    scale  = 4,        // atlas-pixel-to-screen-pixel multiplier
    origin = {10, 10}, // top-left of the text in screen pixels
    cursor = &cursor,  // optional pen position; updated as text advances
)

Newlines (\n) reset the pen to origin.x and advance y by the line height.
Tabs (\t) advance the pen by four spaces.
Unknown codepoints fall through to the next glyph with a space-width advance.

font.info.spacing.x (horizontal) is added to each glyph's advance, and font.info.spacing.y (vertical) is added to the line height, so the BMFont <info spacing="…"> attribute flows through to the layout automatically.

Measuring

measure_text(text, font, scale) returns the [width, height] in screen pixels the text would occupy. Use it to size a container, compute a wrap width, or pre-compute a cursor offset without drawing:

size := bmfont.measure_text("Hello, world!", font, scale=4)
if size.x > max_width {
    // wrap, truncate, or fall back to a smaller font_size
}

space_advance(font) returns the pixel advance of a single space
(used as the fallback when a codepoint isn't in the font).

Example

In example/ you'll see how to draw some text using bitmap fonts.

make # or `odin run example`

It uses karl2d for windowing and rendering.
It requires to have it in the shared: odin collection. (odin/shared/karl2d)

See example/static_fonts.odin for how can bmfonts be loaded directly to karl2d state,
so you can use k2.draw_text() and k2.measure_text() with bmfonts.

Resources

License