Fast, self-contained PDF layout extraction for Node.js. A Rust + PDFium engine compiled to WebAssembly turns a PDF into structured blocks: text, tables (as HTML), and figure placements, each with a normalized bounding box. Everything runs in-process: the PDF bytes are handed straight to WebAssembly and never leave your process. No network calls, no temp files, no external service.
- Node.js >= 18.3
npm install @captain-sdk/pdf-parserconst fs = require('fs');
const { initParser, parsePdf } = require('@captain-sdk/pdf-parser');
(async () => {
// Load the engine once at startup (see "Performance" below). Optional but recommended.
await initParser();
const result = await parsePdf(fs.readFileSync('document.pdf'));
console.log(result.pages); // number of pages
console.log(result.has_text_layer); // true if the PDF carries real text
console.log(result.producer); // e.g. "Amdocs Document Designer"
console.log(result.blocks.length); // extracted layout blocks
})();ESM:
import { initParser, parsePdf } from '@captain-sdk/pdf-parser';interface ParseResult {
pages: number;
has_text_layer: boolean;
producer: string | null;
blocks: Block[];
}
interface Block {
type: string; // see block types below
content: string; // text, or HTML for tables, or "" for figures
page: number; // 1-indexed page
left: number; // bounding box, all normalized 0..1 from the top-left
top: number;
width: number;
height: number;
}parsePdf accepts a Uint8Array, Buffer, or ArrayBuffer. It rejects with an
Error when the bytes are not a PDF the engine can open, so you can show a clean
message instead of crashing.
- Text, Header, Section Header, List Item, Key Value, Footer — text regions, classified by role.
contentis the text. - Table —
contentis HTML (<table><tr><td>...). Both ruled (lined) and unruled (whitespace-aligned) tables are reconstructed. - Figure — marks where a graphic sits, with a bounding box.
contentis empty: figures are located, not rasterized, and there is no OCR. Treat the count as approximate (vector art and raster images can both surface as figures).
The first call loads ~6 MB of WebAssembly and pays PDFium's one-time init, so it is the slow one (up to a couple of seconds cold). Every parse after that is fast (a few milliseconds for a simple page, ~100–200 ms for a dense multi-page doc).
Call initParser() once at startup so your first real parsePdf is not the call
that eats the init cost. If you skip it, parsePdf initializes lazily on first
use.
This package ships two runtime assets it loads from disk at call time:
pdfium/pdfium.wasm and lib/globeparse_wasm_bg.wasm. In a plain Node process
(node app.js) this just works.
Bundlers (webpack, esbuild, Next.js) and serverless packagers (AWS Lambda, Vercel)
often do not trace or copy .wasm and sibling assets automatically. If you
bundle or deploy and see a "file not found" or "PDFium engine" error at runtime,
the asset folders were not included in your build. Fixes:
-
Keep the package external so it loads from
node_modulesat runtime:- esbuild:
--external:@captain-sdk/pdf-parser - Next.js: add
@captain-sdk/pdf-parsertoserverExternalPackages - webpack: mark it in
externals
- esbuild:
-
Or copy the assets into your deployment and point the parser at them:
await initParser({ assetsDir: '/var/task/pdfium' }); // where you copied pdfium/*
assetsDiralso works as the second argument toparsePdf(bytes, { assetsDir }).
MIT (see LICENSE). The bundled PDFium engine is distributed under its own
BSD-3-Clause license, included as PDFIUM-LICENSE.