Writing a Custom Rasterizer Backend
KernSmith's rasterizer system is pluggable. Any library that implements two interfaces — IRasterizer and IRasterizerCapabilities — and registers with RasterizerFactory works as a backend. This guide shows you how.
Quick Start
The fastest way to get started is to copy the example project:
cp -r samples/KernSmith.Rasterizer.Example MyRasterizer
This gives you a skeleton project with all required interfaces stubbed out. See the example's README for a step-by-step customization guide.
The Plugin Contract
Your backend implements two interfaces from KernSmith.Rasterizer:
IRasterizer
Required members (must implement):
| Member | Purpose |
|---|---|
Capabilities |
Returns your IRasterizerCapabilities instance |
LoadFont(ReadOnlyMemory<byte>, int) |
Loads a font from raw file bytes |
RasterizeGlyph(int, RasterOptions) |
Renders one glyph to a bitmap. Return null for missing glyphs |
RasterizeAll(IEnumerable<int>, RasterOptions) |
Renders multiple glyphs, skipping missing ones |
Dispose() |
Releases native resources |
Optional members (have default implementations — override only if your engine provides native support):
| Member | Default | Purpose |
|---|---|---|
LoadSystemFont(string) |
Throws NotSupportedException |
Load a system font by family name |
GetGlyphMetrics(int, RasterOptions) |
Returns null |
Get metrics without rasterizing |
GetFontMetrics(RasterOptions) |
Returns null |
Provide native font-wide metrics (ascent, descent, line height) |
GetKerningPairs(RasterOptions) |
Returns null |
Provide pre-scaled kerning pairs |
SetVariationAxes(IReadOnlyList<VariationAxis>, Dictionary<string, float>) |
No-op | Apply variable font axis values |
SelectColorPalette(int) |
No-op | Select a CPAL color palette |
When optional methods return null, KernSmith falls back to its built-in TTF table parsers.
IRasterizerCapabilities
Required members:
| Member | Type | Purpose |
|---|---|---|
SupportsColorFonts |
bool |
Can render COLR/CPAL/sbix/CBDT color glyphs |
SupportsVariableFonts |
bool |
Can apply fvar axis coordinates |
SupportsSdf |
bool |
Can produce signed distance field output |
SupportsOutlineStroke |
bool |
Can stroke glyph outlines |
SupportedAntiAliasModes |
IReadOnlyList<AntiAliasMode> |
Which AA modes are supported (None, Grayscale, Light, Lcd) |
Optional members (default to false):
| Member | Purpose |
|---|---|
HandlesOwnSizing |
Set to true if your engine handles point-to-pixel conversion internally |
SupportsSystemFonts |
Set to true if you implement LoadSystemFont() |
RasterizedGlyph Output
Each rasterized glyph must provide all 8 properties:
new RasterizedGlyph
{
Codepoint = codepoint,
GlyphIndex = glyphIndex, // Internal font glyph index
BitmapData = pixelBytes, // Grayscale (1 bpp) or RGBA (4 bpp)
Width = bitmapWidth,
Height = bitmapHeight,
Pitch = bytesPerRow, // May include row padding
Metrics = new GlyphMetrics(bearingX, bearingY, advance, width, height),
Format = PixelFormat.Grayscale8 // or PixelFormat.Rgba32
};
Registration
Backends register with RasterizerFactory via a [ModuleInitializer]. This runs automatically when your assembly loads — no manual wiring needed.
Since RasterizerBackend is an enum in the core assembly, third-party backends use a numeric cast. Pick a value of 100 or higher to avoid collisions with built-in backends (FreeType=0, Gdi=1, DirectWrite=2):
using System.Runtime.CompilerServices;
using KernSmith;
using KernSmith.Rasterizer;
namespace KernSmith.Rasterizers.MyRasterizer;
internal static class MyRegistration
{
#pragma warning disable CA2255
[ModuleInitializer]
internal static void Register()
{
RasterizerFactory.Register((RasterizerBackend)100, () => new MyRasterizer());
}
#pragma warning restore CA2255
}
Built-in backends are auto-discovered by RasterizerFactory on first access. For third-party backends, the [ModuleInitializer] fires when your assembly loads. If your assembly isn't loaded automatically (e.g., nothing in the app references a type from it), add a direct type reference or call RuntimeHelpers.RunModuleConstructor():
// Only needed if .NET doesn't load your assembly automatically
System.Runtime.CompilerServices.RuntimeHelpers.RunModuleConstructor(
typeof(KernSmith.Rasterizers.MyRasterizer.MyRasterizer).Module.ModuleHandle);
Packaging
Structure your NuGet package like this:
- Package ID:
KernSmith.Rasterizers.YourName - Dependency:
KernSmith(the core library) - TFM:
net10.0for cross-platform, ornet10.0-windows/net10.0-linuxif platform-specific
No special metapackage or framework reference is needed.
Testing
Validate your backend against FreeType reference output:
- Load a test font (e.g., Roboto-Regular.ttf) with both FreeType and your backend
- Rasterize the same character set at the same size
- Compare glyph metrics (advance, bearings) — small rounding differences (±1px) are normal
- Compare bitmap output visually or via pixel diff
Use RasterizerFactory.IsRegistered() to verify your module initializer fires correctly:
// Force assembly load (needed for third-party backends not in the auto-discovery list)
RuntimeHelpers.RunModuleConstructor(typeof(MyRasterizer).Module.ModuleHandle);
// Verify registration
Assert.True(RasterizerFactory.IsRegistered((RasterizerBackend)100));
// Create and use
using var rasterizer = RasterizerFactory.Create((RasterizerBackend)100);
Assert.NotNull(rasterizer.Capabilities);