Hot Take: PDF Image Extraction Is a Render Problem, Not a Decode Problem
TLDR
Treating PDF image extraction as a binary resource decoding problem ("how do I unpack embedded JPEG bytes?") is fundamentally flawed for browser applications. PDF images are paint commands (paintImageXObject) evaluated dynamically within Current Transformation Matrices (CTM). Render the page onto a high-resolution OffscreenCanvas inside Web Workers, then crop image bounding boxes from the composite canvas.
| Extraction Model | Paradigm | Web Worker Feasibility | Image Crop Quality |
|---|---|---|---|
| Binary Resource Decoding | Unpack raw embedded PDF streams | Fails (Unpopulated page.objs) | Fails (Lacks CTM scale/transform) |
| Canvas Render Intercept | Crop pixels from page.render() | Passes (OffscreenCanvasFactory) | 100% Sharp ($384\text{ DPI}$ crops) |
Technical problem statement: the fallacy of raw stream extraction
Standard PDF Processors attempt to fetch raw image bytes directly from page.objs. However, PDF.js populates page.objs exclusively during page.render() execution loops.
Querying page.objs synchronously prior to rendering yields un-resolved object promises:
$$\text{Error: Requesting object that is not resolved yet}$$
Technical solution: high-resolution render-and-crop architecture
Execute a high-resolution render pass ($4.0\times$ scale) inside a Web Worker, then crop image bounding boxes into Base64 PNG data URLs:
// High-Resolution Canvas Crop Architecture
export async function extractRenderedImageCrop(
pageCanvas,
bbox,
scaleRatio = 2.0,
) {
const sx = Math.max(0, Math.round(bbox.x * scaleRatio));
const sy = Math.max(0, Math.round(bbox.y * scaleRatio));
const sw = Math.min(Math.round(bbox.w * scaleRatio), pageCanvas.width - sx);
const sh = Math.min(Math.round(bbox.h * scaleRatio), pageCanvas.height - sy);
const cropCanvas = new OffscreenCanvas(sw, sh); cropCanvas .getContext("2d") .drawImage(pageCanvas, sx, sy, sw, sh, 0, 0, sw, sh);
const blob = await cropCanvas.convertToBlob({ type: "image/png" }); const arrayBuffer = await blob.arrayBuffer();
return data:image/png;base64,${arrayBufferToBase64(arrayBuffer)}; }
Rule of thumb: Treat PDF image extraction as a canvas rendering intercept problem rather than a binary resource decoding task.