TrueType hinting bytecode interpreter in pure Rust — a port of Apple's
TrueType hinting interpreter
(QuickDraw GX lineage, published in Swift, MIT) — with a step-observer
hook for debuggers, a font loader on read-fonts,
and a WASM binding used by the FontLab TTH Debugger.
fpgm / prep / glyf bytecode ──▶ typftth::Machine ──▶ hinted 26.6 points + touch flags
│
└── StepObserver: every instruction, full state
- No
unsafe, no panics on malformed fonts: every failure is a typed error. - Bit-exact with the Swift reference where it matters: SROUND/S45ROUND
match Apple's reference-interpreter table for all 256 parameters; the
~30 documented quirks of the original C++ engine are reproduced
(
docs/bincompat.md). no_std+alloccore (default-features = false).
| Crate | What |
|---|---|
typftth |
the interpreter, loader (read-fonts/skrifa), hinter, trace recorder |
typftth-cli |
typftth info / hint / sweep |
typftth-wasm |
wasm-bindgen surface (TthFont::record → debugger snapshot blob) |
use typftth::{hinter::Hinter, loader::HintFont, NoTrace};
let data = std::fs::read("font.ttf")?;
let font = HintFont::parse(&data, 0)?;
let coords = font.location(&[(*b"wght", 700.0)]); // normalized 2.14, avar-aware
let mut hinter = Hinter::new(font, 16, &coords)?; // runs fpgm + prep once
let glyph = hinter.hint_glyph(42, &mut NoTrace)?; // runs the glyph program
for (x, y) in glyph.points() { /* 26.6 pixels */ }Trace a run for a debugger:
use typftth::trace::Recorder;
let mut rec = Recorder::new(hinter.font.units_per_em as u32, 16, 42);
let glyph = hinter.hint_glyph(42, &mut rec)?;
rec.finish(&glyph.zone, glyph.error);
let blob = rec.to_blob(); // FontLab TTH Debugger snapshot v1Or implement StepObserver yourself — it sees the machine, the execution
state (program, ip, call depth), both zones and the opcode before every
instruction, and can stop the run.
cargo install typftth-cli
typftth info Font.ttf
typftth hint Font.ttf --gid 42 --ppem 16 --var wght=700 [--trace out.bin]
typftth sweep Font.ttf --ppems 9,12,16,24,48 # corpus health check- Not FreeType: this is the GX interpreter (
GETINFOversion 7 by default). It has no v35/v40 "backward compatibility" modes. WithHinterOptions::freetype(GetInfoProfile::freetype_v35(..))(CLI--getinfo 35|40) it reports FreeType's version and render flags and tolerates out-of-range CVT indices the way FreeType does, so fonts that gate hinting on the rasterizer version take the same branches; the arithmetic stays Apple's (e.g.IPtruncates where FreeType rounds). FreeType stays the oracle in the debugger; typftth is the second opinion. - Not a rasterizer: it produces hinted outlines. typf's
opixabackend rasterizes them. - Composite glyphs are flattened before hinting (component programs are not run individually yet).
Points are scaled like FreeType (FT_DivFix/FT_MulFix, round to nearest)
and CVT entries exactly like FreeType's tt_size_run_prep (2.14 rule by
default; the 2.13 scale >> 6 rule via CvtScaling::FreeType213), so
unhinted outlines and the initial CVT are identical to FreeType's and engine
comparisons only show interpreter differences. On a 27-font variable corpus
at 12/16/24 ppem, 99.2 % of simple glyphs hint to within 1/64 px of FreeType
2.14.3 v35; the rest differ through IP rounding. See docs/bincompat.md.
Apache-2.0. The interpreter is derived from Apple's MIT-licensed Swift
source (see NOTICE).