Font Generation Pipeline

SkillWeb & browsing

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.

Available today. Use it from your connected AI after setup.

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):

  • 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

  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.csInterface for the tool-facing font manager
Gum/Services/Fonts/FontManager.csTool facade — delegates to headless service via DI
Tools/Gum.ProjectServices/FontGeneration/HeadlessFontGenerationService.csCore generation logic — collection, size estimation, bmfont.exe invocation
Tools/Gum.ProjectServices/FontGeneration/IHeadlessFontGenerationService.csInterface for the headless service
Tools/Gum.ProjectServices/FontGeneration/IFontGenerationCallbacks.csCallback interface (output, spinner, file-watch ignore)
Gum/Services/Fonts/ToolFontGenerationCallbacks.csTool-specific callback implementation
RenderingLibrary/Graphics/Fonts/BmfcSave.csFont data model, .bmfc serialization, cache file naming, range utilities
Gum/Content/BmfcTemplate.bmfcTemplate with placeholders for bmfont.exe config
GumProjectFontGenerator/Program.csStandalone 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