TrueType hinting debugger

We needed to take a look at the TrueType instructions and made a small debugger. 
Have fun :)


https://studio.fontlab.com/tth-debugger/

Comments

  • Simon Cozens
    Simon Cozens Posts: 866
    This has been extremely useful to me in finding and diagnosing bugs in my autohinter. Thank you!
  • Yury Yarmola
    Yury Yarmola Posts: 36
    edited 10:51AM
    Simon, this tool was made for you ))
    I started it to check what's happening in Tilvisan hinting :)

    Then Adam extended it with "compare" feature, breakpoints, "jumps" and more.

    Let me know if we need to fix something or add any features, etc. 
  • Adam Twardoch
    Adam Twardoch Posts: 519
    edited 11:01AM

    The FontLab TTH Debugger is a browser-based debugger for TrueType hinting bytecode. If you’re a font engineer interested in TrueType hinting, this may be of interest to you. If “TrueType hinting” is not something that gets you excited — then you can probably ignore FontLab TTH Debugger.  

    You drop a TTF on the page at studio.fontlab.com/tth-debugger, pick a glyph and a size, and the tool runs the font's own instructions at one ppem, one variation location and one render mode: the font program (fpgm), which defines the functions, the control value program (prep), which runs once per size and location, and the selected glyph's program. 

    After every instruction it records the state of the interpreter. Nothing you open leaves your machine: the scaler is FreeType compiled to WebAssembly and runs in the page, and every font you open is stored in the browser's IndexedDB, so your font list survives a reload.

    You select a glyph by character, U+ code point, glyph name or index, set the size with the PPM control, and step. Step runs the highlighted instruction, Step over runs a CALL or LOOPCALL as one step and stops after it returns, Effect jumps to the next instruction that moves a point, and Run goes to the end of the program or to the next breakpoint, which you set by clicking an instruction's address in the listing. The tool replays a recording rather than a live interpreter, so stepping back is exact and the slider scrubs the whole program. 

    The listing, stack, CVT, storage and graphics state panels all show the state as it is before the highlighted instruction, so the highlighted line is what happens next. If the font carries VTT source in its TSI1 table, the VTT toggle shows that source for the current range instead of the disassembly. The glyph view draws the outline over the pixel grid and colours the points the last instruction moved. If you need to know which instruction moved a point, click the point and press [ or ]: a watch expression (p13, x13, t2, cvt8, st5, stack, rp, zp, pv, round) jumps to the next step at which that value changes, which is the fastest way through a program of several thousand steps. Two columns, A and B, are always on screen, and you pick the one property along which they differ: Font, Location, Glyph, Mode or PPM. Column B inherits everything else from column A, so the two columns cannot drift apart on what they are supposed to share.

    The reference engine is FreeType 2.14.3 built to WebAssembly, with a C wrapper that hangs off TT_ExecContext.instruction_trap and captures a snapshot after each instruction: stack, CVT, storage, graphics state, and the position of every point. fpgm and prep run to completion, the glyph program is stepped, and the UI scrubs the recorded blob. The interpreter version follows the render mode, v35 for black-and-white and grayscale and v40 for RGB and BGR subpixel, and FreeType also rasterizes both columns, so the two bitmap previews are comparable. The second engine, AP, is typftth, a Rust port of the opensource Apple TrueType interpreter (Swift rewrite of the old QuickDraw GX interpreter), which is then compiled to WebAssembly. 

    It emits the same snapshot format, so stepping, panels and captions work identically, and it runs under a FreeType-compatible host profile: it reports the same GETINFO version and flags as the FT mode it is paired with, and it tolerates out-of-range CVT indices the way FreeType does, so a font that gates hinting on the rasterizer version takes the same branch in both. The arithmetic stays Apple's: its scaling, its rounding, its IP truncation, its DELTA quirks. A difference you see between FT and AP is therefore engine semantics, not configuration. Around the WebAssembly sit a TypeScript disassembler (IP is a byte offset, immediates folded into the line), an sfnt reader for the glyph list and the unhinted outlines, and variation support that applies gvar and cvar deltas before hinting runs, so you debug a variable font at the instance you select.

    The tool needs glyf and loca: a CFF-flavoured OpenType font is rejected, because it carries no TrueType bytecode to step. Composite glyphs render in both engines, and AP steps the composite instructions it has, but FreeType's debug hook does not expose nested component programs as one stream, so FT shows a static hinted outline and bitmap for a composite. Recordings are kept whole, so a glyph with tens of thousands of steps costs load time and memory in proportion. 

    The scope is hinting: no shaping, no kerning, no layout, one glyph at a time. On engine agreement, scripts/diff-engines.mjs hints every simple glyph in a folder with both engines and writes tests/diff-report.md; on the internal variable-font corpus, 99.2 % of 9 028 glyph runs match within 1/64 px, and the rest differ through IP rounding, which Apple truncates and FreeType rounds. That difference stays in, because flattening it would hide the kind of divergence the two-engine view exists to show.