[Show] taetype: Pure Rust font engine with zero unsafe code

Fonts are untrusted binary data the moment they enter your app. Parsing them natively or in the browser can easily turn into a memory-safety headache or a server bandwidth drain.

I built taetype to fix that: a pure-Rust font engine with #![forbid(unsafe_code)] hard-enforced at the crate root. I also compiled the exact same engine to WebAssembly with taetype-wasm.

Whether you call it from Rust or TypeScript, you get the exact same glyph IDs, advance metrics, and subset bytes.

What it does

Instead of stitching together multiple single-purpose libraries, taetype handles the full font lifecycle:

  • Decode: TTF, OTF, and TTC font collections without dropping unknown tables.
  • Instance: Full OpenType Font Variations (fvar, gvar, avar formats 1/2, HVAR, VVAR, MVAR, cvar, and CFF2 charstring blends) turned into static instances at any axis position.
  • Subset: Shrink fonts down to only used glyphs, with full GSUB/GPOS/GDEF lookup rewriting so ligatures, kerning, and mark attachments keep working. COLR (v0/v1) and CPAL references carry through with dependency chasing.
  • Shape: Complex script shaping, kerning, and ligatures via rustybuzz, plus OpenType JSTF justification priority support.
  • Rasterize: Built-in, zero-dependency CPU coverage rasterizer (taerizer::cpu) for TrueType and CFF/CFF2 outlines.
  • Color & Emoji: COLR v0/v1 (gradients, transforms, layers), CPAL, CBDT/CBLC, sbix bitmap strikes, and SVG-in-OpenType documents.
  • Math Typesetting: Complete OpenType MATH table support (all 56 constants, per-glyph italics correction, and growable glyph variants).

Quick Code Examples

Native Rust (cargo add taetype)

use taetype::Font;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let font_bytes = std::fs::read("MyFont.ttf")?;
    let font = Font::from_bytes(&font_bytes)?;

    // Shape text using variable axes
    let shaped = font.shape("Hello, world", &[("wght", 400.0)], false)
        .expect("font must be shapeable");

    // Subset keeping only the used glyphs
    let subset = font.subset(&shaped.glyphs, &[("wght", 400.0)])?;
    // subset.ttf now holds your minimal font file

    Ok(())
}

Browser TypeScript (npm install taetype-wasm)

import init, { Font } from "taetype-wasm";

await init();

const fontBytes = new Uint8Array(await fetch("MyFont.ttf").then((r) => r.arrayBuffer()));

// Explicit resource management supported out of the box!
using font = Font.fromBytes(fontBytes);

const shaped = font.shape("Hello, world", { wght: 400 }, false);
if (!shaped) throw new Error("failed to shape text");

const subset = font.subset(shaped.glyphs, { wght: 400 });
// subset.ttf is a Uint8Array containing the subset font

shaped.free();
subset.free();

Verification & Reliability

Font parsers are historical magnets for memory exploits. To ensure reliability:

  • Compiler-enforced #![forbid(unsafe_code)] at the crate root.
  • 1,400+ unit and integration tests.
  • Differential testing against Python's fontTools for byte-for-byte accuracy.
  • Continuous fuzzing via cargo-fuzz (libFuzzer) across 6 targets, plus property testing via proptest.
  • Output validation using Chromium's opentype-sanitizer (OTS).

Links

Still working on it and refining it even further :slight_smile:

1 Like