Skip to content

PDF Page Rendering

Background

Several pipeline stages (OpenAI Vision, MathPix per-page, equation audit, segmented pipeline) need rasterised PNG images of individual PDF pages before sending them to external APIs.

Previously this was done with unpdf’s renderPageAsImage, which internally used @napi-rs/canvas — a native Node.js addon that ships .node binaries. This works fine in Node but is incompatible with Cloudflare Workers: esbuild cannot bundle native addons, and the Workers runtime has no dlopen.

Current approach: Browser Rendering

We now use the Cloudflare Browser Rendering binding (Puppeteer-compatible headless Chromium). The utility at workers/api/src/utils/pdf-to-png.ts:

  1. Encodes the single-page PDF as base64.
  2. Launches a headless browser via the existing BrowserProvider interface.
  3. Loads an HTML page that uses pdf.js (from cdnjs.cloudflare.com) to paint the PDF onto a <canvas>.
  4. Takes a full-page PNG screenshot.
  5. Returns the PNG bytes.

Trade-offs

@napi-rs/canvasBrowser Rendering
RuntimeNode.js onlyCloudflare Workers
Latency~200 ms per page~1–3 s per page (cold browser)
FidelityGood (Cairo-based)Excellent (full Chromium renderer)
DependencyNative .node binaryWorkers binding + pdf.js CDN
ScaleLimited by single processCloudflare-managed browser pool

Browser Rendering adds latency per page but gives higher fidelity (Chromium renders fonts, SVGs, and complex PDF features more accurately than Cairo). The browser pool is managed by Cloudflare so there is no server to maintain.

Future options

  • unpdf Workers support: The unpdf library may add a Workers-compatible canvas backend in the future. Monitor https://github.com/nicolo-ribaudo/unpdf.
  • External rendering service: A dedicated microservice running Node.js with @napi-rs/canvas or Puppeteer could serve PNG renders via HTTP, decoupling the dependency from the Workers runtime.
  • Pre-rendered images: For known documents, pages could be pre-rendered at upload time and stored in R2, avoiding runtime rendering entirely.