GetNotes Tools
firecrawl/pdf-inspector
Tool นี้คืออะไร
pdf-inspector เป็นไลบรารี Rust ที่รวดเร็วสำหรับการจำแนกประเภทและดึงข้อความจากไฟล์ PDF เหมาะสำหรับนักพัฒนาที่ต้องการประมวลผล PDF ที่เป็นข้อความ (text-based) ได้อย่างรวดเร็วและแม่นยำ โดยไม่ต้องพึ่ง OCR ช่วยลดค่าใช้จ่ายและเวลาในการประมวลผล
ข้อมูลโปรเจกต์
ดาว
9.2K
Forks
614
License
MIT
อัปเดต GitHub ล่าสุด
4 ส.ค. 2569
เพิ่มใน GetNotes
17 ส.ค. 2569
Repository
firecrawl/pdf-inspector
รูปแบบ
เหมาะกับงาน
เหมาะกับอาชีพ
Ecosystem
Node.js · Rust
แปลและเรียบเรียงโดย AI
เนื้อหาฉบับภาษาไทย
ใช้อ่านเพื่อทำความเข้าใจเบื้องต้น โปรดตรวจสอบรายละเอียดสำคัญกับเอกสารต้นฉบับด้านล่าง
pdf-inspector
ไลบรารี Rust ที่รวดเร็วสำหรับการจำแนกประเภท PDF และการดึงข้อความ ตรวจจับว่า PDF เป็นแบบข้อความ (text-based) หรือแบบสแกน (scanned), ดึงข้อความพร้อมข้อมูลตำแหน่ง และแปลงเป็น Markdown ที่สะอาด — ทั้งหมดนี้โดยไม่ต้องใช้ OCR มีการผูก (bindings) สำหรับ Python, Node.js และ browser WebAssembly
สร้างโดย Firecrawl เพื่อจัดการกับ PDF ที่เป็นข้อความในเครื่องภายในเวลาไม่ถึง 200ms โดยข้ามบริการ OCR ที่มีราคาแพงสำหรับ PDF ประมาณ 54% ที่ไม่จำเป็นต้องใช้
คุณสมบัติ
- การจำแนกประเภทอัจฉริยะ — ตรวจจับ PDF ประเภท TextBased, Scanned, ImageBased หรือ Mixed ในเวลาประมาณ 10-50ms โดยการสุ่มตัวอย่าง content streams คืนค่าคะแนนความเชื่อมั่น (0.0-1.0) และการกำหนดเส้นทาง OCR แบบต่อหน้า
- การดึงข้อความ — การดึงข้อความที่รับรู้ตำแหน่งพร้อมข้อมูลฟอนต์, พิกัด X/Y และลำดับการอ่านแบบหลายคอลัมน์อัตโนมัติ
- การแปลงเป็น Markdown — หัวข้อ (H1-H4 ผ่านอัตราส่วนขนาดฟอนต์), รายการแบบจุด/ตัวเลข/ตัวอักษร, บล็อกโค้ด (การตรวจจับฟอนต์ monospace), ตาราง (อิงตามสี่เหลี่ยมและฮิวริสติก), การจัดรูปแบบตัวหนา/ตัวเอียง, การเชื่อมโยง URL และการแบ่งหน้า
- การตรวจจับตาราง — โหมดคู่: การตรวจจับตารางอิงตามสี่เหลี่ยมจาก PDF drawing ops และการตรวจจับแบบฮิวริสติกจากการจัดแนวข้อความ จัดการกับตารางทางการเงิน, เชิงอรรถ และตารางต่อเนื่องข้ามหน้า
- การรองรับฟอนต์ CID — การถอดรหัส ToUnicode CMap สำหรับฟอนต์ Type0/Identity-H, การเข้ารหัส UTF-16BE, UTF-8 และ Latin-1
- เค้าโครงหลายคอลัมน์ — การตรวจจับคอลัมน์สไตล์หนังสือพิมพ์อัตโนมัติ, ลำดับการอ่านแบบต่อเนื่อง และการรองรับข้อความ RTL
- การตรวจจับปัญหาการเข้ารหัส — ตั้งค่าสถานะการเข้ารหัสฟอนต์ที่เสียหายโดยอัตโนมัติ เพื่อให้ผู้เรียกสามารถกลับไปใช้ OCR ได้
- การโหลดเอกสารครั้งเดียว — เอกสารจะถูกแยกวิเคราะห์เพียงครั้งเดียวและใช้ร่วมกันระหว่างการตรวจจับและการดึงข้อความ หลีกเลี่ยงการ I/O ที่ซ้ำซ้อน
- Browser WebAssembly — รันตัวแยกวิเคราะห์ Rust เดียวกันในเครื่องในเบราว์เซอร์และ Web Workers พร้อม CMaps แบบฝัง และไม่มีการเดินทางไปกลับเซิร์ฟเวอร์
- น้ำหนักเบา — Pure Rust, ไม่มีโมเดล ML, ไม่มีบริการภายนอก พึ่งพา
lopdfเพียงตัวเดียวสำหรับการแยกวิเคราะห์ PDF
การวัดประสิทธิภาพ
ประเมินจากคลังข้อมูล opendataloader-bench (200 PDF) แสดงเฉพาะเอนจินในเครื่องที่ไม่มีการแยกวิเคราะห์ PDF แบบโมเดลเท่านั้น OCR ถูกปิดใช้งาน คะแนนอยู่ระหว่าง 0-1 ยิ่งสูงยิ่งดี
| Engine | Overall | Reading Order (NID) | Tables (TEDS) | Headings (MHS) | Speed (200 docs) |
|---|---|---|---|---|---|
| pdf-inspector | 0.875 | 0.915 | 0.814 | 0.788 | 0.470s |
| liteparse | 0.873 | 0.913 | 0.693 | 0.811 | 0.750s |
| opendataloader | 0.831 | 0.902 | 0.489 | 0.739 | 2.569s |
| pymupdf4llm | 0.735 | 0.886 | 0.401 | 0.424 | 17.117s |
| markitdown | 0.589 | 0.844 | 0.273 | 0.000 | 16.165s |
ผลลัพธ์ได้รับการอัปเดตเมื่อวันที่ 31 กรกฎาคม 2026 บน Apple M4 Pro เวอร์ชันของเอนจินคือ pdf-inspector 0.2.6, LiteParse 2.10.1, OpenDataLoader 2.2.1, PyMuPDF4LLM 0.2.0 และ MarkItDown 0.1.5 ความเร็วคือค่ามัธยฐานของการรันคลังข้อมูลทั้งหมดห้าครั้งแบบสลับหรือหมุนเวียน หลังจากรันวอร์มอัพที่ถูกยกเว้น โดยแต่ละตัวแยกวิเคราะห์จะประมวลผลเอกสารตามลำดับในกระบวนการเดียว
การกำหนดค่าตัวแยกวิเคราะห์ที่สมบูรณ์, การคาดการณ์ต่อเอกสาร, เอาต์พุตของตัวประเมิน และแผนภูมิที่สร้างขึ้นมีอยู่ใน reproducible results branch
เหมาะสมที่สุด: PDF ที่เป็นข้อความดั้งเดิมที่ความเร็ว, ลำดับการอ่าน และโครงสร้างตารางมีความสำคัญ ในการเปรียบเทียบนี้ pdf-inspector ให้คะแนนโดยรวม, ลำดับการอ่าน และตารางที่สูงกว่า พร้อมกับการรันที่สมบูรณ์ที่เร็วที่สุด ทำให้เป็นค่าเริ่มต้นในเครื่องที่แข็งแกร่งสำหรับรายงาน, เอกสารวิจัย, เอกสารทางการเงิน, ใบแจ้งหนี้ และ PDF ทางกฎหมายที่ต้องการ Markdown ที่สะอาดและมีโครงสร้างโดยไม่ต้องเพิ่มความล่าช้าหรือโครงสร้างพื้นฐานของ OCR
ใช้ paired benchmark harness เพื่อเปรียบเทียบสองบิลด์ในเครื่องกับคลังข้อมูลและเวอร์ชันตัวประเมินที่เหมือนกัน
เริ่มต้นอย่างรวดเร็ว
Python
pip install maturin
maturin develop --releaseimport pdf_inspector
result = pdf_inspector.process_pdf("document.pdf")
print(result.pdf_type) # "text_based", "scanned", "image_based", "mixed"
print(result.markdown) # Markdown string or Noneการอ้างอิง API ฉบับเต็ม: docs/python.md
Node.js
npm install @firecrawl/pdf-inspectorimport { readFileSync } from 'fs';
import { processPdf, classifyPdf } from '@firecrawl/pdf-inspector';
const result = processPdf(readFileSync('document.pdf'));
console.log(result.pdfType); // "TextBased", "Scanned", "ImageBased", "Mixed"
console.log(result.markdown); // Markdown string or nullการอ้างอิง API ฉบับเต็ม: napi/README.md
Browser WebAssembly
npm install @firecrawl/pdf-inspector-wasmimport init, { processPdf } from '@firecrawl/pdf-inspector-wasm';
await init();
const response = await fetch('/document.pdf');
const pdf = new Uint8Array(await response.arrayBuffer());
const result = processPdf(pdf);
console.log(result.pdfType);
console.log(result.markdown);การอ้างอิง API ฉบับเต็ม: wasm/README.md
Rust
ติดตั้งจาก crates.io:
cargo add pdf-inspectorหรือเพิ่มด้วยตนเอง:
[dependencies]
pdf-inspector = "1"use pdf_inspector::process_pdf;
let result = process_pdf("document.pdf")?;
println!("Type: {:?}", result.pdf_type);
if let Some(markdown) = &result.markdown {
println!("{}", markdown);
}การอ้างอิง API ฉบับเต็ม: docs/rust-api.md
CLI
# Install the CLI tools
cargo install pdf-inspector
# Convert PDF to Markdown
pdf2md document.pdf
# JSON output (for piping)
pdf2md document.pdf --json
# Positioned TextItem JSON, including is_underline metadata
pdf2md document.pdf --items-json
# Raw markdown only (no headers)
pdf2md document.pdf --raw
# Token-efficient output (collapses long dot leaders and similar source padding)
pdf2md document.pdf --compact
# Insert page break markers (<!-- Page N -->)
pdf2md document.pdf --pages
# Process only specific pages
pdf2md document.pdf --select-pages 1,3,5-10
# Detection only (no extraction)
detect-pdf document.pdf
detect-pdf document.pdf --json
# Detection + layout analysis (tables, columns)
detect-pdf document.pdf --analyze --jsonจากการตรวจสอบซอร์สโค้ด ให้ใช้ cargo run --bin pdf2md -- document.pdf หรือ cargo run --bin detect-pdf -- document.pdf แทน
สถาปัตยกรรม
PDF bytes
│
├─► detector → PdfType (TextBased / Scanned / ImageBased / Mixed)
│
└─► extractor
├─ fonts → font widths, encodings
├─ content_stream → walk PDF operators → TextItems + PdfRects
├─ xobjects → Form XObject text, image placeholders
├─ links → hyperlinks, AcroForm fields
└─ layout → column detection → line grouping → reading order
│
├─► tables
│ ├─ detect_rects → rectangle-based tables (union-find)
│ ├─ detect_heuristic → alignment-based tables
│ ├─ grid → column/row assignment → cells
│ └─ format → cells → Markdown table
│
└─► markdown
├─ analysis → font stats, heading tiers
├─ preprocess → merge headings, drop caps
├─ convert → line loop + table/image insertion
├─ classify → captions, lists, code
└─ postprocess → cleanup → final Markdownเอกสารจะถูกโหลด ครั้งเดียว ผ่าน load_document_from_path / load_document_from_mem และใช้ร่วมกันระหว่างขั้นตอนการตรวจจับและการดึงข้อความ ดังนั้นจึงไม่มีการแยกวิเคราะห์ที่ซ้ำซ้อน
โครงสร้างโปรเจกต์
src/
lib.rs — Public API, PdfOptions builder, convenience functions
python.rs — PyO3 Python bindings
types.rs — Shared types: TextItem, TextLine, PdfRect, ItemType
text_utils.rs — Character/text helpers (CJK, RTL, ligatures, bold/italic)
process_mode.rs — ProcessMode enum (DetectOnly, Analyze, Full)
detector.rs — Fast PDF type detection without full document load
glyph_names.rs — Adobe Glyph List → Unicode mapping
tounicode.rs — ToUnicode CMap parsing for CID-encoded text
extractor/ — Text extraction pipeline
tables/ — Table detection and formatting
markdown/ — Markdown conversion and structure detection
bin/ — CLI tools (pdf2md, detect_pdf)
napi/ — Node.js/Bun bindings (napi-rs)
wasm/ — Browser bindings (wasm-bindgen)
## การทำงานของการจำแนกประเภท
1. แยกวิเคราะห์ตาราง xref และโครงสร้างหน้า (ไม่มีการโหลดอ็อบเจกต์ทั้งหมด)
2. เลือกหน้าตาม `ScanStrategy` (ค่าเริ่มต้น: ทุกหน้าพร้อมการออกก่อนกำหนด)
3. มองหา `Tj`/`TJ` (ตัวดำเนินการข้อความ) และ `Do` (ตัวดำเนินการรูปภาพ) ในสตรีมเนื้อหา
4. จำแนกประเภทตามการมีอยู่ของตัวดำเนินการข้อความในหน้าตัวอย่าง
สิ่งนี้สามารถตรวจจับ PDF ที่มีมากกว่า 300 หน้าได้ในหน่วยมิลลิวินาที ผลลัพธ์ประกอบด้วย `pages_needing_ocr` — รายการหมายเลขหน้าเฉพาะที่ไม่มีข้อความ ทำให้สามารถกำหนดเส้นทาง OCR แบบต่อหน้าได้ แทนที่จะเป็นแบบทั้งหมดหรือไม่มีเลย
### กลยุทธ์การสแกน
| กลยุทธ์ | พฤติกรรม | เหมาะที่สุดสำหรับ |
|---|---|---|
| `EarlyExit` (ค่าเริ่มต้น) | สแกนทุกหน้า หยุดเมื่อเจอหน้าแรกที่ไม่ใช่ข้อความ | ไปป์ไลน์ที่กำหนดเส้นทาง PDF ที่เป็นข้อความไปยังการแยกข้อมูลที่รวดเร็ว |
| `Full` | สแกนทุกหน้า ไม่มีการออกก่อนกำหนด | การจำแนกประเภทแบบผสมเทียบกับแบบสแกนที่แม่นยำ |
| `Sample(n)` | สุ่มตัวอย่าง `n` หน้าที่กระจายอย่างสม่ำเสมอ (หน้าแรก, หน้าสุดท้าย, กลาง) | PDF ขนาดใหญ่มากที่ความเร็วสำคัญกว่าความแม่นยำ |
| `Pages(vec)` | สแกนเฉพาะหมายเลขหน้าแบบ 1-indexed ที่ระบุเท่านั้น | เมื่อผู้เรียกทราบว่าต้องตรวจสอบหน้าใดบ้าง |
## เอาต์พุต Markdown
ตัวแปลงจัดการ:
| องค์ประกอบ | ตรวจจับได้อย่างไร |
|---|---|
| หัวข้อ (H1-H4) | ระดับขนาดตัวอักษรสัมพันธ์กับข้อความเนื้อหา โดยมีการจัดกลุ่ม 0.5pt |
| ตัวหนา/ตัวเอียง | รูปแบบชื่อฟอนต์ (Bold, Italic, Oblique) |
| รายการสัญลักษณ์แสดงหัวข้อย่อย | คำนำหน้า `•`, `-`, `*`, `○`, `●`, `◦` |
| รายการลำดับเลข | รูปแบบ `1.`, `1)`, `(1)` |
| รายการตัวอักษร | รูปแบบ `a.`, `a)`, `(a)` |
| บล็อกโค้ด | ฟอนต์ Monospace (Courier, Consolas, Monaco, Menlo, Fira Code, JetBrains Mono) และการตรวจจับคีย์เวิร์ด |
| ตาราง | การตรวจจับตามสี่เหลี่ยมจาก PDF drawing ops + การตรวจจับแบบฮิวริสติกจากการจัดแนวข้อความ |
| ตารางการเงิน | การแยกโทเค็นสำหรับค่าตัวเลขที่รวมกัน |
| คำบรรยายภาพ | การตรวจจับคำนำหน้า "Figure", "Table", "Source:" |
| ตัวห้อย/ตัวยก | ขนาดตัวอักษรและ Y-offset สัมพันธ์กับเส้นฐาน |
| URL | แปลงเป็นลิงก์ Markdown |
| การแบ่งคำ | รวมคำที่ถูกแบ่งข้ามบรรทัดเข้าด้วยกัน |
| หมายเลขหน้า | กรองออกจากเอาต์พุต |
| อักษรตัวใหญ่ขึ้นต้น | ตัวอักษรเริ่มต้นขนาดใหญ่รวมกับข้อความที่ตามมา |
| จุดนำ | จุดสไตล์ TOC ยุบเป็น " ... " |
## กรณีการใช้งาน: การกำหนดเส้นทาง PDF อัจฉริยะ
pdf-inspector ถูกสร้างขึ้นสำหรับไปป์ไลน์ที่ประมวลผล PDF ในปริมาณมาก แทนที่จะส่ง PDF ทุกไฟล์ผ่าน OCR:
PDF มาถึง → pdf-inspector จำแนกประเภท (~20ms) → TextBased + ความมั่นใจสูง? ใช่ → แยกข้อมูลในเครื่อง (~150ms), เสร็จสิ้น ไม่ → ส่งไปยังบริการ OCR (2-10s)
สิ่งนี้ช่วยประหยัดค่าใช้จ่ายและลดเวลาแฝงสำหรับ PDF ส่วนใหญ่ที่เป็นข้อความอยู่แล้ว (รายงาน, เอกสารวิชาการ, ใบแจ้งหนี้, เอกสารทางกฎหมาย)
## การดีบัก
ดู [docs/debugging.md](https://github.com/firecrawl/pdf-inspector/blob/main/docs/debugging.md) สำหรับการใช้งานตัวแปรสภาพแวดล้อม `RUST_LOG`
## ใบอนุญาต
[MIT](https://github.com/firecrawl/pdf-inspector/blob/main/LICENSE)เอกสารโปรเจกต์
อ่านเอกสารต้นฉบับ
README วิธีติดตั้ง วิธีใช้งาน และข้อกำหนดจาก repository ต้นฉบับ
pdf-inspector
Fast Rust library for PDF classification and text extraction. By default it detects whether a PDF is text-based or scanned, extracts text with position awareness, and converts to clean Markdown without OCR. Native Rust and CLI consumers can opt into selective OCR. Includes bindings for Python, Node.js, and browser WebAssembly.
Built by Firecrawl to handle text-based PDFs locally in under 200ms, skipping expensive OCR services for the ~54% of PDFs that don't need them.
Features
- Smart classification — Detect TextBased, Scanned, ImageBased, or Mixed PDFs in ~10-50ms by sampling content streams. Returns a confidence score (0.0-1.0) and per-page OCR routing.
- Text extraction — Position-aware extraction with font info, X/Y coordinates, and automatic multi-column reading order.
- Markdown conversion — Headings (H1-H4 via font size ratios), bullet/numbered/letter lists, code blocks (monospace font detection), tables (rectangle-based and heuristic), bold/italic formatting, URL linking, and page breaks.
- Table detection — Dual-mode: rectangle-based detection from PDF drawing ops, plus heuristic detection from text alignment. Handles financial tables, footnotes, and continuation tables across pages.
- CID font support — ToUnicode CMap decoding for Type0/Identity-H fonts, UTF-16BE, UTF-8, and Latin-1 encodings.
- Multi-column layout — Automatic detection of newspaper-style columns, sequential reading order, and RTL text support.
- Encoding issue detection — Automatically flags broken font encodings so callers can fall back to OCR.
- Selective OCR — Rust, CLI, Python, and Node can render only pages that need OCR, run PP-OCRv6 Small locally, and preserve per-page provenance and hosted-fallback recommendations.
- Single document load — The document is parsed once and shared between detection and extraction, avoiding redundant I/O.
- Browser WebAssembly — Run the same Rust parser locally in browsers and Web Workers, with embedded CMaps and no server round trip.
- Lightweight by default — The default Rust and browser builds remain pure extraction. Native Python and Node packages include the OCR integration, but PDFium, ONNX Runtime, and model files remain external and are touched only when a page is routed to OCR.
Benchmark
Evaluated on the opendataloader-bench corpus (200 PDFs). Only local engines without model-based PDF parsing are shown; OCR was disabled. Scores are 0-1, higher is better.
| Engine | Overall | Reading Order (NID) | Tables (TEDS) | Headings (MHS) | Speed (200 docs) |
|---|---|---|---|---|---|
| pdf-inspector | 0.875 | 0.915 | 0.814 | 0.788 | 0.470s |
| liteparse | 0.873 | 0.913 | 0.693 | 0.811 | 0.750s |
| opendataloader | 0.831 | 0.902 | 0.489 | 0.739 | 2.569s |
| pymupdf4llm | 0.735 | 0.886 | 0.401 | 0.424 | 17.117s |
| markitdown | 0.589 | 0.844 | 0.273 | 0.000 | 16.165s |
Results were refreshed on July 31, 2026, on an Apple M4 Pro. Engine versions were pdf-inspector 0.2.6, LiteParse 2.10.1, OpenDataLoader 2.2.1, PyMuPDF4LLM 0.2.0, and MarkItDown 0.1.5. Speed is the median of five alternating or rotating complete corpus runs after an excluded warm-up run, with each parser processing documents sequentially in a single process.
The complete parser configuration, per-document predictions, evaluator output, and generated charts are available in the reproducible results branch.
Best fit: Native-text PDFs where speed, reading order, and table structure matter. In this comparison, pdf-inspector delivered the higher overall, reading-order, and table scores, along with the fastest complete run. That makes it a strong local default for reports, research papers, financial documents, invoices, and legal PDFs that need clean, structured Markdown without adding OCR latency or infrastructure.
Use the paired benchmark harness to compare two local builds against the exact same corpus and evaluator revision.
Quick start
Python
pip install pdf-inspectorimport pdf_inspector
result = pdf_inspector.process_pdf("document.pdf")
print(result.pdf_type) # "text_based", "scanned", "image_based", "mixed"
print(result.markdown) # Markdown string or None
# Selective OCR; clean text PDFs do not load the external OCR runtime.
ocr = pdf_inspector.process_pdf_with_ocr("document.pdf")
print(ocr.pages_routed_to_ocr)Full API reference: docs/python.md
Node.js
npm install @firecrawl/pdf-inspectorimport { readFileSync } from 'fs';
import { processPdf, processPdfWithOcr } from '@firecrawl/pdf-inspector';
const pdf = readFileSync('document.pdf');
const result = processPdf(pdf);
console.log(result.pdfType); // "TextBased", "Scanned", "ImageBased", "Mixed"
console.log(result.markdown); // Markdown string or null
const ocr = await processPdfWithOcr(pdf); // selective OCR, off the event loop
console.log(ocr.pagesRoutedToOcr);Full API reference: napi/README.md
Browser WebAssembly
npm install @firecrawl/pdf-inspector-wasmimport init, { processPdf } from '@firecrawl/pdf-inspector-wasm';
await init();
const response = await fetch('/document.pdf');
const pdf = new Uint8Array(await response.arrayBuffer());
const result = processPdf(pdf);
console.log(result.pdfType);
console.log(result.markdown);Full API reference: wasm/README.md
Rust
Install from crates.io:
cargo add pdf-inspectorOr add it manually:
[dependencies]
pdf-inspector = "1"use pdf_inspector::process_pdf;
let result = process_pdf("document.pdf")?;
println!("Type: {:?}", result.pdf_type);
if let Some(markdown) = &result.markdown {
println!("{}", markdown);
}Full API reference: docs/rust-api.md
CLI
# Install the CLI tools
cargo install pdf-inspector
# Convert PDF to Markdown
pdf2md document.pdf
# JSON output (for piping)
pdf2md document.pdf --json
# Positioned TextItem JSON, including is_underline metadata
pdf2md document.pdf --items-json
# Raw markdown only (no headers)
pdf2md document.pdf --raw
# Token-efficient output (collapses long dot leaders and similar source padding)
pdf2md document.pdf --compact
# Insert page break markers (<!-- Page N -->)
pdf2md document.pdf --pages
# Process only specific pages
pdf2md document.pdf --select-pages 1,3,5-10
# Detection only (no extraction)
detect-pdf document.pdf
detect-pdf document.pdf --json
# Detection + layout analysis (tables, columns)
detect-pdf document.pdf --analyze --jsonRust and CLI consumers opt into OCR at build time:
cargo install pdf-inspector --features ocr --bin pdf2md
PDFIUM_LIB_PATH=/path/to/libpdfium ORT_DYLIB_PATH=/path/to/libonnxruntime \
pdf2md scan.pdf --ocr auto --jsonThe OCR JSON envelope is versioned and reports routed pages, per-page source and confidence, warnings, and pages recommended for the hosted document pipeline. Native Python and Node packages expose the same pipeline without a source-build feature. All native entry points still require separately installed PDFium and ONNX Runtime libraries only when OCR is routed. See the OCR runtime setup guide for pinned downloads, platform support, model-cache behavior, and hosted-fallback integration. See the Rust API guide for lower-level controls.
From a source checkout, use cargo run --bin pdf2md -- document.pdf or cargo run --bin detect-pdf -- document.pdf instead.
Architecture
PDF bytes
│
├─► detector → PdfType (TextBased / Scanned / ImageBased / Mixed)
│
└─► extractor
├─ fonts → font widths, encodings
├─ content_stream → walk PDF operators → TextItems + PdfRects
├─ xobjects → Form XObject text, image placeholders
├─ links → hyperlinks, AcroForm fields
└─ layout → column detection → line grouping → reading order
│
├─► tables
│ ├─ detect_rects → rectangle-based tables (union-find)
│ ├─ detect_heuristic → alignment-based tables
│ ├─ grid → column/row assignment → cells
│ └─ format → cells → Markdown table
│
└─► markdown
├─ analysis → font stats, heading tiers
├─ preprocess → merge headings, drop caps
├─ convert → line loop + table/image insertion
├─ classify → captions, lists, code
└─ postprocess → cleanup → final MarkdownThe document is loaded once via load_document_from_path / load_document_from_mem and shared between the detection and extraction stages, so there's no redundant parsing.
Project structure
src/
lib.rs — Public API, PdfOptions builder, convenience functions
python.rs — PyO3 Python bindings
types.rs — Shared types: TextItem, TextLine, PdfRect, ItemType
text_utils.rs — Character/text helpers (CJK, RTL, ligatures, bold/italic)
process_mode.rs — ProcessMode enum (DetectOnly, Analyze, Full)
detector.rs — Fast PDF type detection without full document load
glyph_names.rs — Adobe Glyph List → Unicode mapping
tounicode.rs — ToUnicode CMap parsing for CID-encoded text
extractor/ — Text extraction pipeline
tables/ — Table detection and formatting
markdown/ — Markdown conversion and structure detection
bin/ — CLI tools (pdf2md, detect_pdf)
napi/ — Node.js/Bun bindings (napi-rs)
wasm/ — Browser bindings (wasm-bindgen)How classification works
- 1Parse the xref table and page tree (no full object load)
- 2Select pages based on
ScanStrategy(default: all pages with early exit) - 3Look for
Tj/TJ(text operators) andDo(image operators) in content streams - 4Classify based on text operator presence across sampled pages
This detects 300+ page PDFs in milliseconds. The result includes pages_needing_ocr — a list of specific page numbers that lack text, enabling per-page OCR routing instead of all-or-nothing.
Scan strategies
| Strategy | Behavior | Best for |
|---|---|---|
EarlyExit (default) | Scan all pages, stop on first non-text page | Pipelines routing TextBased PDFs to fast extraction |
Full | Scan all pages, no early exit | Accurate Mixed vs Scanned classification |
Sample(n) | Sample n evenly distributed pages (first, last, middle) | Very large PDFs where speed matters more than precision |
Pages(vec) | Only scan specific 1-indexed page numbers | When the caller knows which pages to check |
Markdown output
The converter handles:
| Element | How it's detected |
|---|---|
| Headings (H1-H4) | Font size tiers relative to body text, with 0.5pt clustering |
| Bold/italic | Font name patterns (Bold, Italic, Oblique) |
| Bullet lists | •, -, *, ○, ●, ◦ prefixes |
| Numbered lists | 1., 1), (1) patterns |
| Letter lists | a., a), (a) patterns |
| Code blocks | Monospace fonts (Courier, Consolas, Monaco, Menlo, Fira Code, JetBrains Mono) and keyword detection |
| Tables | Rectangle-based detection from PDF drawing ops + heuristic detection from text alignment |
| Financial tables | Token splitting for consolidated numeric values |
| Captions | "Figure", "Table", "Source:" prefix detection |
| Sub/superscript | Font size and Y-offset relative to baseline |
| URLs | Converted to Markdown links |
| Hyphenation | Rejoins words broken across lines |
| Page numbers | Filtered from output |
| Drop caps | Large initial letters merged with following text |
| Dot leaders | TOC-style dots collapsed to " ... " |
Use case: smart PDF routing
pdf-inspector was built for pipelines that process PDFs at scale. Instead of sending every PDF through OCR:
PDF arrives
→ pdf-inspector classifies it (~20ms)
→ TextBased + high confidence?
YES → extract locally (~150ms), done
NO → send to OCR service (2-10s)This saves cost and latency for the majority of PDFs that are already text-based (reports, papers, invoices, legal docs).
Debugging
See docs/debugging.md for RUST_LOG environment variable usage.
