Series: Building PdfWord — a free, no-backend PDF tools site (Part 14)
The PDF editor is the tool I'm most embarrassed about — three full rewrites, each one abandoned for a different reason. Version 1 was a weekend hack. Version 2 was an over-engineered disaster. Version 3 is what ships today: click existing text to replace it in place, add new text, draw shapes, highlight, whiteout — all in the browser.
Here's the autopsy of all three attempts, and why the third one survived.
Try it: Edit PDF
Attempt 1: the overlay hack (abandoned: fonts)
The naive approach: render each page with pdf.js, let the user type in an HTML overlay positioned over the page, then stamp the text into the PDF with pdf-lib at the same coordinates.
It worked — for about ten minutes. Then I tested on a real document and discovered the coordinate systems don't line up the way you'd hope. PDF y-axis points up, canvas y points down, and the scale factor between the rendered canvas and PDF points introduces rounding drift. Text placed "exactly" over the original sat 2–3 pixels off — invisible in the editor, glaring in the downloaded PDF.
Worse: the replacement text was always Helvetica. Put Helvetica next to the document's original serif body text and it looks like a ransom note. I shipped it anyway, got exactly one user email ("the text doesn't match"), and killed it that night.
Attempt 2: the canvas-everything rewrite (abandoned: complexity)
Attempt 2's thesis: if coordinates are the problem, eliminate the coordinate problem. Render everything — original page AND edits — on one canvas, then convert the canvas to a PDF page image.
Technically it worked perfectly. The output looked pixel-identical to the editor. But every page became a full-page image: a 2MB text PDF ballooned into a 40MB image-PDF. Text was no longer selectable. File size exploded. I'd solved the visual problem by destroying everything a PDF is for.
The lesson was expensive in the way only a week of wasted work can be: the clever solution that violates the format's purpose is not a solution. A PDF editor that outputs unsearchable images is a screenshot tool with extra steps.
Attempt 3: text-aware in-place editing (shipped)
The final design treats the PDF as what it is — positioned text — and works with it:
-
Read the real text positions. pdf.js
getTextContent()gives every fragment's exact coordinates and font size. I render invisible click targets over the actual text runs — you're clicking the real text, not an approximation. - Replace in place, matching the original. When you edit a text run, the replacement goes into the PDF at the exact same coordinates, same font size, same color. It's still Helvetica (the WinAnsi font wall from Part 12 applies), but size and position match — which is what makes edits undetectable in practice.
- Edits are a separate layer. New text, shapes, highlights, and whiteout go on top of the original content stream rather than rewriting it. Undo is just "remove the top layer." The original document is never mutated until you hit download.
// The core insight: edit AT the text's real position, not an overlay guess
const items = await page.getTextContent();
items.items.forEach(it => {
const [x, y] = [it.transform[4], it.transform[5]];
// click target at the text's actual PDF coordinates
makeEditable(x, y, it.str, it.transform[0]); // size from transform
});
The whiteout tool deserves a mention: it paints a rectangle in the page's background color over the original text, then you type new text on top. The old text is still technically in the file (it's covered, not deleted — I say this plainly in the guide), which is fine for fixing a typo on a draft and not fine for redacting secrets. Honesty about that distinction matters.
What the three attempts taught me
- Attempt 1 failed on fidelity. Close enough is not enough when the output is a document someone sends to a client.
- Attempt 2 failed on purpose. A technically perfect solution to the wrong problem.
- Attempt 3 works because it respects the format. Positioned text in, positioned text out, edits as a layer.
The meta-lesson: the first version teaches you what the problem actually is. The second version teaches you what the constraints are. The third version is the product. Budget for three.
Try it: Edit PDF — click any text and replace it in place.
How many rewrites did your hardest feature take? And was the second one always the over-engineered one, or is that just me?
Top comments (0)