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.
git clone https://github.com/vchelaru/Gum
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"
.claude/skills/gum-tool-font-generation/SKILL.mdFont 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
for parallelismTask.WhenAll - 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
- Property collection:
reads font properties from aTryGetBmfcSaveFor
(with optional instance prefix and forced overrides) and returns aStateSave
or null. For direct Text instances this is sufficient, but for component instances containing Text,BmfcSave
recursively descends usingCollectFontsFromNestedTextInstances
to resolve font properties through exposed variables and inheritance.RecursiveVariableFinder - Deduplication:
iterates all elements/states/instances and deduplicates byCollectRequiredFonts
(a deterministic name encoding all font parameters).FontCacheFileName - Size estimation: Before generating,
either runs a binary-search optimization (AssignEstimatedNeededSizeOn
, controlled byGetOptimizedSizeFor
project setting) or uses a heuristic lookup table (AutoSizeFontOutputs
) based on effective font size.EstimateBlocksNeeded - Template expansion:
loadsBmfcSave.Save()
, replaces placeholders, and writes theContent/BmfcTemplate.bmfc
file alongside the target.bmfc
path..fnt - bmfont.exe invocation:
launchesCreateBitmapFontFilesIfNecessaryAsync
viabmfont.exe -c "<bmfc>" -o "<fnt>"
withProcess.Start
. The process is awaited async (UseShellExecute = true
) or synchronously depending onWaitForExitAsync
.createTask - File-watch suppression: Before writing, all expected output paths (.bmfc, .fnt, .png pages) are registered with
so the file watcher doesn't trigger reloads.IFontGenerationCallbacks.OnIgnoreFileChange
Font Cache Naming
BmfcSave.FontCacheFileName produces paths like FontCache/Font18Arial.fnt. The name encodes all parameters that affect output:
- Base:
(spaces in font name → underscores)Font{size}{name} - Suffixes:
(outline),_o{N}
,_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:
(ASCII + Latin-1 Supplement)32-126,160-255 - Project-level
setting overrides for all fontsFontRanges - 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
lines (max 10 blocks per line) for bmfont.exe compatibilitychars=
can derive ranges from a text file's unique charactersGenerateRangesFromFile
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
| File | Purpose |
|---|---|
| Interface for the tool-facing font manager |
| Tool facade — delegates to headless service via DI |
| Core generation logic — collection, size estimation, bmfont.exe invocation |
| Interface for the headless service |
| Callback interface (output, spinner, file-watch ignore) |
| Tool-specific callback implementation |
| Font data model, .bmfc serialization, cache file naming, range utilities |
| Template with placeholders for bmfont.exe config |
| Standalone CLI for batch font generation |