Engineering Journal
Pdf Processor
Pdf Processor

Pdfium Heuristics Port Hottake

2026-07-24

Port the Invariant, Not the Code

TLDR: Most cross-library ports fail because people transliterate loops instead of transplanting invariants. A C++/PDFium math reconstructor moved into a pdfjs pipeline in an afternoon — not because the code translated cleanly (it didn't, the granularity is completely different), but because the thing worth porting was never the code. It was six numbers.


The Granularity Was Incompatible. It Didn't Matter.

pdf_md reads PDFs through PDFium and gets one record per glyph: exact ink bounds, per-character. Our pipeline reads through pdfjs and gets runs — "1 + " arrives as one item with one x-position and an aggregate width. A line-by-line translation of pdf_md's math_to_latex is impossible; half the loops iterate over data we don't have.

What we do have is the ability to approximate the missing grain: explode runs into atoms, interpolate x-positions proportionally. That interpolation is wrong by a few percent per character. And the port works anyway — first-shot correct on the fraction, the radical, the stretched parens.

Why? Because every decision in the algorithm compares a position against a threshold like 0.34 × fontSize. The interpolation error is an order of magnitude below the decision margin. The algorithm was never about the glyph records. It was about the thresholds.

Thresholds Encode Typography, Not Libraries

Look at what actually got ported:

None of these numbers know anything about PDFium, C++, or per-glyph records. They describe how human typesetters lay out mathematics — conventions that have been stable since Knuth, and mostly since Monotype. That's why they survive a data-model change untouched. The loops, the memory layout, the iteration order — all of that is disposable scaffolding around the invariants.

This is the test for whether a port will be cheap: can you write down what the source code knows in a dozen sentences that never mention its data structures? If yes, the port is a rewrite against those sentences. If no, you don't understand the source yet, and transliterating it will faithfully reproduce your lack of understanding.

Where Ports Actually Break

The two bugs in this port are instructive because both were places where the source's assumptions about its input leaked through disguised as algorithm:

The trailing-script loop consumed every small off-axis atom after an operand. Correct for PDFium's stream, where overset annotations rarely surface that way. Wrong for ours, where "maps to" drawn over an arrow arrives as a trailing run and got eaten as a superscript. The loop condition wasn't algorithm — it was an unstated claim about what the input could contain.

The fragment-absorption window used ±2pt. Fine at one font size, wrong the moment an 8pt exponent overhangs a paren by 4pt. A constant where an invariant (0.8 × glyph size) should have been.

Both fixes were the same move: find the buried assumption, replace it with a size-relative rule. Which is to say — the bugs were exactly the places where the original code had failed to be invariant-shaped, and the port's job was to finish the abstraction the source started.

The Uncomfortable Corollary

If the value is in the invariants, then reading a reference implementation and re-deriving its thresholds against your own data is not "not-invented-here syndrome." It's the only port that survives contact with a different substrate. The MIT license lets you copy the code; the code was never the asset.

Read this post in the full Engineering Journal →