- 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
- Fill color -- tint the glyph body with a custom RGBA fill color
- Gradient fill -- per-glyph vertical/angled gradients with midpoint, offset, scale, and cyclic control
- Drop shadow -- offset, color, opacity, and two-parameter blur control (kernel size + passes)
- Gamma correction -- adjust grayscale coverage to darken or lighten rendered glyphs
- SDF rendering -- signed distance field output with configurable spread for resolution-independent text
- Super sampling -- 2x-4x rasterization with box-filter downscale for smoother edges
- Advance adjustment -- global per-glyph horizontal advance offset for custom letter spacing
- 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
- Web/CDN font sourcing -- fetch WOFF fonts from font CDNs (Bunny Fonts, Google Fonts) via the
KernSmith.Fonts.Webpackage, ideal for WASM where there is no filesystem - 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 8.0 and .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
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 | Modern Windows rendering with natural/symmetric hinting. Color and variable fonts are not yet implemented; use FreeType for those. |
| 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)
.WithBackend(RasterizerBackend.Gdi)
.Build();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.
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.
See the API reference guide for BmFont, the fluent builder, FontGeneratorOptions, BmFontResult, BmFontModel, and the exception types.
dotnet add package KernSmith
Or via the NuGet Package Manager:
Install-Package KernSmith
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
});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");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
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();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();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 registrationsRegistering fonts explicitly also ensures cross-platform consistency -- the same fonts render identically everywhere regardless of what the OS has installed.
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}");
}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
});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 breakdownSeveral 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);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
});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
});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();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();// 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).
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) equivalentThe older ToString(), ToXml(), and ToBinary() methods still work and return the same data.
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);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();
}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).
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);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.
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).
var result = BmFont.Builder()
.WithFont("variable-font.ttf")
.WithSize(32)
.WithVariationAxis("wght", 700) // Bold weight
.WithVariationAxis("wdth", 75) // Condensed width
.Build();var result = BmFont.Generate("color-emoji.ttf", new FontGeneratorOptions
{
Size = 64,
ColorFont = true,
ColorPaletteIndex = 0 // CPAL palette index
});Replace a font glyph, or add a brand-new one for a codepoint the font does not contain, by
supplying raw pixel data. Pass either a prepared CustomGlyph or the raw bytes directly. The
data must be raw pixels (RGBA32 or Grayscale8) — not an encoded PNG.
// 8x8 solid white RGBA block to use for the private-use codepoint U+E000.
int w = 8, h = 8;
byte[] pixels = new byte[w * h * 4];
for (int i = 0; i < pixels.Length; i++) pixels[i] = 255;
var result = BmFont.Builder()
.WithFont("font.ttf")
.WithSize(32)
.WithCharacters(CharacterSet.Ascii)
// codepoint, width, height, pixels, format, optional xAdvance
.WithCustomGlyph(0xE000, w, h, pixels, PixelFormat.Rgba32, xAdvance: w)
.Build();The options-object equivalent uses the CustomGlyphs dictionary:
var options = new FontGeneratorOptions
{
Size = 32,
CustomGlyphs = new Dictionary<int, CustomGlyph>
{
[0xE000] = new CustomGlyph(w, h, pixels, PixelFormat.Rgba32, XAdvance: w)
}
};
var result = BmFont.Generate("font.ttf", options);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.
By default, only the TTF tables needed for your requested codepoints are fully parsed, keeping memory usage low for large CJK or Unicode fonts.
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
});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
});var result = BmFont.Generate("font.ttf", new FontGeneratorOptions
{
Size = 32,
TextureFormat = TextureFormat.Tga // Also: TextureFormat.Png, TextureFormat.Dds
});See COMPARISON.md for a detailed feature comparison with BMFont, Hiero, msdf-atlas-gen, and other bitmap font generators.
MIT. See LICENSE for details.
KernSmith is authored by Kaltinril (Jeremy Swartwood) — the author listed on every published NuGet package. The copyright holder of record is "KernSmith contributors", matching the LICENSE.
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.
