For most of the web’s history, “online PDF tool” meant one thing: upload your file to a server, wait, and download the result. The PDF format was complicated enough that the only realistic way to manipulate it was with a server-side library like pdf-lib, iText, or Ghostscript. That has changed. Two complementary JavaScript libraries — @libpdf/core for editing and pdfjs-dist for rendering — now let you merge, split, rotate, and view PDFs without any of the file’s bytes ever leaving your device.
@libpdf/core is a pure-JavaScript PDF editor. It can load an existing PDF into memory as a structured document, manipulate its pages (reorder, delete, rotate, copy between documents), embed images and text, and write a fresh PDF out as a Uint8Array. Because it operates on the parsed document tree rather than re-encoding page content, operations like merging or rotating are fast and lossless — the page streams are copied verbatim, so image quality, embedded fonts, and vector graphics remain pixel-identical to the source. This is why merging two 5 MB PDFs in FormFix takes a fraction of a second: no re-encoding is happening, just structural surgery.
For rendering — turning PDF pages into images you can see — pdfjs-dist is the standard. Originally written by Mozilla for Firefox’s built-in PDF viewer, it is a complete PDF interpreter that runs in any modern browser. It parses page content streams, handles fonts and color spaces, rasterises vector paths, and paints the result onto a <canvas> or OffscreenCanvas. FormFix uses it for pdf-to-image conversion: each page is rendered to an OffscreenCanvas at the requested scale, then exported as a PNG blob. Rendering happens in a Web Worker so the main thread stays free, and the resulting blobs can be zipped for download or previewed individually.
The key architectural decision is where the work runs. PDF parsing is CPU-bound; rendering a complex page at high DPI can take 100 ms or more. Doing this on the main thread would freeze the UI and produce a janky experience. FormFix solves this with Web Workers: the PDF bytes are transferred (not copied, where possible) into a worker, which does all the heavy lifting and posts back progress events. The worker can also be cancelled mid-task via an AbortSignal, which matters for large documents where a user might change their mind. OffscreenCanvas is the second piece of the puzzle — it lets the worker draw directly to a canvas without round-tripping through the DOM, which is dramatically faster than the older pattern of posting pixel arrays back to the main thread.
Lazy loading keeps the initial page weight low. The full pdfjs-dist library is around 400 KB minified; loading it on every page would blow the performance budget. FormFix therefore code-splits it: the homepage and image tools load zero PDF code, and only the PDF-specific routes (pdf-to-image, merge-pdf, split-pdf, etc.) pull in the relevant chunks. Even on those routes, the pdfjs-dist worker script is loaded only after a user actually selects a file. For users who never open a PDF tool, the cost is zero. The PDF worker file itself (pdf.worker.min.mjs) is served from the same origin and cached by the Service Worker, so subsequent visits cost nothing.
There are still some things browsers cannot do well with PDFs. Editing text inside an existing PDF — say, fixing a typo in a generated invoice — is theoretically possible but practically painful, because PDFs store text as positioned glyphs rather than editable runs. FormFillable forms (AcroForm) can be read and filled, but the UX is rough. Converting a PDF to an editable format (Word, Excel, HTML) requires either OCR plus heuristics or an external service. For the common cases, though — merge, split, rotate, render, image-to-PDF — the in-browser stack is now mature, fast, and private. The next time you need to combine three contracts into one file, there is no reason to send them anywhere.