Running a desktop-grade PDF engine in a browser tab
A quick tour of how we use WebAssembly to bring PDFium, the engine family behind Chromium's PDF viewer, into the RatioAI workspace.
By the RatioAI team
PDF looks simple from the outside. It's just a document, right? Under the hood it's a surprisingly deep format: object streams, cross-reference tables, embedded fonts, compressed images, incremental updates, encryption dictionaries, and decades of slightly-broken files produced by every tool imaginable.
Writing a PDF engine from scratch that handles all of that well is a multi-year project. So instead of reinventing it, we stood on the shoulders of one of the most battle-tested engines out there.
Meet PDFium
PDFium is the open-source PDF library that grew out of the Chromium project. It's written in C and C++, it's been hammered by billions of real-world documents, and it's very good at the hard parts: parsing messy files, rendering pages accurately, and handling fonts.
The catch, of course, is that it's native code. Browsers don't run C++. That's where WebAssembly comes in.
What WebAssembly actually gives us
WebAssembly, or WASM, is a compact binary format that browsers can execute at close to native speed. You compile C or C++ (or Rust, or Go) to a .wasm file, ship it like any other asset, and the browser runs it in the same sandbox as regular JavaScript.
For us, that means PDFium compiled to WASM runs right inside your tab. When you open a PDF in RatioAI, the page thumbnails and previews you see are rendered by that engine locally.
- Accurate rendering of real-world, messy PDFs.
- Same browser sandbox, so no extra permissions or plugins.
- Loaded once and cached, so later visits start faster.
Mixing engines on purpose
We don't use just one library for everything. PDFium is great at reading and rendering. For building and rewriting documents, things like merging, reordering pages, stamping watermarks or adding page numbers, we also use pdf-lib, a pure TypeScript library that is very pleasant to work with for structural edits. And we use PDF.js, Mozilla's renderer, where it fits best.
Picking the right engine per job sounds like extra complexity, and it is a bit. But it means each tool uses whatever is most reliable for that specific task rather than forcing one library to do everything.
Getting it fast enough
The first time we loaded the WASM build, it worked, but it wasn't snappy. A few things made a big difference. We preload the engine in the background as soon as the app opens, so it's warm by the time you drop a file. We render thumbnails lazily, only for pages you can actually see. And we're careful about copying big buffers between JavaScript and WASM memory, because that copying adds up quickly on large files.
The end result is that a long, image-heavy PDF feels responsive, even though everything is happening on your own device.