KernSmith 0.26.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package KernSmith --version 0.26.0
                    
NuGet\Install-Package KernSmith -Version 0.26.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="KernSmith" Version="0.26.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="KernSmith" Version="0.26.0" />
                    
Directory.Packages.props
<PackageReference Include="KernSmith" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add KernSmith --version 0.26.0
                    
#r "nuget: KernSmith, 0.26.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package KernSmith@0.26.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=KernSmith&version=0.26.0
                    
Install as a Cake Addin
#tool nuget:?package=KernSmith&version=0.26.0
                    
Install as a Cake Tool

<p align="center"> <img src="assets/social-preview.png" alt="KernSmith — .NET bitmap font generator" width="640"> </p>

<p align="center"> <a href="https://www.nuget.org/packages/KernSmith"><img src="https://img.shields.io/nuget/v/KernSmith.svg" alt="NuGet"></a> <a href="https://discord.gg/wfSetqqg23"><img src="https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white" alt="Discord"></a> </p>

<p align="center"> <a href="LICENSE">License</a> · <a href="CHANGELOG.md">Changelog</a> · <a href="samples/">Samples</a> · <a href="https://discord.gg/wfSetqqg23">Discord</a> </p>

Features

  • Font format support -- TTF, OTF, WOFF, WOFF2, and TTC collections (select a face with FaceIndex)
  • BMFont output -- text, XML, and binary .fnt formats with .png atlas 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, color, and shape (round, square, plus); on the experimental Native backend, optionally drawn from the vector outline (OutlineMode.Vector)
  • 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)
  • Atlas variants -- an unoffset dropshadow silhouette packed into the same shared atlas PNG as the primary font, with its own .fnt (see Atlas Variants)
  • Incremental glyph addition -- add glyphs to an existing atlas at runtime without moving anything already placed (see Incremental Glyph Addition)
  • Gamma correction -- adjust grayscale coverage to darken or lighten rendered glyphs (FreeType and Native)
  • Hint levels -- HintLevel picks how strongly outlines are grid-fitted (None, Light, Normal, Full), independent of the anti-alias mode; each backend lists the levels it implements and an unlisted level steps down to the nearest one it has
  • Fractional metrics -- FractionalMetrics writes each glyph's unrounded advance and left side bearing (and what hinting moved them by) as kernsmithchar lines in the .fnt metadata, in 1/64 px, for renderers that lay text out fractionally
  • Hinting baked into kerning -- BakeHintingIntoKerning folds the hinter's per-pair spacing corrections into the kerning pairs, so pairs the grid fit left touching are opened (Native at Normal, on by default there)
  • Same bytes on every host -- the Native backend's pixels and metrics are byte-identical across CLI, desktop, MonoGame/KNI, Blazor WASM, NativeAOT, x64 and arm64, proven by CI against a committed hash manifest
  • SDF rendering -- signed distance field output with configurable spread for resolution-independent text; multi-channel MSDF/MTSDF (SdfMode) on the experimental Native backend
  • Super sampling -- 2x-4x rasterization with box-filter downscale for smoother edges (LCD output always renders at the final size)
  • Anti-aliasing modes -- Grayscale (default), None, Light and Lcd subpixel output with a selectable LcdFilter and LcdSubpixelOrder (support varies by backend)
  • Advance adjustment -- global per-glyph horizontal advance offset for custom letter spacing
  • Design-unit glyph metrics -- FontInfo.DesignMetrics exposes per-glyph advance width and left side bearing in unscaled font design units, keyed by codepoint, for text-width measurement (e.g. word-wrap) without rasterizing
  • Variable fonts -- set variation axes (weight, width, slant, etc.)
  • Color fonts -- COLR/CPAL color glyphs with palette selection, and CBDT/sbix bitmap emoji
  • Symbol fonts -- Wingdings, Webdings, Symbol and other fonts with only a Windows symbol cmap work on every backend, BMFont-compatibly: each glyph is available at its U+F0xx code and at its byte decoded as Windows-1252 (see KernSmith.Font.SymbolEncoding)
  • Synthetic styles -- bold and italic on every backend; condensed/expanded width (WidthPercent) and a custom italic angle (ItalicAngle) on the experimental Native backend
  • 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, shadow)
  • 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(), and GetPremultipliedRgbaPixelData() for straight-alpha, single-channel, or premultiplied RGBA pixel access for GPU upload and custom rendering pipelines
  • Config formats -- read and write BMFont .bmfc and 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 .fnt files (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 WOFF2/WOFF fonts from font CDNs (Bunny Fonts, Google Fonts) via the KernSmith.Fonts.Web package, 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, Android, iOS, Mac Catalyst and Blazor WASM via .NET 8.0 and .NET 10.0 (see Platform Support)
  • Pluggable rasterizers -- swap rendering backends to match your platform and feature needs
  • Hardened font parsing -- core's font parser and the Native backend apply bounds validation and resource limits, so malformed font files fail with an exception instead of hanging or crashing. The StbTrueType backend also checks each font's structure against the OpenType spec and rejects a non-conforming font with a FontParsingException -- defense in depth only, so StbTrueType still needs trusted fonts. For untrusted fonts (user uploads, URLs from users, servers) use the Native or FreeType backend; StbTrueType is for trusted fonts only (see the backend table)

Rasterizer Backends

KernSmith supports pluggable rasterizer backends. Install at least one backend package to generate fonts.

Backend Platform Hinting Color Fonts Variable Fonts SDF Notes
FreeType<br>NuGet Windows, Linux x64, macOS, Android Yes (native bytecode + autohint fallback); hint levels None, Light, Full Yes Yes Yes Cross-platform, the most complete published backend (no MSDF, vector outlines, WidthPercent or ItalicAngle; COLR version 1 glyphs render as grayscale). Default choice unless you have a platform- or environment-specific reason to pick another backend.
GDI<br>NuGet Windows only Yes (native Windows hinting); hint level Full only No No No Matches BMFont reference output for pixel-perfect parity.
DirectWrite<br>NuGet Windows only Yes (natural/symmetric hinting); hint levels None, Light No No No Modern Windows rendering. Color and variable fonts are not yet implemented; use FreeType for those.
StbTrueType<br>NuGet Cross-platform No (hint level None only) No No Yes Pure C#, no native dependencies. Good for WASM and AOT. Trusted fonts only: the underlying stb_truetype library does no bounds checking, so a malformed font can crash the process -- use Native or FreeType for untrusted input. No hinting engine: AntiAliasMode.None keeps thin stems with dropout control, but they are not grid-fitted, so stem widths can vary at small sizes -- prefer AntiAliasMode.Grayscale or raise SuperSampleLevel for crisper small-size text.
Native<br>experimental, not on NuGet yet Cross-platform Own auto-hinter: Normal by default (both axes, fades out by 40 ppem), Light (y-only), None; no bytecode Yes (COLR v0 and v1, CBDT/sbix) Yes Yes KernSmith's own rasterizer -- pure C#, zero third-party dependencies. Renders TrueType (.ttf) and CFF/CFF2 (.otf) outlines, including variable instances of both. Synthetic bold/italic match FreeType's. The only backend with multi-channel SDF (SdfMode.Msdf/Mtsdf), vector outlines (OutlineMode.Vector), WidthPercent and ItalicAngle. Anti-aliasing None (with dropout control), Grayscale, Light and Lcd (RGB/BGR or vertical panels, selectable filter). No TrueType bytecode hinting or system fonts. Available today from the repository and the CLI (--rasterizer native); the package is not published, so it cannot be resolved from a NuGet-only build.

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

The experimental Native backend has no package yet -- reference src/KernSmith.Rasterizers.Native from a source build, or use the CLI's --rasterizer native.

Select a backend when generating:

var result = BmFont.Builder()
    .WithFont("font.ttf")
    .WithSize(32)
    .WithBackend(RasterizerBackend.Gdi)
    .Build();

Platform Support

Platform StbTrueType FreeType GDI DirectWrite Native (experimental)
Windows ✅ ✅ ✅ ✅ ✅
Linux x64 ✅ ✅ ❌ ❌ ✅
Linux arm64 ✅ ❌ ❌ ❌ ✅
macOS ✅ ✅ ❌ ❌ ✅
Android ✅ ✅ ¹ ❌ ❌ ✅
iOS ³ ✅ ❌ ² ❌ ❌ ✅
Mac Catalyst ✅ ❌ ❌ ❌ ✅
Blazor WASM ✅ ⁴ ❌ ❌ ❌ ✅ ⁴

✅ tested in CI or on a device/emulator · ⏳ expected to work, not yet verified · ❌ unsupported (no native library or Windows-only API)

  1. Android x86_64 (emulators, Chromebooks) needs KernSmith.Rasterizers.FreeType 0.23.0 or later, which carries the Android packaging fix (see CHANGELOG); earlier packages leak FreeTypeSharp's desktop Linux library into the APK and fail with DllNotFoundException. arm64 works in all versions.
  2. An iOS app that references KernSmith.Rasterizers.FreeType fails to link, even if FreeType is never used: FreeTypeSharp 3.1.0's static iOS libfreetype.a references libpng symbols (_png_create_read_struct and others) that nothing provides. Do not reference the FreeType package in iOS apps; use StbTrueType or Native. FreeTypeSharp has no native library for Linux arm64, Mac Catalyst, or WASM.
  3. iOS is verified on the iOS simulator in CI (Release, no JIT); a real-iPhone confirmation is tracked in phase 186.
  4. Blazor WASM is built and published in CI (the wasm-publish job) but the output is not run there. StbTrueType and Native were verified by running the published sample in headless Chrome (interpreted build; the AOT build is not yet run in a browser).

Mobile notes:

  • Register backends explicitly. Android Release builds are trimmed and iOS is always AOT-compiled, so reflection-based auto-discovery cannot be relied on -- call RasterizerFactory.Register(RasterizerBackend.StbTrueType, () => new StbTrueTypeRasterizer()) (and likewise for FreeType/Native) at startup. See Native AOT and trimming.
  • No system fonts. DefaultSystemFontProvider returns no fonts on Android, iOS and Mac Catalyst; pass font bytes from an embedded resource or app asset instead of a family name.
  • The evidence for this table is the platform smoke test, run per platform by the mobile-smoke.yml workflow.

Blazor WASM

KernSmith runs entirely client-side in Blazor WebAssembly using the StbTrueType or Native (experimental) backend. See the Blazor WASM sample for a working example.

// At startup: a published Blazor app is trimmed, so auto-discovery cannot be relied on
RasterizerFactory.Register(RasterizerBackend.StbTrueType, () => new StbTrueTypeRasterizer());

// Generate in-browser
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. StbTrueType is the recommended choice (pure C#, no native dependencies):

using KernSmith;
using KernSmith.Rasterizer;
using KernSmith.Rasterizers.StbTrueType;

// Register at startup; the direct constructor reference keeps the backend from being trimmed.
RasterizerFactory.Register(RasterizerBackend.StbTrueType, () => new StbTrueTypeRasterizer());

The same pattern works for FreeType and the experimental Native backend; FreeType additionally needs its native library on the target platform (see Platform Support).

See the rasterizers documentation for details.

See the API reference guide for BmFont, the fluent builder, FontGeneratorOptions, BmFontResult, BmFontModel, and the exception types.

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 synthetic
  • WithForceSyntheticBold() / WithForceSyntheticItalic() always apply synthetic styling, skipping native face lookup
  • When using a file path (not a system font), bold/italic is always synthetic -- WithBold() and WithForceSyntheticBold() 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.

Font Location Hints

If you already know where a system font lives on disk, HintFontLocation is a lighter-weight alternative to RegisterFont -- it skips OS-specific resolution (Windows registry, filename heuristics, full directory scan) for that family without loading the font's bytes into memory up front:

BmFont.HintFontLocation("Arial", @"C:\Windows\Fonts\arial.ttf");

var result = BmFont.GenerateFromSystem("Arial", new FontGeneratorOptions { Size = 32 });

The hint is validated the same way as any other cached/seeded entry (the file must exist and its parsed family name must match) before it's trusted, so a wrong hint just falls through to normal resolution. See System Font Resolution for the full resolution chain and diagnostics.

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
});

With a different corner shape (Round is the default; Square fills diagonals, Plus gives diamond-shaped corners):

var result = BmFont.Generate("font.ttf", new FontGeneratorOptions
{
    Size = 48,
    Characters = CharacterSet.Ascii,
    Outline = 3,
    OutlineShape = OutlineShape.Square
});

Outlines are a bitmap effect that works with every backend. OutlineMode = OutlineMode.Vector asks the backend to draw the ring from the glyph's vector outline instead (same width, color, shape and cell sizes, with a crisper edge and solid narrow counters); only the experimental Native backend does, the others keep the effect. It cannot be combined with SDF.

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 atlas pages in the generation's TextureFormat (.png, .tga or .dds) or custom AtlasEncoder, 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, benchmark-fonts, 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
});

COLR layers take their colors from the selected CPAL palette; CBDT/sbix bitmap emoji are drawn from the font's PNG strikes (FreeType returns them at the strike's own size, Native scales them to the requested size). See the backend table for which backend covers which format.

Custom Glyphs

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

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
});

AntiAlias = Lcd output is never super sampled: a backend that renders LCD gets the final size, because box-filtering the subpixel pattern would average it away.

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.

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.

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (8)

Showing the top 5 NuGet packages that depend on KernSmith:

Package Downloads
KernSmith.Rasterizers.FreeType

FreeType rasterizer backend for KernSmith. Runs on Windows, Linux x64, macOS, and Android (not iOS, where it fails to link). Renders color fonts (COLRv0/CPAL, CBDT, sbix; COLRv1 glyphs as grayscale), variable fonts, SDF, font hinting (bytecode with autohint fallback), and every anti-alias mode including Light and LCD.

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.StbTrueType

StbTrueType rasterizer backend for KernSmith. Pure C#, cross-platform. No native dependencies. Good for Blazor WASM and iOS AOT. For trusted fonts only: stb_truetype does no bounds checking, so use the Native or FreeType backend for untrusted fonts.

KernSmith.Rasterizers.DirectWrite.TerraFX

DirectWrite rasterizer backend for KernSmith using TerraFX.Interop.Windows. Windows-only. Loads system fonts and renders with ClearType-hinted grayscale antialiasing. Color and variable fonts are not yet supported.

KernSmith.Rasterizers.Gdi

GDI rasterizer backend for KernSmith. Windows-only. Matches BMFont output for pixel-perfect parity.

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.27.0 157 11/6/2026
0.26.0 391 11/2/2026
0.25.0 207 11/1/2026
0.24.0 9,206 10/18/2026
0.23.1 237 10/17/2026
0.23.0 1,350 10/14/2026
0.22.1 198 10/13/2026
0.22.0 237 9/10/2026
0.21.0 17,109 8/28/2026
0.20.0 1,769 8/5/2026
0.19.0 260 7/31/2026
0.18.2 15,444 7/28/2026
0.18.1 330 7/28/2026
0.18.0 260 7/27/2026
0.17.0 308 7/19/2026
0.16.0 4,197 7/12/2026
0.15.3 306 7/12/2026
0.15.2 384 6/29/2026
0.15.1 5,842 6/16/2026
0.15.0 1,154 6/7/2026
Loading failed

See CHANGELOG.md