extract-pdf

中文文档
0.1.552发布于 2026/11/03rights.institute/PROSPERTypeScriptESM + CJS构建来源已验证

Convert a PDF (URL or ArrayBuffer) into clean HTML with structural tagging — headings, lists, footnotes, code blocks, bold/italic. Slim by default (PDF.js loads at runtime from the pdfjs-serverless CDN build), with optional Granite Docling OCR for pages w

原始使用文档
文档目录12 节
  1. Install
  2. Docs and live demo
  3. Usage
  4. Options
  5. Return value
  6. Parse methods
  7. Processor modes: frontend, hybrid, Docling OCR, or a remote…
  8. Running the Docling processor server
  9. Hosting the processor on Hugging Face Spaces
  10. Detecting whether a PDF needs OCR
  11. Pipeline
  12. Folder structure


Documentation GitHub Stars NPM Monthly Downloads npm version NPM Total Downloads TypeScript types Install size
GitHub Issues Open Pull Requests Merged Pull Requests GitHub Discussions GitHub last commit
Open in StackBlitz Bun TypeScript Vite

🤖 Agent skill — npx skills@latest add https://github.com/OpenSourceAGI/qwksearch-research-agent --skill extract-pdf (what it covers)

extract-pdf

When users upload a PDF, they expect an instant chat response, not to wait for 5 min on OCR model.

Instant no-backend-needed javascript to convert a PDF (URL or ArrayBuffer) into clean HTML with structural tagging — headings, lists, footnotes, code blocks, bold/italic, and Table of Contents entries. Works in Node.js, Cloudflare Workers, and browser environments via pdfjs-serverless.

Slim by default: the package bundles no PDF engine. PDF.js is loaded lazily at runtime from jsDelivr's ESM build of pdfjs-serverless (https://cdn.jsdelivr.net/npm/pdfjs-serverless@1/+esm, pinned to the major version) — a zero-dependency, single ~1.6 MB minified redistribution of Mozilla PDF.js built for serverless/edge runtimes. Node.js and Bun can't import remote URLs, so there the loader falls back to the locally-installed pdfjs-serverless optional dependency. Everything OCR-related (Granite Docling via @huggingface/transformers, @napi-rs/canvas) is likewise optional and imported only when actually used.

For OCR-grade fidelity on pages with infographics, charts, and tables, the package also ships the Granite Docling path (merged from the former extract-pdf-docling package): run all pages through the frontend JS parser, all pages through Docling OCR, a hybrid mode that regex-scans pages and OCRs only the ones that need it, or point at the URL of a remote docling-compatible processor (see server/).

Install

bun add extract-pdf

Docs and live demo

site/ is one Cloudflare Worker serving the docs for this package and extract-webpage, plus a live demo at /demo: upload a PDF or paste a URL and get HTML back from /api/convert, with the Docling OCR follow-up, or extract a webpage through /api/extract. Run it with bun install at the repository root, then cd site && bun run dev. CI deploys it on every push that touches it. See site/README.md for secrets, custom domains, CPU limits and hardening.

Usage

import { convertPDFToHTML } from "extract-pdf";

const { html, title, author } = await convertPDFToHTML(
  "https://example.com/paper.pdf",
);
// or pass an ArrayBuffer from fs.readFile / fetch
const { html } = await convertPDFToHTML(buffer, { addPageNumbers: true });

Options

OptionDefaultDescription
addPageNumbersfalseInserts [n] markers at each page boundary
addCitationtrueReads PDF metadata and first-page heading to populate title/author in the return value
method"ts-block-algorithm"Parsing engine — "ts-block-algorithm", "liteparse", or "liteparse-wasm" (see below)
liteParseOptions{}Passed through to LiteParse's constructor when method is "liteparse" or "liteparse-wasm"
processor"frontend"Where OCR happens — "frontend", "hybrid", "docling", or a processor URL (see below)
processorUrl—Remote docling-compatible API used by "hybrid"/"docling" instead of the in-process model
ocrScanOptions{}Threshold tuning for the hybrid page scan (scanPagesForOCR)
doclingOptions{}prompt, maxTokens, scale for the Docling OCR model, and processorHeaders sent to processorUrl

Return value

{ html: string, title?: string, author?: string, format: "pdf",
  processor: string, ocrScan: OcrScanResult }

Parse methods

convertPDFToHTML supports three interchangeable parsing engines via options.method:

MethodEngineEnvironmentsOCR
"ts-block-algorithm" (default)The pure-TS pipeline documented below (this package)Node.js, Cloudflare Workers, browserNo
"liteparse"LiteParse (native, @llamaindex/liteparse)Node.js onlyOptional
"liteparse-wasm"LiteParse (WASM, @llamaindex/liteparse-wasm)Node.js, Cloudflare Workers, browserOptional (via callback)
import { convertPDFToHTML } from "extract-pdf";

const { html } = await convertPDFToHTML(buffer, { method: "liteparse" });

// Or the WASM build, which also runs in browsers and Cloudflare Workers:
const { html } = await convertPDFToHTML(buffer, { method: "liteparse-wasm" });

LiteParse ships a native (napi) addon, so method: "liteparse" only runs in Node.js — it is not bundled into browser or Cloudflare Workers builds. Install it explicitly (bun add @llamaindex/liteparse) since it's an optional dependency; if it isn't installed, convertPDFToHTML returns { error } instead of throwing.

method: "liteparse-wasm" delegates to LiteParse's WebAssembly build instead, which runs anywhere WASM does — browsers, Cloudflare Workers, and Node.js. Install it explicitly (bun add @llamaindex/liteparse-wasm) since it's also an optional dependency; if it isn't installed, convertPDFToHTML returns { error } instead of throwing. The WASM build has no OCR engine built in — pass a liteParseOptions.ocrEngine callback (e.g. backed by tesseract-js) to enable OCR.

By default both LiteParse paths run with OCR disabled (ocrEnabled: false) — matching this package's "instant, no backend" philosophy. Use detectPdfNeedsOcr (below) to decide when a document is worth re-parsing with liteParseOptions: { ocrEnabled: true }.

Processor modes: frontend, hybrid, Docling OCR, or a remote URL

Separately from method, the processor option decides whether pages go through the Granite Docling OCR model — which preserves layout, tables, charts, code, and formulas at the cost of model inference per page:

processorWhat happens
"frontend" (default)All pages parsed by the pure-JS text-layer pipeline. No OCR, no model, instant.
"hybrid"JS pipeline everywhere + a regex scan (scanPagesForOCR) flags pages with infographics/figures/tables or no usable text layer; only those pages are rasterized and OCR'd with Docling.
"docling"Every page rasterized and OCR'd with Docling.
"https://..." (URL)Like "docling", but every page is POSTed to that docling-compatible processor API instead of running the model in-process.
// Hybrid: fast JS parse, OCR only the pages that need it
const { html, ocrScan } = await convertPDFToHTML(buffer, { processor: "hybrid" });
console.log(ocrScan.pagesNeedingOcr); // e.g. [3, 7] — pages with tables/figures
// each ocrScan.pages[i] lists reasons: "table-caption", "figure-caption",
// "table-rows", "numeric-grid", "no-text", "sparse-text", "garbled-text"

// All pages through a remote Docling processor (this package's server/):
const { html } = await convertPDFToHTML(buffer, {
  processor: "http://localhost:3000",
});

// Hybrid with the OCR offloaded to a remote processor:
const { html } = await convertPDFToHTML(buffer, {
  processor: "hybrid",
  processorUrl: "http://localhost:3000",
});

OCR'd pages are emitted as <section class="ocr-page" id="page-N">…</section> (tables become real <table> markup, captions <figcaption>, formulas <code class="formula">, …); pages whose OCR fails keep their JS-parsed HTML.

Running Docling in-process requires the optional @huggingface/transformers dependency (the ONNX model onnx-community/granite-docling-258M-ONNX, ~1 GB, downloads on first use) and — in Node.js — @napi-rs/canvas for page rasterization (browsers/Workers use OffscreenCanvas).

The regex scanner is also exported standalone, and so are the OCR helpers:

import { scanPagesForOCR, doctagsToHtml, ocrImageWithDocling } from "extract-pdf";

const scan = scanPagesForOCR(pageTexts); // string[] of per-page text
// { needsOcr, pagesNeedingOcr: number[], pages: [{ page, reasons, captions }] }

Running the Docling processor server

The server/ folder contains the Hono HTTP API (formerly the extract-pdf-docling package) exposing the Granite Docling model:

bun run serve:docling   # or dev:docling for --watch; port 3000
  • Swagger UI: http://localhost:3000/docs, spec at /openapi.json
  • POST /api/v1/convert — { imageUrl, prompt?, maxTokens? }
  • POST /api/v1/convert-base64 — { imageBase64, mimeType?, prompt?, maxTokens? }
  • POST /api/v1/convert-stream — SSE token stream
  • GET /health

A server/wrangler.jsonc is included for deploying it as a Cloudflare Worker. Any deployment of it (or any API with the same contract) can be passed as the processor/processorUrl option above.

Hosting the processor on Hugging Face Spaces

docling-space/ packages the same model as a Hugging Face Docker Space: token auth, one generation at a time with a bounded queue, the model loaded lazily, and optional sanitized HTML output. Its README has the deploy steps. Send the Space's token (and, for a private Space, a Hugging Face read token) with doclingOptions.processorHeaders:

const { html } = await convertPDFToHTML(buffer, {
  processor: "hybrid",
  processorUrl: "https://YOUR_HF_USERNAME-extract-pdf-docling.hf.space",
  doclingOptions: {
    processorHeaders: {
      "X-Docling-Token": process.env.DOCLING_API_TOKEN,
      Authorization: `Bearer ${process.env.HF_SPACE_TOKEN}`, // private Space only
    },
    maxTokens: 1500,
  },
});

Hybrid mode still waits for every flagged page before it returns. To answer at once and enhance afterwards, run processor: "frontend", return its HTML, and OCR ocrScan.pagesNeedingOcr as a follow-up; the live demo does exactly that.

Detecting whether a PDF needs OCR

Before committing to a full (and potentially slow) OCR parse, detectPdfNeedsOcr runs a cheap, text-layer-only pass and reports whether each page needs OCR or other heavy parsing — useful for routing documents to different pipelines (fast path vs. OCR vs. screenshots vs. a heavier parser like LlamaParse or Docling):

import { detectPdfNeedsOcr, convertPDFToHTML } from "extract-pdf";

const assessment = await detectPdfNeedsOcr(buffer);
// { needsOcr: boolean, pages: PageComplexityStats[], reasons: string[] }

if (!assessment.needsOcr) {
  const { html } = await convertPDFToHTML(buffer, { method: "liteparse" });
} else {
  console.log("Needs OCR:", assessment.reasons); // e.g. ["scanned", "sparse-text"]
  // Route to an OCR-enabled pipeline, e.g.:
  const { html } = await convertPDFToHTML(buffer, {
    method: "liteparse",
    liteParseOptions: { ocrEnabled: true },
  });
}

reasons collects every distinct signal found across pages: "scanned", "no-text", "sparse-text", "embedded-images", "garbled", or "vector-text". Like method: "liteparse", this is Node.js only and requires @llamaindex/liteparse.

Pipeline

The conversion runs a sequential chain of transformations on a ParseResult (pages → items):

Raw pdfjs text spans
  → CalculateGlobalStats   — font heights, distances, format map
  → CompactLines           — merge spans on the same y-line into LineItems
  → RemoveRepetitiveElements — strip recurring page headers/footers
  → VerticalToHorizontal   — rotate vertical character runs
  → DetectTOC              — identify Table of Contents pages, link headings
  → DetectHeaders          — classify items as H1–H6 by font height
  → DetectListItems        — detect bullet/numbered list items
  → GatherBlocks           — group adjacent same-type lines into blocks
  → DetectCodeQuoteBlocks  — mark indented blocks as CODE
  → DetectListLevels       — add indentation for nested list levels
  → ToTextBlocks           — flatten blocks to { category, text } pairs
  → ToHTML                 — render pairs as <p>, <h1>–<h6>, <ul>, <code>

Folder structure

src/
  pdf-to-html.ts          — main entry point (convertPDFToHTML)
  liteparse-to-html.ts    — "liteparse" method (native napi addon)
  liteparse-wasm-to-html.ts — "liteparse-wasm" method (WASM, browser/edge)
  ocr-page-scan.ts        — regex scan flagging infographic/table pages for OCR
  docling-ocr.ts          — Granite Docling OCR (local ONNX or remote URL),
  │                         page rasterization, doctags → HTML
  detect-needs-ocr.ts     — LiteParse-based OCR routing (Node.js only)
  models/                 — data classes: Page, ParseResult, TextItem,
  │                         LineItem, LineItemBlock, Word, BlockType, …
  transforms/
  │  base/                — abstract Transformation, ToLineItem*, ToLineItemBlock*
  │  line-item/           — per-line-item transformations
  │  block/               — per-block transformations
  │  calculate-global-stats.ts
  │  to-text-blocks.ts
  │  to-html.ts
  utils/
     load-pdfjs.ts        — lazy CDN loader for pdfjs-serverless
     string-functions.ts
     page-item-functions.ts
     page-number-functions.ts
server/                   — Granite Docling HTTP processor (Hono):
   server.js, routes.js, schemas.js, model.js, wrangler.jsonc

版本与发布记录

extract-pdf 共发布 553 个版本,首次发布于 2026/06/04;过去 12 个月发布了 553 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.1.552latest2026/11/03今天
0.1.5512026/11/03今天
0.1.5502026/11/03今天
0.1.5492026/11/03今天
0.1.5482026/11/03今天
0.1.5472026/11/03今天

关于 extract-pdf 的常见问题

extract-pdf 是什么?

转换PDF (URL或ArrayBuffer)到干净的HTML与结构标签-标题,列表,脚注,代码块,粗体/斜体。默认为Slim (PDF.js在运行时从pdfjs-无服务器CDN构建中加载),为页面w提供可选的Granite Docling OCR

如何安装 extract-pdf?

在项目目录运行 npm install extract-pdf,或使用 pnpm add / yarn add / bun add。

extract-pdf 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

extract-pdf 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

extract-pdf 可以商用吗?

extract-pdf 使用 rights.institute/PROSPER 许可。声明的许可为 rights.institute/PROSPER,请阅读许可原文确认使用条件。以上为概括,不构成法律意见,请以许可原文为准。

extract-pdf 还在维护吗?

最新版本 0.1.552 发布于 2026/11/03(今天);至今共发布 553 个版本,其中过去 12 个月发布了 553 个;首次发布于 2026/06/04。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。