KernSmith 0.14.0
See the version list below for details.
dotnet add package KernSmith --version 0.14.0
NuGet\Install-Package KernSmith -Version 0.14.0
<PackageReference Include="KernSmith" Version="0.14.0" />
<PackageVersion Include="KernSmith" Version="0.14.0" />
<PackageReference Include="KernSmith" />
paket add KernSmith --version 0.14.0
#r "nuget: KernSmith, 0.14.0"
#:package KernSmith@0.14.0
#addin nuget:?package=KernSmith&version=0.14.0
#tool nuget:?package=KernSmith&version=0.14.0
<p align="center"> <img src="assets/social-preview.png" alt="KernSmith — .NET bitmap font generator" width="640"> </p>
<p align="center"> <a href="LICENSE">License</a> · <a href="CHANGELOG.md">Changelog</a> · <a href="samples/">Samples</a> </p>
Features
- Font format support -- TTF, OTF, and WOFF input
- BMFont output -- text, XML, and binary
.fntformats with.pngatlas pages - GPOS kerning -- extracts kerning pairs directly from OpenType GPOS tables
- Atlas packing -- MaxRects (default) and Skyline algorithms with autofit, power-of-two, and non-square texture support
- Outline rendering -- configurable width and color
- Gradient fill -- per-glyph vertical/angled gradients with midpoint control
- Drop shadow -- offset, blur radius, color, and opacity
- SDF rendering -- signed distance field output for resolution-independent text
- Super sampling -- 2x-4x rasterization with box-filter downscale for smoother edges
- Variable fonts -- set variation axes (weight, width, slant, etc.)
- Color fonts -- COLR/CPAL emoji and color glyph rendering with palette selection
- Channel packing -- pack multiple glyphs into RGBA channels for compact atlases
- Per-channel compositing -- independent control of what each RGBA channel contains (glyph, outline, both, zero, one)
- Font subsetting -- only parses tables for requested codepoints
- Custom glyphs -- replace or add glyphs with user-supplied images
- Texture formats -- PNG (default), TGA, and DDS atlas output
- Pixel export --
AtlasPage.GetRgbaPixelData(),GetAlpha8PixelData(), andGetPremultipliedRgbaPixelData()for straight-alpha, single-channel, or premultiplied RGBA pixel access for GPU upload and custom rendering pipelines - Config formats -- read and write BMFont
.bmfcand Hiero.hiero(libGDX) config files; reading auto-detects the format by inspecting file content (extension used only when content is inconclusive), writing selects the format by extension - Reading BMFont files -- load and parse existing
.fntfiles (auto-detects text/XML/binary) - Fluent builder API -- chainable configuration as an alternative to options objects
- System font loading -- generate from installed fonts by family name, with a font registry for platforms without system font access
- Fully in-memory -- entire pipeline runs without touching disk unless you call
ToFile() - Batch generation -- parallel multi-font generation with font caching
- Pipeline metrics -- stage-level timing breakdown for profiling
- Cross-platform -- Windows, Linux, macOS via .NET 10.0
- Pluggable rasterizers -- swap rendering backends to match your platform and feature needs
- Hardened font parsing -- bounds validation and resource limits for untrusted font files
Rasterizer Backends
KernSmith supports pluggable rasterizer backends. Install at least one backend package to generate fonts.
| Backend | Package | Platform | Notes |
|---|---|---|---|
| FreeType | KernSmith.Rasterizers.FreeType |
Windows, Linux, macOS | Cross-platform, full feature support. |
| GDI | KernSmith.Rasterizers.Gdi |
Windows only | Matches BMFont reference output for pixel-perfect parity. |
| DirectWrite | KernSmith.Rasterizers.DirectWrite.TerraFX |
Windows only | Color font (COLR/CPAL) and variable font rendering via DirectWrite. |
| StbTrueType | KernSmith.Rasterizers.StbTrueType |
Cross-platform | Pure C#, no native dependencies. Ideal for WASM, AOT, serverless. |
Install a backend:
dotnet add package KernSmith.Rasterizers.FreeType
dotnet add package KernSmith.Rasterizers.Gdi
dotnet add package KernSmith.Rasterizers.DirectWrite.TerraFX
dotnet add package KernSmith.Rasterizers.StbTrueType
Select a backend when generating:
var result = BmFont.Builder()
.WithFont("font.ttf")
.WithSize(32)
.WithRasterizer("gdi")
.Build();
Blazor WASM
KernSmith runs entirely client-side in Blazor WebAssembly using the StbTrueType backend. See the Blazor WASM sample for a working example.
// Generate in-browser — StbTrueType is auto-discovered
var result = BmFont.Generate(fontBytes, new FontGeneratorOptions
{
Size = 32,
Characters = CharacterSet.Ascii,
Backend = RasterizerBackend.StbTrueType
});
string fntText = result.FntText;
byte[] pngData = result.GetPngData(0);
AOT compilation (RunAOTCompilation=true) is recommended for production performance.
Native AOT and trimming
Backend auto-discovery is reflection-based and does not work under Native AOT or trimming -- the backend may be trimmed away or cannot be resolved by name, so generation fails with a "backend is not registered" error. You must register a backend explicitly. Use StbTrueType, the only AOT-compatible backend (pure C#, no native dependencies):
using System.Runtime.CompilerServices;
using KernSmith.Rasterizers.StbTrueType;
// Force the backend to load and register; the typeof reference prevents trimming.
RuntimeHelpers.RunClassConstructor(typeof(StbTrueTypeRasterizer).TypeHandle);
See the rasterizers documentation for details.
Installation
dotnet add package KernSmith
Or via the NuGet Package Manager:
Install-Package KernSmith
Quick Start
Generate a bitmap font from a .bmfc config file in one call:
using KernSmith;
var result = BmFont.FromConfig("myfont.bmfc");
result.ToFile("output/myfont");
FromConfig auto-detects the format by inspecting the file content (using the extension only as a fallback when the content is inconclusive), so a Hiero .hiero (libGDX) config works the same way:
var result = BmFont.FromConfig("myfont.hiero");
result.ToFile("output/myfont");
Generate from a TTF file using FontGeneratorOptions:
var result = BmFont.Generate("path/to/font.ttf", new FontGeneratorOptions
{
Size = 32,
Characters = CharacterSet.Ascii
});
// Write .fnt + .png + .bmfc files to disk
result.ToFile("output/myfont");
For the simplest case, just pass a size:
var result = BmFont.Generate("path/to/font.ttf", 48);
You can also pass raw font bytes:
byte[] fontData = File.ReadAllBytes("path/to/font.ttf");
var result = BmFont.Generate(fontData, new FontGeneratorOptions
{
Size = 24,
Characters = CharacterSet.ExtendedAscii,
Kerning = true
});
Fluent Builder
The BmFont.Builder() API provides a chainable alternative:
var result = BmFont.Builder()
.WithFont("path/to/font.ttf")
.WithSize(32)
.WithCharacters(CharacterSet.Ascii)
.WithPadding(1)
.WithSpacing(1, 1)
.WithKerning()
.Build();
result.ToFile("output/myfont");
Bold and Italic
var result = BmFont.Builder()
.WithSystemFont("Arial")
.WithSize(32)
.WithBold() // Uses native bold face, falls back to synthetic
.WithItalic() // Uses native italic face, falls back to synthetic
.Build();
// Force synthetic styling (skip native face lookup)
var result2 = BmFont.Builder()
.WithSystemFont("Arial")
.WithSize(32)
.WithForceSyntheticBold() // Always applies synthetic bold
.WithForceSyntheticItalic() // Always applies synthetic italic
.Build();
WithBold()/WithItalic()use the native bold/italic face when available (system fonts), falling back to syntheticWithForceSyntheticBold()/WithForceSyntheticItalic()always apply synthetic styling, skipping native face lookup- When using a file path (not a system font), bold/italic is always synthetic --
WithBold()andWithForceSyntheticBold()produce identical results - For native vs synthetic distinction, use
WithSystemFont()so the font family can be searched for a matching face
Bitmap Post-Processors
For backends without native bold/italic support, use bitmap-level post-processors:
var result = BmFont.Builder()
.WithFont("font.ttf")
.WithSize(32)
.WithPostProcessor(new BoldPostProcessor(strength: 2))
.WithPostProcessor(new ItalicPostProcessor(shear: 0.2f))
.Build();
You can also start the builder from a .bmfc config and override individual settings:
var result = BmFont.Builder()
.FromConfig("base.bmfc") // or "base.hiero" — format auto-detected by file content
.WithSize(48)
.Build();
System Fonts
Generate from a system-installed font by family name:
var result = BmFont.GenerateFromSystem("Arial", new FontGeneratorOptions
{
Size = 36,
Characters = CharacterSet.Ascii
});
Or with the builder:
var result = BmFont.Builder()
.WithSystemFont("Arial")
.WithSize(36)
.WithCharacters(CharacterSet.Latin)
.Build();
Font Registration
On platforms without system font access (Blazor WASM, mobile, containers), GenerateFromSystem() cannot find installed fonts. Register raw font data to make it available by family name:
byte[] arialData = File.ReadAllBytes("fonts/Arial.ttf");
BmFont.RegisterFont("Arial", arialData);
// Now GenerateFromSystem works with the registered font
var result = BmFont.GenerateFromSystem("Arial", new FontGeneratorOptions
{
Size = 32,
Characters = CharacterSet.Ascii
});
Register style variants separately:
BmFont.RegisterFont("Arial", arialRegularData);
BmFont.RegisterFont("Arial", arialBoldData, style: "Bold");
BmFont.RegisterFont("Arial", arialItalicData, style: "Italic");
BmFont.RegisterFont("Arial", arialBoldItalicData, style: "Bold Italic");
Registered fonts take priority over system fonts. If a registered font is not found, GenerateFromSystem() falls back to system font lookup automatically. The Bold/Italic/Bold Italic cascade is also applied -- requesting bold will check for a registered "Bold" style before falling back.
Remove registrations when no longer needed:
BmFont.UnregisterFont("Arial"); // Remove default style
BmFont.UnregisterFont("Arial", style: "Bold"); // Remove specific style
BmFont.ClearRegisteredFonts(); // Remove all registrations
Registering fonts explicitly also ensures cross-platform consistency -- the same fonts render identically everywhere regardless of what the OS has installed.
Batch Generation
Generate multiple fonts in parallel with shared font caching:
// Batch generate multiple fonts with parallel execution
var jobs = new List<BatchJob>
{
new BatchJob { SystemFont = "Arial", Options = new FontGeneratorOptions { Size = 32 } },
new BatchJob { SystemFont = "Arial", Options = new FontGeneratorOptions { Size = 48, Bold = true } },
new BatchJob { FontPath = "custom.ttf", Options = new FontGeneratorOptions { Size = 24 } },
};
var result = BmFont.GenerateBatch(jobs, new BatchOptions { MaxParallelism = 4 });
foreach (var job in result.Results)
{
if (job.Success)
job.Result!.ToFile($"output/font-{job.Index}");
}
Font Cache
Pre-load fonts for reuse across multiple generations:
// Pre-load fonts for reuse across multiple generations
var cache = new FontCache();
cache.LoadSystemFont("Arial");
cache.LoadFile("custom.ttf");
var result = BmFont.GenerateBatch(jobs, new BatchOptions
{
FontCache = cache,
MaxParallelism = 4
});
Pipeline Metrics
Profile pipeline stages to identify bottlenecks:
// Profile pipeline stages
var result = BmFont.Generate(fontData, new FontGeneratorOptions
{
Size = 32,
CollectMetrics = true
});
Console.WriteLine(result.Metrics); // Prints stage-level timing breakdown
Character Sets
Several presets are available, and you can define custom sets:
// Built-in presets
CharacterSet.Ascii // U+0020..U+007E (95 printable ASCII characters)
CharacterSet.ExtendedAscii // U+0020..U+00FF (includes accented Latin characters)
CharacterSet.Latin // ASCII + Latin Extended-A + Latin Extended-B
// From a string of characters
var custom = CharacterSet.FromChars("ABCDabcd0123!@#$");
// From Unicode ranges
var cyrillic = CharacterSet.FromRanges((0x0400, 0x04FF));
// Combine multiple sets
var combined = CharacterSet.Union(CharacterSet.Ascii, cyrillic);
Effects
Outline
var result = BmFont.Generate("font.ttf", new FontGeneratorOptions
{
Size = 48,
Characters = CharacterSet.Ascii,
Outline = 2 // 2-pixel black outline
});
With a colored outline:
var result = BmFont.Generate("font.ttf", new FontGeneratorOptions
{
Size = 48,
Characters = CharacterSet.Ascii,
Outline = 3,
OutlineR = 255, // Red outline
OutlineG = 0,
OutlineB = 0
});
Gradient
var result = BmFont.Generate("font.ttf", new FontGeneratorOptions
{
Size = 48,
Characters = CharacterSet.Ascii,
GradientStartR = 255, GradientStartG = 255, GradientStartB = 0, // Yellow top
GradientEndR = 255, GradientEndG = 0, GradientEndB = 0, // Red bottom
GradientAngle = 90f, // Top-to-bottom (default)
GradientMidpoint = 0.5f
});
Shadow
var result = BmFont.Generate("font.ttf", new FontGeneratorOptions
{
Size = 48,
Characters = CharacterSet.Ascii,
ShadowOffsetX = 2,
ShadowOffsetY = 2,
ShadowBlur = 3,
ShadowR = 0, ShadowG = 0, ShadowB = 0, // Black shadow
ShadowOpacity = 0.8f
});
For a crisp, uniform silhouette instead of soft antialiased edges, enable hard shadow:
var result = BmFont.Builder()
.WithFont("font.ttf")
.WithSize(48)
.WithCharacters(CharacterSet.Ascii)
.WithShadow(2, 2, 0)
.WithHardShadow()
.Build();
Combining Effects
All effects can be combined. They are composited in fixed order: shadow (back), outline (middle), gradient (front).
var result = BmFont.Builder()
.WithFont("font.ttf")
.WithSize(64)
.WithCharacters(CharacterSet.Ascii)
.WithOutline(2, 0, 0, 0)
.WithGradient((255, 200, 0), (255, 50, 0), angleDegrees: 90f)
.WithShadow(offsetX: 3, offsetY: 3, blur: 4, color: (0, 0, 0), opacity: 0.6f)
.Build();
Output Formats
Write to Disk
// Text format (default)
result.ToFile("output/myfont");
// XML format
result.ToFile("output/myfont", OutputFormat.Xml);
// Binary format
result.ToFile("output/myfont", OutputFormat.Binary);
ToFile writes the .fnt descriptor, all .png atlas pages, and a .bmfc config file (when source options are available).
In-Memory Access
Convenience properties give you the .fnt content without calling a formatter directly:
// BMFont text format
string fntText = result.FntText;
// BMFont XML format
string fntXml = result.FntXml;
// BMFont binary format
byte[] fntBinary = result.FntBinary;
// Encode atlas pages to PNG/TGA/DDS byte arrays
byte[][] pngFiles = result.GetPngData();
byte[] firstPng = result.GetPngData(0);
byte[][] tgaFiles = result.GetTgaData();
byte[][] ddsFiles = result.GetDdsData();
// Round-trip: export the config that produced this result
string bmfcText = result.ToBmfc();
string hieroText = result.ToHiero(); // Hiero (.hiero / libGDX) equivalent
The older ToString(), ToXml(), and ToBinary() methods still work and return the same data.
MonoGame / Game Engine Integration
Generate a bitmap font and load it straight into your engine without writing temp files:
var result = BmFont.FromConfig("ui-font.bmfc");
// Feed these to your engine's SpriteFont or BMFont loader
string fntText = result.FntText;
byte[] pngBytes = result.GetPngData(0);
Raw Pixel Access
You can also access the raw RGBA pixel data on each atlas page:
foreach (var page in result.Pages)
{
byte[] rgba = page.PixelData;
int width = page.Width;
int height = page.Height;
// Encode individual pages to other formats
byte[] tga = page.ToTga();
byte[] dds = page.ToDds();
}
Reading and Writing Config Files
KernSmith supports both BMFont .bmfc and Hiero .hiero (libGDX) configuration files. Use ConfigFormatFactory to read or write either format. Reading auto-detects the format by inspecting the file content (the extension is used only as a fallback when the content is inconclusive); writing selects the format by file extension:
using KernSmith;
// Read any supported config format (auto-detected by file content)
BmfcConfig config = ConfigFormatFactory.ReadConfig("project.hiero");
BmfcConfig other = ConfigFormatFactory.ReadConfig("project.bmfc");
// Write to a specific format (chosen by extension)
ConfigFormatFactory.WriteConfig(config, "output.hiero");
ConfigFormatFactory.WriteConfig(config, "output.bmfc");
The format-specific readers and writers are also available directly:
// BMFont .bmfc
BmfcConfig bmfcConfig = BmfcConfigReader.Read("path/to/font.bmfc");
BmfcConfig bmfcParsed = BmfcConfigReader.Parse(bmfcContent);
BmfcConfigWriter.WriteToFile(bmfcConfig, "path/to/output.bmfc");
string bmfcText = BmfcConfigWriter.Write(bmfcConfig);
// Hiero .hiero (libGDX)
BmfcConfig hieroConfig = HieroConfigReader.Read("path/to/font.hiero");
BmfcConfig hieroParsed = HieroConfigReader.Parse(hieroContent);
HieroConfigWriter.WriteToFile(hieroConfig, "path/to/output.hiero");
string hieroText = HieroConfigWriter.Write(hieroConfig);
Hiero is a lossy target for some KernSmith features (channel packing, variable axes, super sampling, and color fonts have no .hiero equivalent).
Reading BMFont Files
Load an existing .fnt file (auto-detects text, XML, or binary format):
// Load .fnt and associated .png atlas pages from disk
BmFontResult loaded = BmFont.Load("path/to/myfont.fnt");
// Access the model
var charCount = loaded.Model.Characters.Count;
var kerningCount = loaded.Model.KerningPairs.Count;
// Load just the model without atlas images
var model = BmFont.LoadModel(File.ReadAllBytes("myfont.fnt"));
// Or from a text-format string
var model2 = BmFont.LoadModel(fntTextContent);
CLI Tool
A reference command-line tool is included in tools/KernSmith.Cli/. See the CLI README for usage.
Available commands: generate, init, batch, benchmark, inspect, convert, list-fonts, list-rasterizers, info.
The init command generates a .bmfc or .hiero config file from CLI flags without rendering a font (the format is chosen by the output extension), so you can scaffold a config and tweak it by hand. generate --config and batch accept both .bmfc and .hiero configs, auto-detecting the format by inspecting file content.
Use --time to display elapsed time or --profile to show a full pipeline stage breakdown.
Advanced Features
SDF (Signed Distance Fields)
var result = BmFont.Generate("font.ttf", new FontGeneratorOptions
{
Size = 48,
Characters = CharacterSet.Ascii,
Sdf = true
});
Note: SDF cannot be combined with super sampling (SuperSampleLevel > 1).
Variable Fonts
var result = BmFont.Builder()
.WithFont("variable-font.ttf")
.WithSize(32)
.WithVariationAxis("wght", 700) // Bold weight
.WithVariationAxis("wdth", 75) // Condensed width
.Build();
Color Fonts
var result = BmFont.Generate("color-emoji.ttf", new FontGeneratorOptions
{
Size = 64,
ColorFont = true,
ColorPaletteIndex = 0 // CPAL palette index
});
Channel Packing
Pack glyphs into individual RGBA channels for 4x atlas density:
var result = BmFont.Generate("font.ttf", new FontGeneratorOptions
{
Size = 16,
Characters = CharacterSet.Ascii,
ChannelPacking = true
});
Channel packing cannot be combined with color font rendering.
Font Subsetting
By default, only the TTF tables needed for your requested codepoints are fully parsed, keeping memory usage low for large CJK or Unicode fonts.
Autofit Texture
Automatically find the smallest power-of-two texture size that fits all glyphs on a single page:
var result = BmFont.Generate("font.ttf", new FontGeneratorOptions
{
Size = 24,
Characters = CharacterSet.Ascii,
AutofitTexture = true
});
Super Sampling
Rasterize at 2x-4x resolution and downscale for smoother edges:
var result = BmFont.Generate("font.ttf", new FontGeneratorOptions
{
Size = 32,
Characters = CharacterSet.Ascii,
SuperSampleLevel = 2
});
Texture Formats
var result = BmFont.Generate("font.ttf", new FontGeneratorOptions
{
Size = 32,
TextureFormat = TextureFormat.Tga // Also: TextureFormat.Png, TextureFormat.Dds
});
How KernSmith Compares
See COMPARISON.md for a detailed feature comparison with BMFont, Hiero, msdf-atlas-gen, and other bitmap font generators.
License
MIT. See LICENSE for details.
Third-Party Licenses
KernSmith bundles and links against third-party components, listed with their licenses and required attributions in THIRD-PARTY-NOTICES.md. Notably, the FreeType rasterizer backend (FreeType 2.13.2 via FreeTypeSharp) and the synthetic-bold (embolden) port in the StbTrueType backend use FreeType under the FreeType License (FTL).
Portions of this software are copyright © 2023 The FreeType Project (www.freetype.org). All rights reserved.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- StbImageSharp (>= 2.30.15)
- StbImageWriteSharp (>= 1.16.7)
-
net8.0
- StbImageSharp (>= 2.30.15)
- StbImageWriteSharp (>= 1.16.7)
NuGet packages (5)
Showing the top 5 NuGet packages that depend on KernSmith:
| Package | Downloads |
|---|---|
|
KernSmith.Rasterizers.FreeType
FreeType rasterizer backend for KernSmith. Cross-platform. Supports color fonts, variable fonts, SDF, and outline effects. |
|
|
KernSmith.GumCommon
Bridges KernSmith bitmap font generation with Gum's BmfcSave font descriptor. Provides shared mapping logic used by platform-specific packages (KernSmith.MonoGameGum, KernSmith.KniGum, etc.) to generate fonts at runtime. |
|
|
KernSmith.Rasterizers.Gdi
GDI rasterizer backend for KernSmith. Windows-only. Matches BMFont output for pixel-perfect parity. |
|
|
KernSmith.Rasterizers.DirectWrite.TerraFX
DirectWrite rasterizer backend for KernSmith using TerraFX.Interop.Windows. Windows-only. Supports color fonts and variable fonts. |
|
|
KernSmith.Rasterizers.StbTrueType
StbTrueType rasterizer backend for KernSmith. Pure C#, cross-platform. No native dependencies. Ideal for Blazor WASM, iOS AOT, and serverless. |
GitHub repositories (1)
Showing the top 1 popular GitHub repositories that depend on KernSmith:
| Repository | Stars |
|---|---|
|
vchelaru/Gum
Flexible layout tool for creating UI on any platform
|
| Version | Downloads | Last Updated |
|---|---|---|
| 0.15.0 | 82 | 6/7/2026 |
| 0.14.0 | 249 | 6/4/2026 |
| 0.13.0 | 4,806 | 5/5/2026 |
| 0.12.4 | 3,142 | 4/10/2026 |
| 0.12.3 | 5,486 | 4/6/2026 |
| 0.12.2 | 4,100 | 4/3/2026 |
| 0.12.1 | 4,198 | 4/2/2026 |
| 0.12.0 | 4,292 | 4/2/2026 |
| 0.11.0 | 4,462 | 4/1/2026 |
| 0.10.4 | 4,516 | 3/31/2026 |
| 0.10.3 | 4,508 | 3/31/2026 |
| 0.10.2 | 4,520 | 3/29/2026 |
| 0.10.1 | 4,512 | 3/29/2026 |
| 0.9.6 | 4,477 | 3/28/2026 |
| 0.9.5 | 6,095 | 3/23/2026 |
| 0.9.4 | 5,994 | 3/23/2026 |
| 0.9.3 | 5,997 | 3/23/2026 |
See CHANGELOG.md