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
- 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'.
- Before opening, verify the bytes start with `%PDF-` and the ArrayBuffer is not detached (`byteLength > 0`).
- For password-protected files, obtain the password (see `isPDFUserPasswordProtected`) and pass it to `openRawDocumentSafe(data, password)`.
- 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
- Verify the buffer starts with %PDF- and is non-empty/detached before opening.
- Map PDFium error codes (4=password, 3=format, 5=security, 6=page) to distinct UX.
- Obtain the password via the existing prompt flow for code-4 files.
- Never reuse a buffer that was transferred to a Worker (detached) — re-read from the File.
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
- PDFium: failed to create page
- PDFium: failed to create new document
- Failed to load page ${pageIndex}
- PDFium: failed to create document
- Failed to process ${file.name}: ${error instanceof Error ? e
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/64fd49de07a85d2f.
Report an issue: GitHub.