Stirling-Tools/Stirling-PDF · error · Error

PDFium: failed to open document (error ${err})

Error message

PDFium: failed to open document (error ${err})

What it means

`openRawDocument`/`openRawDocumentSafe` malloc a WASM buffer, copy bytes in, and call `FPDF_LoadMemDocument`. On failure (null pointer) it frees the buffer and reads `FPDF_GetLastError()`. PDFium error codes: 1=UNKNOWN, 2=FILE (read/access), 3=FORMAT (not a PDF or structurally invalid), 4=PASSWORD (wrong/missing password), 5=SECURITY (unsupported protection), 6=PAGE. This is the most diagnostic of the PDFium errors because it surfaces the numeric cause.

Source

Thrown at frontend/editor/src/core/services/pdfiumService.ts:311

/**
 * Load a PDF into PDFium memory and return the document pointer.
 * Caller MUST call `closeRawDocument(docPtr)` when finished.
 */
export async function openRawDocument(
  data: ArrayBuffer | Uint8Array,
  password?: string,
): Promise<number> {
  const m = await getPdfiumModule();
  const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
  const len = bytes.length;
  const ptr = m.pdfium.wasmExports.malloc(len);
  copyToWasmHeap(m, bytes, ptr);

  const docPtr = m.FPDF_LoadMemDocument(ptr, len, password ?? "");
  if (!docPtr) {
    m.pdfium.wasmExports.free(ptr);
    const err = m.FPDF_GetLastError();
    throw new Error(`PDFium: failed to open document (error ${err})`);
  }
  // Keep the buffer alive — freed in closeRawDocument()
  _docDataPtrs.set(docPtr, ptr);
  return docPtr;
}

/**
 * Open a raw document — convenience alias that delegates to {@link openRawDocument}.
 * Kept for API compatibility with callers that were updated to use the "Safe" variant.
 */
export async function openRawDocumentSafe(
  data: ArrayBuffer | Uint8Array,
  password?: string,
): Promise<number> {
  return openRawDocument(data, password);
}

/**

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Map the error code to UX: 4 → password prompt flow; 3 → 'file is not a valid PDF or is damaged'; 5 → 'unsupported security'; 6 → 'a page in this document is corrupt'.
  2. Before opening, verify the bytes start with `%PDF-` and the ArrayBuffer is not detached (`byteLength > 0`).
  3. For password-protected files, obtain the password (see `isPDFUserPasswordProtected`) and pass it to `openRawDocumentSafe(data, password)`.
  4. Never reuse a buffer that was transferred to a Worker (detached) — re-read from the File.

Example fix

// before
const docPtr = m.FPDF_LoadMemDocument(ptr, len, password ?? "");
if (!docPtr) {
  m.pdfium.wasmExports.free(ptr);
  const err = m.FPDF_GetLastError();
  throw new Error(`PDFium: failed to open document (error ${err})`);
}

// after (human-readable codes)
const PDFIUM_ERRORS = { 1:"unknown",2:"file access",3:"invalid/corrupt PDF",4:"password required",5:"unsupported security",6:"corrupt page" };
if (!docPtr) {
  m.pdfium.wasmExports.free(ptr);
  const err = m.FPDF_GetLastError();
  throw new Error(`PDFium: failed to open document — ${PDFIUM_ERRORS[err] ?? `code ${err}`}`);
}
Defensive patterns

Strategy: validation

Validate before calling

// Cheap pre-checks before opening
function looksLikePdfBytes(data: ArrayBuffer | Uint8Array): boolean {
  const u = data instanceof Uint8Array ? data : new Uint8Array(data);
  return u.length >= 5 && u[0] === 0x25 && u[1] === 0x50 && u[2] === 0x44 && u[3] === 0x46;
}
if (!looksLikePdfBytes(data)) throw new Error("Data is not a PDF");

const PDFIUM_ERRORS: Record<number,string> = {1:"unknown",2:"file access",3:"invalid/corrupt PDF",4:"password required",5:"unsupported security",6:"corrupt page"};

Type guard

function isDetached(buf: ArrayBuffer | Uint8Array): boolean {
  return (buf instanceof ArrayBuffer && buf.byteLength === 0 && (buf as any).__proto__ !== null) || false;
}

Try / catch

try {
  const docPtr = await openRawDocumentSafe(data, password);
} catch (e) {
  const m = e instanceof Error ? e.message : "";
  const code = /error (\d)/.exec(m)?.[1];
  if (code === "4") promptForPassword();
  else if (code === "3") notifyUser("This file is not a valid PDF or is damaged.");
  else throw e;
}

Prevention

When it happens

Trigger: Error 4: the PDF is user-password-protected and no/wrong password was supplied. Error 3: bytes are not a PDF, or the PDF is truncated/corrupt (bad xref/header). Error 2: data couldn't be read (rare for in-memory). Error 6: document opened but a page object is broken. Code 0/1: indeterminate.

Common situations: Exporting/rendering a password-protected PDF without prompting for the password; passing a `Uint8Array` whose underlying `ArrayBuffer` was detached; a partially-downloaded file; double-processing the same detached buffer after `transfer`.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/64fd49de07a85d2f. Report an issue: GitHub.