The PDF editor's edit-text mode is no longer a polite lie. Until this week, clicking a word and typing a replacement drew a white rectangle over the original glyphs and stamped new ones on top — the same trick most online "PDF editors" use. Saved files preserved the original byte stream underneath, the new text didn't match the original font, and anything wider than the original word overflowed adjacent text. Same problem Adobe charges $20/month to solve.
This week's wave swaps that cover-and-overlay hack out for a real text-edit engine built on pdfium-wasm — the same C++ library that powers Chromium's PDF viewer, compiled to run in your browser. Edits rewrite the underlying content stream. The original font, color, position, and matrix are preserved. Adjacent text shifts to make room. Long edits wrap at the right margin and push following paragraphs down. The 600 KB engine loads lazily on the first edit, never on page load.
What changed for users
- Click a word, type, save. That's it. The click lands a caret where you pressed — no more drag-rectangle picker that didn't quite know which characters you meant.
- Font, color, and position survive the edit. Edit "John Doe" → "Jane Smith" on a contract, the rendered text stays in the same Helvetica / Times / Calibri / Roboto / whatever, same color, same baseline. No more black text appearing where the original was navy.
- Adjacent text on the same line gets out of the way. Type a longer word into the middle of a line and the rest of the line shifts right. Same model as Acrobat Pro on a single-line edit. (Full paragraph reflow — wrap to next line + push following paragraphs down when an edit makes a paragraph taller — landed in the engine but doesn't reliably trigger on real-world PDFs yet. Tracked for v1.3.)
- CJK, Cyrillic, Greek, smart quotes now work. Old engine threw on any glyph the original font's subset didn't ship. New engine swaps in NotoSans (Latin / Cyrillic / Greek / Vietnamese superset) when the original font can't render your new characters. Sans-serif edits stay sans-serif; serif stays serif; bold stays bold; italic stays italic.
- Toast warning when reflow clips off the page. Acrobat clips silently. We tell you.
Editor UX polish
- Click-to-edit replaces drag-to-select in the edit-text mode. Single click on a text run picks that run; caret lands at the click position. Mirrors Acrobat exactly.
- Subtle dashed cyan underline appears under text you're editing in the preview. The preview can't load the PDF's actual embedded font into the browser without significant glue, so font family + metrics are an approximation — the underline marks the preview as "approximate". The saved file uses the real font.
- Preview now reads pdfjs's font family hint — a serif source PDF previews in serif, sans-serif in sans-serif, instead of always falling back to Helvetica.
- Cover-rect cleanup: the white box that hides the original glyphs is now sized to whichever is wider (original glyph extent vs new text), with descender padding so the dots from
g/p/y/; don't peek past the edge.
Engineering notes (skip if you don't care)
- pdfium-wasm (
@embedpdf/pdfium, MIT, ~2 MB gz) runs in a dedicated Web Worker, loaded via the project's existing wasm-cache (IndexedDB blob cache with SHA-256 verification). First edit pays the load; subsequent edits in the same session are instant.
- Same-line shift uses
FPDFPageObj_Transform translates on every text object whose pre-mutation top-Y matched the edited line.
- Wrap detection: when a shifted sibling's new right edge crosses
pageWidth − 36 pt (half-inch margin heuristic), it wraps to (leftMargin, baselineY − lineHeight). Line-height is detected from the nearest line below, falling back to fontHeight × 1.2.
- Font fallback chain: PDF base-14 fonts first (Helvetica / Times / Courier with bold + italic variants), NotoSans for everything else. Bytes lazy-fetched from
public/fonts/ only when an edit actually introduces glyphs the original subset can't ship.
- Bypassed
@embedpdf/engines and called the raw FPDF* / PDFiumExt_* symbols directly. The high-level wrapper hides the raw doc pointer behind a private cache, which we need exposed for FPDFText_SetText (per-object op). Direct calls also drop ~2 MB of unused engine surface (annotations, render, redact, image convert).
Known limits (tracked for v1.3)
- Paragraph reflow doesn't trigger reliably on real-world PDFs yet. The wrap-to-next-line + push-paragraphs-down logic ships and works on synthetic fixtures, but real PDFs hit edge cases in line detection that suppress the trigger. Single-line shift works in practice; multi-line reflow needs a rebuild against a real-PDF corpus.
- Edit-text font picker still only exposes Helvetica / Times / Courier in the toolbar. The save path embeds full NotoSans bold + italic variants when needed, but the picker doesn't let you choose them at edit time.
- Bundle hit: pdfium adds ~2 MB gz. Only loaded on the first edit, never on page load — sessions that just view PDFs pay nothing. Acceptable per the editor's role as the flagship tool.
- Calibri / Roboto / Open Sans fall back to NotoSans (Regular / Bold / Italic / BoldItalic, picked from the original font's weight + italic angle). Visually close for body copy, not pixel-perfect for headings.
- Mid-word break on overflow: only whole sibling text objects wrap. A single text object that spans the right margin still overflows. Workaround: edit it in two pieces.
- No cross-page overflow: content pushed past the page bottom clips. Toast warns; user fixes manually. Acrobat clips silently in the same scenario.
Files don't leave the device. The editor still runs entirely in-browser; pdfium adds no network calls except the one-time WASM + font fetches (same-origin, cached forever after).