| name | gum-tool-font-generation |
| description | Gum bitmap font generation — tool converts font properties into .fnt/.png via bmfont.exe or KernSmith. Triggers: BmfcSave, HeadlessFontGenerationService, FontManager, FontFileGeneratorSelector, KernSmithFileGenerator, BmfcTemplate.bmfc, texture size estimation, dropshadow. |
Font Generation Pipeline
Gum generates BMFont-format bitmap fonts (.fnt + .png atlas). Pipeline: collect font properties → build BmfcSave → pick a generator backend → produce .fnt + .png. Two interchangeable IFontFileGenerator backends exist, chosen per-project via GumProjectSave.FontGenerator (FontGeneratorType.BmFont, the default for back-compat, or .KernSmith):
BmFontExeFileGenerator shells out to the embedded bmfont.exe (Windows-only). Legacy path; does not support dropshadow at all — BmfcTemplate.bmfc has no dropshadow placeholders, so BmfcSave.HasDropshadow/Dropshadow* fields are silently ignored on this backend.
KernSmithFileGenerator calls the KernSmith library in-process (cross-platform, no external exe, supports dropshadow). This is the newer, recommended backend.
FontFileGeneratorSelector picks between them at generation time (project isn't loaded yet when DI wires services up, so selection can't happen at construction).
Architecture
FontManager (tool facade)
└─ HeadlessFontGenerationService (core logic, headless)
├─ BmfcSave (data model + .bmfc serialization)
│ └─ BmfcTemplate.bmfc (template file with placeholders, bmfont.exe only)
└─ FontFileGeneratorSelector → BmFontExeFileGenerator | KernSmithFileGenerator
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 for the bmfont.exe backend only (throws PlatformNotSupportedException off-Windows when FontGeneratorType.BmFont is selected). It owns:
- Collecting all unique fonts a project needs (
CollectRequiredFonts)
- Deciding whether a font file already exists or needs (re)generation
- Invoking the selected
IFontFileGenerator — one call per font, all awaited via Task.WhenAll for parallelism
- Texture size estimation (heuristic) and optimization (binary search over
AvailableSizes)
Texture-size estimation is bmfont.exe-specific and does not run for KernSmith. EstimateBlocksNeeded's heuristic (and the dropshadow blindness that falls out of it) only matters for BmFontExeFileGenerator. IFontFileGenerator.RequiresSizeEstimation (false on KernSmithFileGenerator, true on BmFontExeFileGenerator) gates the whole AssignEstimatedNeededSizeOn call in HeadlessFontGenerationService — KernSmith instead sizes its own atlas via FontGeneratorOptions.AutofitTexture.
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:
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.
- Deduplication:
CollectRequiredFonts iterates all elements/states/instances and deduplicates by FontCacheFileName (a deterministic name encoding all font parameters).
- 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.
- Template expansion:
BmfcSave.Save() loads Content/BmfcTemplate.bmfc, replaces placeholders, and writes the .bmfc file alongside the target .fnt path.
- 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.
- 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
| File | Purpose |
|---|
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 |