Font Generation Pipeline
SkillWeb & browsingGum 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.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Font Generation Pipeline skill
What this skill tells your AI
The instructions your AI receives, as published by vchelaru/gum in .claude/skills/gum-tool-font-generation/SKILL.md and read by ahel’s review.
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):
BmFontExeFileGeneratorshells out to the embeddedbmfont.exe(Windows-only). Legacy path; does not support dropshadow at all —BmfcTemplate.bmfchas no dropshadow placeholders, soBmfcSave.HasDropshadow/Dropshadow*fields are silently ignored on this backend.KernSmithFileGeneratorcalls 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 viaTask.WhenAllfor 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:
TryGetBmfcSaveForreads font properties from aStateSave(with optional instance prefix and forced overrides) and returns aBmfcSaveor null. For direct Text instances this is sufficient, but for component instances containing Text,CollectFontsFromNestedTextInstancesrecursively descends usingRecursiveVariableFinderto resolve font properties through exposed variables and inheritance. - Deduplication:
CollectRequiredFontsiterates all elements/states/instances and deduplicates byFontCacheFileName(a deterministic name encoding all font parameters). - Size estimation: Before generating,
AssignEstimatedNeededSizeOneither runs a binary-search optimization (GetOptimizedSizeFor, controlled byAutoSizeFontOutputsproject setting) or uses a heuristic lookup table (EstimateBlocksNeeded) based on effective font size. - Template expansion:
BmfcSave.Save()loadsContent/BmfcTemplate.bmfc, replaces placeholders, and writes the.bmfcfile alongside the target.fntpath. - bmfont.exe invocation:
CreateBitmapFontFilesIfNecessaryAsynclaunchesbmfont.exe -c "<bmfc>" -o "<fnt>"viaProcess.StartwithUseShellExecute = true. The process is awaited async (WaitForExitAsync) or synchronously depending oncreateTask. - File-watch suppression: Before writing, all expected output paths (.bmfc, .fnt, .png pages) are registered with
IFontGenerationCallbacks.OnIgnoreFileChangeso 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
FontRangessetting 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 GenerateRangesFromFilecan 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 |
Off Windows: BmFont requests resolve to KernSmith
bmfont.exe only runs on Windows, but GumProjectSave.FontGenerator defaults to BmFont for back-compat. FontGeneratorResolver (Gum.ProjectServices) maps the requested type to the effective one: BmFont on macOS/Linux becomes KernSmith; everything else passes through. FontFileGeneratorSelector and the CLI fonts command both go through it, and the selector takes an optional isBmFontSupported delegate so tests can exercise the substitution on any OS. KernSmith output differs slightly from bmfont output, so a project generated on Windows and then on macOS will have different .fnt/.png bytes unless it already uses KernSmith.
Signals
- GitHub stars
- 620
- Forks
- 80
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
gum-tool-font-generation- Source
- github.com/vchelaru/gum