Futhark
A high-performance text rendering library for Odin. Parses TrueType (.ttf), OpenType/CFF (.otf), and variable fonts with bitmap rasterization, SDF, MSDF, GSUB shaping, and text layout — all in pure Odin with zero dependencies.
Feature Comparison
| Feature |
Futhark |
stb_truetype |
fontdue |
msdfgen |
FreeType |
| TrueType outlines |
✓ |
✓ |
✓ |
— |
✓ |
| CFF/OTF outlines |
✓ |
✓ |
✓ |
— |
✓ |
| Bitmap rasterization |
✓ |
✓ |
✓ |
— |
✓ |
| SDF generation |
✓ |
✓ |
— |
✓ |
— |
| MSDF generation |
✓ |
— |
— |
✓ |
— |
| MTSDF generation |
✓ |
— |
— |
✓ |
— |
| GSUB substitution |
✓ |
— |
— |
— |
— |
| GPOS kerning |
✓ |
partial |
✓ |
— |
✓ |
| Text layout |
✓ |
— |
✓ |
— |
— |
| Variable fonts |
✓ |
— |
— |
— |
✓ |
| COLR/CPAL color |
✓ |
— |
— |
— |
✓ |
| Autohinting |
✓ |
— |
— |
— |
✓ |
| Font atlas packing |
✓ |
✓ |
— |
✓ |
— |
| OS/2 metadata |
✓ |
— |
— |
— |
✓ |
| Glyph names |
✓ |
— |
— |
— |
✓ |
| TrueType hinting VM |
— |
— |
— |
— |
✓ |
| Zero dependencies |
✓ |
✓ |
✓ |
— |
— |
Quick Start
import tt "futhark"
// Load a font
font: tt.Font_Info
tt.init_font(&font, raw_data(font_data), 0)
scale := tt.scale_for_pixel_height(&font, 24.0)
// Render a glyph to bitmap
w, h, xoff, yoff: i32
bitmap := tt.get_codepoint_bitmap(&font, scale, scale, 'A', &w, &h, &xoff, &yoff)
defer tt.free_bitmap(bitmap)
// Lay out a string (with kerning + GSUB)
result := tt.layout_text(&font, "Hello!", {
scale = scale, max_width = 400, wrap = .Word, apply_gsub = true,
})
defer delete(result.glyphs)
// Generate MSDF for GPU text
msdf := tt.get_codepoint_msdf(&font, scale, 'A', 4, 128, 32.0, &w, &h, &xoff, &yoff)
defer tt.free_msdf(msdf)
// Variable font at weight=700
coords: tt.Var_Coords
tags := [?]string{"wght"}
vals := [?]f32{700}
tt.set_variation(&font, &coords, tags[:], vals[:])
bitmap_bold := tt.get_glyph_bitmap_var(&font, scale, scale, glyph, &coords, &w, &h, &xoff, &yoff)
API Overview
Core — Font Loading
| Procedure |
Purpose |
init_font(info, data, offset) |
Initialize a font from raw bytes |
scale_for_pixel_height(info, px) |
Get scale factor for a target pixel height |
find_glyph_index(info, codepoint) |
Map Unicode codepoint → glyph index |
Rendering — Bitmaps
| Procedure |
Purpose |
get_codepoint_bitmap(info, sx, sy, cp, ...) |
Render a glyph to a new grayscale bitmap |
make_codepoint_bitmap(info, buf, w, h, stride, sx, sy, cp) |
Render into an existing buffer |
get_glyph_bitmap_hinted(info, sx, sy, gi, mode, ...) |
Render with autohinting |
get_glyph_bitmap_var(info, sx, sy, gi, coords, ...) |
Render at specific variation coordinates |
free_bitmap(bmp) |
Free a rendered bitmap |
Rendering — Distance Fields
| Procedure |
Purpose |
get_codepoint_sdf(info, scale, cp, pad, ...) |
Single-channel signed distance field |
get_codepoint_msdf(info, scale, cp, pad, ...) |
Multi-channel SDF (3 channels, sharp corners) |
get_codepoint_mtsdf(info, scale, cp, pad, ...) |
Multi-channel + true distance (4 channels) |
Text — Layout & Shaping
| Procedure |
Purpose |
layout_text(info, text, settings) |
Position glyphs with wrapping, alignment, kerning |
apply_gsub_default(info, glyphs) |
Apply default GSUB substitutions |
apply_gsub_feature(info, tag, glyphs) |
Apply a specific feature (e.g. "liga") |
get_codepoint_kern_advance(info, cp1, cp2) |
Get kerning between two codepoints |
Metrics
| Procedure |
Purpose |
get_font_vmetrics(info, &asc, &desc, &gap) |
Ascent, descent, line gap |
get_codepoint_hmetrics(info, cp, &adv, &lsb) |
Advance width, left side bearing |
get_codepoint_box(info, cp, &x0, &y0, &x1, &y1) |
Glyph bounding box |
get_os2_metrics(info, &os2) |
Weight, width, x-height, cap-height |
get_glyph_name(info, gi) |
PostScript glyph name |
Variable Fonts
| Procedure |
Purpose |
is_variable_font(info) |
Check if font has variation axes |
get_variation_axes(info, axes) |
Enumerate axes (weight, width, etc.) |
set_variation(info, coords, tags, values) |
Set axis values with avar remapping |
get_glyph_hmetrics_var(info, gi, coords, &adv, &lsb) |
Metrics at specific axes |
Atlas Packing
| Procedure |
Purpose |
pack_begin(ctx, pixels, w, h, stride, pad) |
Start packing glyphs into atlas |
pack_font_ranges(ctx, data, index, ranges, n) |
Pack character ranges |
pack_end(ctx) |
Finish packing |
get_packed_quad(chardata, w, h, idx, &x, &y, &q, align) |
Get UV coords for rendering |
Performance
Benchmarked against stb_truetype (C), fontdue (Rust), and msdfgen (C++) on AdwaitaSans-Regular.ttf:
| Operation |
stb (C) |
Futhark |
fontdue (Rust) |
msdfgen (C++) |
| init |
46 ns |
48 ns |
11M ns |
— |
| cmap lookup |
10.8 ns |
5.5 ns |
2.2 ns |
— |
| glyph shape |
59 ns |
6 ns |
— |
1,074 ns |
| bitmap |
1,146 ns |
2,669 ns |
941 ns |
— |
| SDF |
76,862 ns |
67,684 ns |
— |
106,242 ns |
| MSDF |
— |
242,689 ns |
— |
803,245 ns |
| kerning |
64 ns |
19 ns |
4.2 ns |
— |
| var bitmap |
— |
2,867 ns |
— |
— |
Testing
Tests and benchmarks are on the testing branch:
git checkout testing
./tests/run_tests.sh # Oracle comparison vs stb_truetype
./tests/bench/run_bench.sh # Four-way performance benchmark
./tests/correctness/run_correctness.sh # Pixel-level comparison vs FreeType
License
Sharkk Minimal License — see license.md