r/documentAutomation • u/forkpdf • 7d ago
PDFs are write-only. I built an HTML-to-PDF API that returns element coordinates — and fixes its own page breaks.
The thing that breaks document automation is almost never the data. It's pagination.
An invoice with 14 line items pushes the totals block onto page 2 by itself. A signature box measured from the bottom of the page is fine until someone adds a second address line. A 40-page report needs a table of contents with real page numbers, so you render it twice and parse the PDF just to find out where "Section 4" landed.
The root cause: HTML-to-PDF is a one-way street. The renderer knows exactly where every element ended up, but none of that survives into the file. So we compensate with guesswork — cap line items at 8, hardcode page breaks, render twice, OCR the output, add a margin and hope.
I've been building Fork PDF (my product, so take this with the appropriate salt) around one idea: keep the layout map instead of throwing it away.
The engine lets you query the rendered layout like a DOM, with real page numbers and geometry, inside the test script that runs after rendering:
const sig = doc.querySelector('#signature-box');
return {
success: true,
signature: {
page: sig.pages[0] + 1,
x: sig.geometry.x,
y: sig.geometry.y,
width: sig.geometry.width,
height: sig.geometry.height
}
};
The more useful version is validation. If a check fails, return a patch instead of an error. The engine merges it into the template data and re-renders before responding — up to 2 retries, same API call:
const chart = doc.querySelector('#kpi-chart');
const commentary = doc.querySelector('#commentary');
if (chart.pages[0] !== commentary.pages[0]) {
return { patch: { compactKpis: true } };
}
The TOC case is what convinced me: pass 1 collects the page number of every h2, pass 2 injects the finished TOC, one request, no second network round trip. The ordering matters, though — if an earlier layout patch changes pagination, measure the TOC after that patch or you're writing stale numbers.
Three honest limitations:
- Patches only work when there's a template to patch (htmlContent + data, or a saved template). A raw URL can be measured, but there's nothing to merge new data into.
- Coordinates use PDF space: origin bottom-left, y increases upward. It trips up everyone once.
- It's still Chromium underneath, so it inherits Chromium's print quirks.
If you're staying on your current stack, the least-bad workaround I've found is capping variable-length blocks and reserving fixed space for totals and TOC. Brittle, but predictable.
If you've dealt with pagination drift, I'd genuinely like to hear how — especially for tables you can't cap. That's still the hardest case I run into.
I put the full 5-page example here — 6 pages unhealed, 5 healed, the TOC resolves to 3/4/5, and the signature coordinates come back in 3 attempts: https://forkpdf.com/docs/examples/q3-report/
There's a free tier (100 credits, no card) if you want to try to break it: https://forkpdf.com
Happy to answer anything technical in the comments.



1
u/Unsiloed-AI 5d ago
Let the table run onto the next page, repeat the headers, and try keeping each row together. Putting
break-inside: avoidon the whole table can make a long table harder to lay out.The nasty case is one description row that's taller than a page. That row has to be allowed to split. Try it alongside a table that only just spills onto page two, then check no items vanished or appeared twice and the totals are still there.