<< All versions

Skill v1.0.0

currentLLM-judged scan90/100
opensourceagi/qwksearch-research-agent/ask-extract-pdf
──Details
PublishedOctober 21, 2026 at 10:27 AM
Content Hashsha256:13514ac79df22d9e...
Git SHAa5f904295058
──Files
Files (1 file, 6.6 KB)
SKILL.md6.6 KBactive
SKILL.md · 104 lines · 6.6 KB

version: "1.0.0" name: ask-extract-pdf description: Guide to extract-pdf (packages/extract-pdf), the PDF-to-structured-HTML converter — convertPDFToHTML() and its two independent switches, method (ts-block-algorithm / liteparse / liteparse-wasm) and processor (frontend / hybrid / docling / a processor URL), the Granite Docling OCR path and its optional dependencies, the page-scan heuristics, and the bundled Hono OCR server. Use when a PDF converts to empty or mangled HTML, when headings/lists/code blocks come out wrong, when scanned or table-heavy pages need OCR, or when the package fails to load in Cloudflare Workers or the browser.


Working With extract-pdf

packages/extract-pdf, published as extract-pdf. Converts a PDF URL or buffer into HTML with real structure — headings inferred from text-height statistics, lists, footnotes linked to their anchors, code and quote blocks, repeated headers removed, and each page number preserved in an invisible <i>. Slim by default: PDF.js is loaded at runtime from the pdfjs-serverless CDN build, so it works in Node, Cloudflare Workers and browsers. Options table: API.md.

Setup

ts
import { convertPDFToHTML } from "extract-pdf";
const html = await convertPDFToHTML("https://example.com/paper.pdf");
const html2 = await convertPDFToHTML(await fs.readFile("paper.pdf"));

The only runtime dependency is grab-url. Everything OCR-related (@huggingface/transformers) and both LiteParse engines (@llamaindex/liteparse, @llamaindex/liteparse-wasm) are optional — install them only if you select that path.

Two independent switches

method chooses the parser; processor chooses where OCR happens. They are not alternatives to each other.

methodEngineRuns on
"ts-block-algorithm" (default)This package's own TS pipeline over pdfjs text spansNode, Workers, browsers
"liteparse"LiteParse native (napi addon)Node only
"liteparse-wasm"LiteParse WebAssemblyAnywhere WASM runs; OCR needs an ocrEngine callback
processorBehaviour
"frontend" (default)Pure JS text layer, no OCR, no model
"hybrid"Every page through the JS pipeline, then scanPagesForOCR regex-flags pages with infographics/figures/tables (or no usable text layer) and only those are re-done with Granite Docling
"docling"Every page rasterized and OCR'd with Granite Docling
an http(s):// URLLike "docling", but pages are POSTed to that processor instead of running in-process

Setting method: "liteparse" short-circuits the whole file: the processor, ocrScanOptions and doclingOptions options are forwarded to LiteParse rather than driving this package's OCR path.

Picking the right call

You wantCall
Text-layer PDF → HTMLconvertPDFToHTML(urlOrBuffer)
OCR only the pages that need itconvertPDFToHTML(x, { processor: "hybrid" })
OCR everything, remotelyconvertPDFToHTML(x, { processor: "https://your-ocr" })
Ask first whether OCR is worth itdetectPdfNeedsOcr(x, opts) → PdfOcrAssessment
The per-page scan on its ownscanPagesForOCR(pages, opts) → OcrScanResult
OCR one imageocrImageWithDocling(imageBase64, opts)
OCR chosen pagesocrPdfPagesWithDocling(...)
A page as a PNGrenderPdfPageToPngBase64(...)
Docling doctags → HTMLdoctagsToHtml(doctags)
The pdfjs instanceloadPdfJs()
Host the OCR model yourselfbun run serve:docling (server/) — Hono + @hono/zod-openapi, Swagger UI, /api/v1/convert-base64, /health
Host it on Hugging Facedocling-space/ — Docker Space with token auth and a one-job queue; see its README

Recipes

Point at your own OCR service. Deploy server/ (it has its own wrangler.jsonc), or docling-space/ as a Hugging Face Docker Space, then pass processor: "https://…" or processorUrl. The client POSTs base64 page images to {processorUrl}/api/v1/convert-base64; auth headers go in doclingOptions.processorHeaders (the Space wants X-Docling-Token, plus Authorization: Bearer hf_… when it is private).

Answer fast, OCR later. "hybrid" waits for every flagged page. For an instant result, run "frontend", return its HTML, and OCR ocrScan.pagesNeedingOcr afterwards. The live demo in site/ (components/demo/PdfDemo.tsx with worker/api.ts) does this: Workers have no canvas, so the browser rasterizes the flagged pages and the Worker forwards each image to the Space.

Tune the hybrid scan. ocrScanOptions (see ScanPagesForOCROptions) sets the thresholds that decide "this page looks like a figure/table". Widen them before reaching for full "docling" — OCR'ing every page is the expensive option.

Tune the model. doclingOptions: prompt (default "Convert this page to docling."), maxTokens (default 4096), scale (default 2, i.e. 144 DPI).

Troubleshooting

SymptomCause → fix
Empty or near-empty HTMLA scanned PDF with no text layer. "frontend" cannot see it — use "hybrid" or "docling".
Cannot find module '@huggingface/transformers'The in-process OCR model is an optional dependency. Install it, or set processorUrl and run the model remotely.
@llamaindex/liteparse not found, or a napi error on Workersmethod: "liteparse" is Node-only and ships a native addon. Use "liteparse-wasm" or the default.
liteparse-wasm produces no OCR textThe WASM build does not bundle an OCR engine — pass an ocrEngine callback (e.g. tesseract-js).
Headings are wrong or missingHeading level is inferred from standard deviation of text height, so a PDF with uniform font sizes yields none. DetectHeaders/CalculateGlobalStats in src/transforms/ are where to tune.
Repeated page headers still appearremovePageHeaders defaults to true but works by finding text repeated across pages — a two-page PDF gives it nothing to compare.
Page numbers appear in the textThey are emitted as invisible <i> markers on purpose. addPageNumbers (default false) adds visible # marks.
OCR is very slow"docling" rasterizes and runs a vision model on every page. Use "hybrid", or lower doclingOptions.scale.
Fails at import time in a bundlerPDF.js is fetched at runtime from the CDN build, not bundled. Do not try to pre-bundle pdfjs-serverless; ensure the runtime can reach it.
bun test passes but vitest finds nothingThis package runs on bun test, not vitest, and is deliberately absent from the root vitest projects.
All versions