UNPKG

pdf-to-png-converter

Version:

Node.js utility to convert PDF file/buffer pages to PNG files/buffers. No build-time compilation required — pre-built native binaries included for all major platforms.

101 lines (100 loc) 6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPdfFileBuffer = getPdfFileBuffer; const node_fs_1 = require("node:fs"); function rejectOversized(byteLength, maxInputBytes) { if (byteLength > maxInputBytes) { throw new Error(`Input PDF exceeds maxInputBytes (${byteLength} > ${maxInputBytes} bytes)`); } } /** An object exposing a finite numeric `length` — the shape pdf.js itself accepts as byte data. */ function isByteArrayLike(value) { return typeof value === 'object' && value !== null && Number.isFinite(value.length); } /** * Normalizes every supported input shape to a `Uint8Array` that pdfjs may safely transfer * (detach). Closing the return type here means this module is the single owner of "what shape we * hand pdfjs" — downstream seams (`getPdfDocument`, the worker dispatch) take a `Uint8Array` and * re-derive nothing. */ async function getPdfFileBuffer(pdfFile, maxInputBytes) { if (typeof pdfFile === 'string') { const stats = await node_fs_1.promises.stat(pdfFile); if (!stats.isFile()) { throw new Error(`Input PDF path is not a regular file: ${pdfFile}`); } rejectOversized(stats.size, maxInputBytes); const buffer = await node_fs_1.promises.readFile(pdfFile); // Post-read re-check: closes the TOCTOU window between stat() and readFile(). // If the file was replaced or grew between the two calls, the buffer may exceed // maxInputBytes — reject it before it propagates further into pdfjs parsing. rejectOversized(buffer.byteLength, maxInputBytes); if (buffer instanceof ArrayBuffer) { // Fresh allocation owned by this call — a full-span view is safe to hand over. return new Uint8Array(buffer); } if (Buffer.isBuffer(buffer)) { // Zero-copy handoff. This Buffer was freshly allocated by readFile and is never // exposed to the caller, so pdfjs may safely transfer (detach) its underlying // ArrayBuffer — unlike the caller-owned branches below, no defensive copy is needed. // readFile allocates non-pooled memory today; the full-span guard protects against // any future pooled allocation whose ArrayBuffer is shared with unrelated data. // Empty (or detached, byteLength 0) buffers take the copy path so pdfjs raises its // clear "empty PDF" error instead of an opaque constructor TypeError. if (buffer.byteLength > 0 && buffer.byteOffset === 0 && buffer.byteLength === buffer.buffer.byteLength) { return new Uint8Array(buffer.buffer); } return new Uint8Array(buffer); } throw new Error(`Unsupported buffer type: ${Object.prototype.toString.call(buffer)}`); } if (Buffer.isBuffer(pdfFile)) { rejectOversized(pdfFile.byteLength, maxInputBytes); return new Uint8Array(pdfFile); } rejectOversized(pdfFile.byteLength, maxInputBytes); // Defensive copy. pdfjs `getDocument()` lists the input's underlying ArrayBuffer as a // transferable, which DETACHES it (byteLength → 0) when the data is a full-span Uint8Array. // Returning the caller's buffer by reference would therefore neuter their input and break // reuse across calls. The string-path and Node-Buffer branches above already allocate fresh // memory; copy the Uint8Array / ArrayBuffer branch too so every supported input shape leaves // the caller's buffer intact. if (pdfFile instanceof Uint8Array) { return Uint8Array.from(pdfFile); } if (pdfFile instanceof ArrayBuffer) { return new Uint8Array(pdfFile.slice(0)); } if (pdfFile instanceof SharedArrayBuffer) { // `slice()` would yield another SharedArrayBuffer (non-transferable, shared) — copy into a // fresh, regular-ArrayBuffer-backed Uint8Array so downstream pdfjs always receives // unshared memory. `new Uint8Array(sab)` only views the SAB, so copy via `Uint8Array.from`. return Uint8Array.from(new Uint8Array(pdfFile)); } // The branches below are unreachable for TypeScript callers — the parameter type is exhausted // above — but `instanceof` is realm-bound, so genuine byte containers created in another realm // land here. pdf.js accepted all of these before this seam closed the union (`getDataProp` // takes anything satisfying `ArrayBuffer.isView` or having a numeric `length`, and the old // loader coerced the rest with `new Uint8Array(value)`), so they must keep converting. const candidate = pdfFile; // Cross-realm typed arrays and `DataView`s — e.g. built inside `node:vm` or `isolated-vm`, // where a real `Uint8Array` fails `instanceof` while still satisfying the declared type. if (ArrayBuffer.isView(candidate)) { return Uint8Array.from(new Uint8Array(candidate.buffer, candidate.byteOffset, candidate.byteLength)); } // Cross-realm `ArrayBuffer`, for the same reason. if (Object.prototype.toString.call(candidate) === '[object ArrayBuffer]') { return Uint8Array.from(new Uint8Array(candidate)); } // Array-likes of byte values — most commonly a Node `Buffer` that round-tripped through JSON // as `{ type: 'Buffer', data: number[] }` and reaches us as that `data` array. `byteLength` is // `undefined` on these, so the size cap above skipped them; apply it to `length` here. if (isByteArrayLike(candidate)) { rejectOversized(candidate.length, maxInputBytes); return Uint8Array.from(candidate); } // Genuinely unsupported. Reject here with the same message the file-path branch uses, rather // than passing the value through for pdfjs to reject: this module owns the input contract, so // an unsupported shape must not escape it. throw new Error(`Unsupported buffer type: ${Object.prototype.toString.call(pdfFile)}`); }