DEV Community

hao jia
hao jia

Posted on

pdf.js render() resolves fine on a huge page, but the canvas reads back transparent

A failed render ought to look like a failure: a rejected promise, an exception, something in the console. With pdf.js on an oversized page it does not. The promise resolves, the code after await runs, and the canvas you hand to the next step is empty. All numbers below come from one machine (Apple M4, desktop Chromium 149.0.7827.55, pdfjs-dist 4.4.168), single runs, on a blank vector page I generated myself. Other browsers and phones have different limits, and I did not test them.

What the render call reported

The page was 14400 × 14400 pt. I rendered it at four scales and read pixels back from the canvas afterwards. At scale 0.5 (7200 × 7200) and scale 1 (14400 × 14400) the top-middle pixel was opaque white, as expected. At scale 1.1 the canvas was 15840 × 15840 and still fine, with the page's border colour showing up at the edge.

At scale 1.3333333 the canvas would be 19199 × 19199, an area of 368,601,601 pixels. render().promise resolved after about 1 ms, no error was thrown, and the pixels I read back were 0,0,0,0. The earlier scales took 54, 167 and 195 ms. A 1 ms render of a page that large should have looked suspicious, but nothing in the API told me so.

The canvas itself behaves the same way

To see whether pdf.js was involved at all, I took it out. A bare canvas was filled with a small red square and one pixel was read back. Then I asked for toDataURL() on the ones that failed. This is the complete output of that probe:

Chromium 149.0.7827.55
16384x16384 thrown=no readback=221,0,0,255 dataURL=skipped
16385x16384 thrown=no readback=0,0,0,0 dataURL=data:,
20000x13421 thrown=no readback=221,0,0,255 dataURL=skipped
19199x19199 thrown=no readback=0,0,0,0 dataURL=data:,
65536x1 thrown=no readback=0,0,0,0 dataURL=data:,
Enter fullscreen mode Exit fullscreen mode

So the line sits between 16384 × 16384 (area 268,435,456, works) and 16385 × 16384 (fails). It is not a per-side limit: 20000 × 13421 has an area of 268,420,000 and worked. 65536 × 1 failed on its own, so a width limit exists too, and I only know it is somewhere above 32768, because 32768 × 1 worked in an earlier run. In every failing case getContext('2d') still returned a context and fillRect threw nothing.

My reading, which is an inference and not something I verified in the Chromium source: the browser cannot allocate the backing bitmap, so every drawing call goes to a surface that does not exist. That would explain why pdf.js has nothing to complain about. From its side, it issued all its draw calls and none of them raised.

The check I added

Because the failure is silent, the promise cannot be the success signal. pdf.js paints a white page background by default, so an alpha of 0 in the corner can only mean nothing was drawn. A page that is meant to have a transparent background would break this check, so it only fits the default case.

async function renderChecked(page, viewport, canvas) {
  await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;
  const alpha = canvas.getContext('2d').getImageData(0, 0, 1, 1).data[3];
  if (alpha === 0) throw new Error(`blank canvas ${canvas.width}x${canvas.height}`);
}
Enter fullscreen mode Exit fullscreen mode

Reading one pixel is a small call (I did not time it), and toDataURL() returning data:, works as a second signal if you are about to encode the canvas anyway. Neither replaces a size check before you create the canvas. The readback only tells you that it already went wrong, which is enough to avoid uploading or caching an empty image.

Where I would not rely on this

I tested a single blank vector page and a bare canvas. I did not test real complex pages, other desktop browsers, or any phone. If your pipeline sends rendered pages to a server, a silent failure also matters for compliance: an empty image that looks like a success could end up archived as if it were complete.

For PDF to image on the desktop side without writing any of this, ImgIng https://imging.ai/ has a PDF to image page that runs in a desktop browser with 144, 216 and 300 DPI presets. Across 18 runs I captured, I saw only GET requests and no request carrying the file. Nothing above says anything about how it handles oversized pages.

Top comments (0)