Gum gum-tool-font-generation

Reference guide for Gum's bitmap font generation pipeline — how the tool converts font properties into .fnt/.png files via bmfont.exe. Load this when working on BmfcSave, HeadlessFontGenerationService, FontManager, BmfcTemplate.bmfc, font cache naming, texture size estimation, or the GumProjectFontGenerator CLI.

install
source · Clone the upstream repo
git clone https://github.com/vchelaru/Gum
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/vchelaru/Gum "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/gum-tool-font-generation" ~/.claude/skills/vchelaru-gum-gum-tool-font-generation && rm -rf "$T"
manifest: .claude/skills/gum-tool-font-generation/SKILL.md
source content

Font Generation Pipeline

Gum generates BMFont-format bitmap fonts (

.fnt
+
.png
atlas) by shelling out to
bmfont.exe
. The pipeline is: collect font properties → build BmfcSave → write .bmfc file → invoke bmfont.exe → produce .fnt + .png.

Future direction: The bmfont.exe dependency is being evaluated for replacement due to platform limitations (Windows-only) and other concerns.

Architecture

FontManager (tool facade)
  └─ HeadlessFontGenerationService (core logic, headless)
       ├─ BmfcSave (data model + .bmfc serialization)
       │    └─ BmfcTemplate.bmfc (template file with placeholders)
       └─ bmfont.exe (external process, one per font, run in parallel)

FontManager is the tool-facing entry point. It wires

IFontGenerationCallbacks
(UI output, spinner, file-watch suppression) and delegates all real work to HeadlessFontGenerationService via
IHeadlessFontGenerationService
.

HeadlessFontGenerationService is platform-checked (Windows-only, throws

PlatformNotSupportedException
otherwise). It owns:

  • Collecting all unique fonts a project needs (
    CollectRequiredFonts
    )
  • Deciding whether a font file already exists or needs (re)generation
  • Launching bmfont.exe processes — one per font, all awaited via
    Task.WhenAll
    for parallelism
  • Texture size estimation (heuristic) and optimization (binary search over
    AvailableSizes
    )

BmfcSave holds the six font properties (FontName, FontSize, OutlineThickness, UseSmoothing, IsItalic, IsBold) plus ranges, spacing, and output dimensions. Its

Save()
method loads
BmfcTemplate.bmfc
and does string replacement to produce the
.bmfc
file that bmfont.exe consumes. It also owns
FontCacheFileName
which determines the output path.

Generation Flow

  1. Property collection:
    TryGetBmfcSaveFor
    reads font properties from a
    StateSave
    (with optional instance prefix and forced overrides) and returns a
    BmfcSave
    or null. For direct Text instances this is sufficient, but for component instances containing Text,
    CollectFontsFromNestedTextInstances
    recursively descends using
    RecursiveVariableFinder
    to resolve font properties through exposed variables and inheritance.
  2. Deduplication:
    CollectRequiredFonts
    iterates all elements/states/instances and deduplicates by
    FontCacheFileName
    (a deterministic name encoding all font parameters).
  3. Size estimation: Before generating,
    AssignEstimatedNeededSizeOn
    either runs a binary-search optimization (
    GetOptimizedSizeFor
    , controlled by
    AutoSizeFontOutputs
    project setting) or uses a heuristic lookup table (
    EstimateBlocksNeeded
    ) based on effective font size.
  4. Template expansion:
    BmfcSave.Save()
    loads
    Content/BmfcTemplate.bmfc
    , replaces placeholders, and writes the
    .bmfc
    file alongside the target
    .fnt
    path.
  5. bmfont.exe invocation:
    CreateBitmapFontFilesIfNecessaryAsync
    launches
    bmfont.exe -c "<bmfc>" -o "<fnt>"
    via
    Process.Start
    with
    UseShellExecute = true
    . The process is awaited async (
    WaitForExitAsync
    ) or synchronously depending on
    createTask
    .
  6. File-watch suppression: Before writing, all expected output paths (.bmfc, .fnt, .png pages) are registered with
    IFontGenerationCallbacks.OnIgnoreFileChange
    so the file watcher doesn't trigger reloads.

Font Cache Naming

BmfcSave.FontCacheFileName
produces paths like
FontCache/Font18Arial.fnt
. The name encodes all parameters that affect output:

  • Base:
    Font{size}{name}
    (spaces in font name → underscores)
  • Suffixes:
    _o{N}
    (outline),
    _noSmooth
    ,
    _Italic
    ,
    _Bold

This means two elements with identical font settings share one cached file.

Channel Behavior (Outline vs No-Outline)

When OutlineThickness is 0: alpha=0, RGB channels=4 (glyph in alpha channel). When OutlineThickness > 0: alpha=1, RGB channels=0 (outline uses color channels).

Texture Size Optimization

GetOptimizedSizeFor
does a binary search over
AvailableSizes
(32x32 up to 8192x8192) to find the smallest texture that keeps the font on a single page. Each probe generates the font to a temp directory and parses the
pages=
line from the resulting
.fnt
file.

The heuristic fallback (

EstimateBlocksNeeded
) uses a lookup table mapping effective font size to a number of 256-pixel blocks, then factors the block count into width x height.

Character Ranges

  • Default:
    32-126,160-255
    (ASCII + Latin-1 Supplement)
  • Project-level
    FontRanges
    setting overrides for all fonts
  • Ranges are validated (no spaces, proper start < end), with automatic fallback to default
  • Space character (32) is always included via
    EnsureRangesContainSpace
  • Large range sets are split across multiple
    chars=
    lines (max 10 blocks per line) for bmfont.exe compatibility
  • GenerateRangesFromFile
    can derive ranges from a text file's unique characters

Embedded Resources

bmfont.exe
and
BmfcTemplate.bmfc
are embedded in the
Gum.ProjectServices
assembly and extracted on first use by
EnsureToolsExtracted
. The template uses placeholder tokens like
FontNameVariable
,
FontSizeVariable
,
{UseSmoothing}
, etc.

Standalone CLI

GumProjectFontGenerator
is a standalone console app that loads a
.gumx
project and generates all missing fonts. It uses
HeadlessFontGenerationService
directly with no callbacks.

Font Creation Paths

All font generation now routes through

HeadlessFontGenerationService
. There are two call sites:

On-demand:

CustomSetPropertyOnRenderable
resolves
IFontManager
via DI (
Builder.Get<IFontManager>()
) and calls
CreateFontIfNecessary(BmfcSave)
when a font property changes. The BBCode path (
GetAndCreateFontIfNecessary
) uses the same pattern for inline font tags.

Bulk (tool-only):

CreateAllMissingFontFiles
scans the entire project on load or "regenerate fonts" and generates all missing font files in parallel.

Both delegate to

HeadlessFontGenerationService
. Legacy
IRuntimeFontService
,
GenerateMissingFontsForReferencingElements
, and the embedded bmfont.exe in RenderingLibrary have been removed.

Key Files

FilePurpose
Gum/Services/Fonts/IFontManager.cs
Interface for the tool-facing font manager
Gum/Services/Fonts/FontManager.cs
Tool facade — delegates to headless service via DI
Tools/Gum.ProjectServices/FontGeneration/HeadlessFontGenerationService.cs
Core generation logic — collection, size estimation, bmfont.exe invocation
Tools/Gum.ProjectServices/FontGeneration/IHeadlessFontGenerationService.cs
Interface for the headless service
Tools/Gum.ProjectServices/FontGeneration/IFontGenerationCallbacks.cs
Callback interface (output, spinner, file-watch ignore)
Gum/Services/Fonts/ToolFontGenerationCallbacks.cs
Tool-specific callback implementation
RenderingLibrary/Graphics/Fonts/BmfcSave.cs
Font data model, .bmfc serialization, cache file naming, range utilities
Gum/Content/BmfcTemplate.bmfc
Template with placeholders for bmfont.exe config
GumProjectFontGenerator/Program.cs
Standalone CLI for batch font generation