bymsdfgen
bymsdfgen is a pure-Rust msdf generator — a pure-Rust alternative to msdfgen, the Chlumský original, with no C++ toolchain and no FFI.
A generator of (multi-channel) signed distance fields from font glyphs and vector shapes. It is not a mechanical transliteration of the C++: the geometry layer is redesigned around data-oriented principles and Rust's zero-cost abstractions to remove the original's main sources of overhead and entire classes of bugs.
This system was created as a core dependency for byard, a high-performance UI engine.
Empowering Next-Gen Rust UI
Modern graphic applications and UI engines like byard demand instantaneous font rasterization, high frame-rates, and dynamic vector rendering at arbitrary scales. Traditional CPU-based glyph rasterizers fail to maintain sharpness when scaled or rotated in 3D space, and caching arbitrary text sizes into traditional texture atlases quickly starves GPU VRAM.
Multi-channel Signed Distance Fields (MSDF) solve this by storing distance vectors across three color channels, allowing simple pixel shaders to reconstruct razor-sharp vector shapes at any zoom level with minimal memory overhead.
However, using the original C++ msdfgen in a modern Rust engine introduces severe friction:
The FFI & Toolchain Nightmare: Compiling C++ code with complex dependencies (like FreeType and libpng) for target platforms like Android, iOS, or WebAssembly (WASM) is notoriously fragile.
The WASM Speed Bump: For web-based instances of `byard, emscripten-compiled C++ bindings introduce boundary overhead and bloat the payload size.
Runtime Allocation Overhead: The C++ original aggressively allocates on the heap, risking garbage-collection-like stuttering on UI render loops.
bymsdfgen solves all of this. It brings a zero-FFI, compile-anywhere, memory-optimized
MSDF generator directly to the Rust community.
Why a rewrite instead of a port
| msdfgen (C++) | bymsdfgen (Rust) |
|---|---|
std::vector<EdgeSegment*> — pointer chasing, cache misses |
EdgeSegment is a Copy enum; contours store segments in flat, contiguous Vecs (struct-of-arrays) |
Virtual EdgeSegment hierarchy → vtable dispatch |
match on an enum → direct jumps, inlining, auto-vectorization |
Manual new/delete in EdgeHolder |
Ownership; no manual memory management, no clone()/delete |
Bare double for every coordinate space (silent space-mix bugs) |
Point<DesignSpace/EmSpace/ShapeSpace/PixelSpace> newtypes — mixing spaces is a compile error (zero runtime cost via PhantomData) |
OpenMP #pragma (race conditions still compile) |
rayon row-parallelism; data-race freedom proven at compile time |
| FreeType + libpng + TinyXML2 (native deps) | ttf-parser (zero-copy from &[u8]), png, hand-written SVG path parser — no FFI |
Hand-rolled CLI arg parsing, global state, _legacy API duplication |
clap; one clean API, no globals |
Numeric type is f64 throughout, matching the original for output parity.
Layout
crates/
bymsdfgen-core # geometry + distance fields. No I/O deps. Feature `parallel` (rayon, default on)
bymsdfgen-io # font (ttf-parser), image/raw output, shape-description, SVG path import
bymsdfgen-cli # the `bymsdfgen` binary
Build & test
CLI usage
bymsdfgen <mode> <input> [options]
Modes: sdf, psdf, msdf (default), mtsdf, metrics.
Inputs (exactly one): --font <ttf> --char <code>, --svg <file>,
--shapedesc <file>, --defineshape <def>, --stdin.
Character codes: decimal (65), hex (0x41), literal ('A'), or glyph index (g34).
Common options: -o/--output, --format, --dimensions W H, --range/--pxrange,
--scale, --translate X Y, --autoframe, --angle <a|aD>,
--edgecolors simple|inktrap|distance, --seed, --fillrule, --no-overlap,
--no-scanline, --printmetrics, --exportshape, --threads.
Example (mirrors the original's headline example):
Library
use ;
use ;
use edge_coloring_simple;
use Vector2;
use ;
let data = read?;
let font = from_slice.unwrap;
let mut shape = new;
font.load_glyph;
shape.normalize;
edge_coloring_simple;
let t = new;
let mut msdf: = new;
generate_msdf;
save_png?;
Parity notes
- The full MSDF error-correction pass (corner/edge protection, SDF-only and exact-distance artifact detection) is implemented.
- SVG import parses the last
<path>of a file (M/L/H/V/C/S/Q/T/A/Z). Skia-based self-intersection resolution (FULL_PREPROCESS) has no pure-Rust equivalent and is not included; winding/sign correction via the scanline pass is.