mxbmrp3

mxbmrp3_fontgen - a portable PiBoSo .fnt bitmap-font generator

A cross-platform, drop-in replacement for PiBoSo’s Windows-only fontgen.exe. It rasterizes a TrueType/OpenType font and writes the exact .fnt binary the PiBoSo games (MX Bikes / GP Bikes / KRP / WRS) load - so the output works both in-game and in this plugin’s companion-window software renderer.

Build it in Visual Studio (the mxbmrp3_fontgen target in the generated build/msvc/mxbmrp3.sln, x64) or cross-platform with build.sh:

tools/fontgen/build.sh                      # builds tools/fontgen/mxbmrp3_fontgen

# Auto-normalise: drop a .ttf/.otf in and get a normalised, drop-in .fnt.
# Sizes the cell to 135px, width-normalises digits to the plugin's column, and
# centres the digit band - the whole "make it match the others" pipeline.
tools/fontgen/mxbmrp3_fontgen MyFont.ttf             # -> MyFont.fnt
tools/fontgen/mxbmrp3_fontgen MyFont.ttf out.fnt

# Config path (full control / manual tweaks):
tools/fontgen/mxbmrp3_fontgen myfont.cfg out.fnt

Why not just use fontgen.exe?

  fontgen.exe mxbmrp3_fontgen
Platform Windows 32-bit only (needs Wine + wine32:i386 on Linux) Linux / macOS / Windows, native
Cell size opaque auto-fit; scale is an inverse area knob (2.0 → smaller cell) you set cell_height in pixels, or omit it for auto-fit
Vertical placement baked from the font, no control center / voffset to fix off-centre fonts
High-range glyphs CP1252 correct CP1252 (bytes 0x80–0x9F mapped to the right Unicode glyphs)

It reproduces fontgen’s packing algorithm faithfully - at a given cell_height the glyph widths, atlas packing, and advance follow the same rules (test.sh asserts a regenerated RobotoMono-Regular.fnt is structurally identical to the shipped font).

The vertical-centering fix

fontgen sizes the glyph cell to the actual ink bounding box of the whole character range (topmost ascender to bottommost descender). For a font with deep descenders relative to its caps - e.g. Tiny5 - the cell reserves descender room that the digits/caps don’t use, so on a number plate the digits float off-centre (~7% of the cell). fontgen has no way to correct this.

mxbmrp3_fontgen adds these knobs:

Config format

A superset of PiBoSo fontgen’s config - existing configs work unchanged. Keys:

[config]                                 # section header optional
name        = Roboto Mono Regular        # font name stored in the .fnt
filename    = RobotoMono-Regular.ttf     # source .ttf/.otf (path relative to cwd)
code_page   = 1252                        # 1252 (CP1252) or anything else = Latin-1
char_start  = 32                          # first byte (0..255)
char_end    = 255                         # last byte
spacing     = 20                          # px gap between glyphs in the atlas (mip-safe; normalised default)
bitmap_x    = 512                         # atlas width
bitmap_y    = 512                         # atlas height
cell_height = 135                         # NEW: cell height in px (omit = auto-fit)
center      = 0                           # NEW: 1 = centre the digit band
voffset     = 0                           # NEW: manual vertical nudge in px (+ = down)
mono_advance = 0                          # NEW: >0 = width-normalise digit advance to this ratio
normalize   = 0                           # NEW: 1 = apply the standard defaults below

Normalisation (automatic)

normalize = 1 (and the bare-.ttf invocation) applies the standard defaults to any field you didn’t set - so different fonts render at a consistent size, width, and vertical position in-game:

field normalised default why
cell_height 135 high-resolution cell - same on-screen size across fonts, but crisp when a HUD scales text up (high-DPI, large widgets); atlas auto-grows to 2048²
mono_advance 0.489 RobotoMono’s digit-advance/cell = the plugin’s monospace column, so numbers fit the plate the same in every font
center on centre the digit band (fixes off-centre bakes like Tiny5)
spacing 20 mip-safe inter-glyph gap. The games render the atlas with mipmaps; at the small sizes most HUD text is drawn (~20-40px, far below the 135px cell) the mip chain box-averages neighbouring cells together, so too small a gap makes the next glyph bleed across as a faint bar hugging the edge (the “green bar to the right of the K”). 20px keeps the neighbour out of the mip footprint; padding changes only atlas packing, not glyph widths/advances (on-screen layout is unchanged)

Any field you set explicitly wins, so normalize = 1 with center = 0 (or a custom voffset / cell_height) keeps your manual tweak. The shipped fonts (except the reference RobotoMono-Regular) are generated this way.

cell_height is a quality/memory knob, NOT a performance one. Both the in-game and companion text renderers iterate the destination pixels of each glyph and sample the atlas (scale = size × screenH / cellH cancels the cell resolution), so per-frame text cost tracks the on-screen text size (pixel area), not the atlas resolution. Measured on the software rasterizer across cell 48→270 at a fixed on-screen size: ~30µs/string, flat within noise (the small atlas was no faster, the large one no slower); the same text at 0.012→0.030 on-screen size went 15→75µs/string (∝ area). So pick cell_height for crispness when a HUD scales text up (and mind the atlas memory/load), never for speed - the levers for text render cost are on-screen size and character count.

scale from the old format is accepted but ignored - use cell_height instead (it is predictable; fontgen’s scale is not).

.fnt binary format

Specified once, at the top of mxbmrp3_fontgen.cpp - the writer, so it is the copy that cannot drift from what is actually emitted. tools/hud_window/README.md covers the reader’s side: decompression, caching, and why the atlas cell height makes the on-screen metrics exact.

Test

test.sh builds the tool, regenerates RobotoMono-Regular.fnt, and asserts the result is structurally identical to the shipped font (cell height, glyph widths, advance, atlas dimensions) and that the atlas round-trips through inflate.