Introduction: The Pain Points of PDF Parsing
As developers, processing PDFs is an unavoidable requirement — invoice parsing, contract extraction, paper crawling, data archiving. Nearly every backend project bumps into this at some point. But PDF parsing has long been a headache:
- OCR services are expensive: Calling cloud OCR APIs costs a few cents per page, and the bill adds up fast at scale
- Open-source solutions are slow: PyMuPDF4LLM takes 17 seconds to process 200 documents, and MarkItDown takes 16 seconds
- Formatting gets lost: Traditional extraction tools spit out plain text — heading hierarchy, table structure, and code blocks are all gone
- No smart routing: They can't automatically distinguish scanned PDFs from native text PDFs, so even documents that don't need OCR still go through the OCR pipeline
In July 2026, the well-known web crawling platform Firecrawl open-sourced a Rust-based PDF parsing library called pdf-inspector. Within just a few weeks, it racked up 7,900+ Stars and landed in the top three of GitHub Trending's daily list. It promises to complete local PDF classification and text extraction within 200ms, automatically outputting formatted Markdown — no OCR, no external services needed.
Today, let's take a deep dive into this project and see how it manages to outperform mainstream solutions.
What Is pdf-inspector?
pdf-inspector is a PDF classification and text extraction library written in Rust by the Firecrawl team. Its core capabilities include:
- Smart classification — Detects PDF type in 10–50ms: native text (TextBased), scanned (Scanned), image-based (ImageBased), or mixed (Mixed), returning confidence scores and per-page OCR routing suggestions
- Text extraction — Position-aware text extraction with font information, X/Y coordinates, and automatic multi-column reading order
- Markdown conversion — Automatically recognizes H1–H4 headings, lists, code blocks (via monospace font detection), tables, bold/italic, URL links, and page breaks
- Table detection — Dual-mode table detection (rectangle detection based on PDF drawing operations + heuristic detection based on text alignment), handling financial reports and cross-page tables flawlessly
- CID font support — ToUnicode CMap decoding, supporting Type0/Identity-H fonts, UTF-16BE, UTF-8, and Latin-1 encoding
- Multi-language bindings — Provides Python, Node.js, and browser WebAssembly bindings
Why Rust? Rust's zero-cost abstractions and memory safety let pdf-inspector achieve extreme parsing speed without relying on ML models or external services. The entire library has just one external dependency: lopdf (a Rust PDF parsing library).
Performance Comparison
Based on official benchmarks on Apple M4 Pro (200 documents, opendataloader-bench corpus):
| Engine | Composite Score | Reading Order | Table Recognition | Heading Recognition | 200 Docs Time |
|---|---|---|---|---|---|
| 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.169s |
| MarkItDown | 0.589 | 0.844 | 0.273 | 0.000 | 16.117s |
pdf-inspector leads across the board in composite score, reading order, table recognition, and speed — taking just 1/36 of PyMuPDF4LLM's time and 1/34 of MarkItDown's.
Installation
pdf-inspector supports multiple installation methods, covering Rust, Python, Node.js, and browser environments.
Rust (Native)
Add the dependency in your Cargo.toml:
[dependencies]
pdf-inspector = "0.2"
Or install via the cargo CLI:
cargo add pdf-inspector
Python
pdf-inspector provides Python bindings via maturin:
pip install maturin
git clone https://github.com/firecrawl/pdf-inspector.git
cd pdf-inspector
maturin develop --release
💡 Note: pdf-inspector hasn't been published to PyPI yet, so you'll need to build from source. The Firecrawl team plans to release a PyPI package once it's stable.
Node.js
npm install @firecrawl/pdf-inspector
Browser WebAssembly
npm install @firecrawl/pdf-inspector-wasm
The Wasm version embeds the full Rust parser in the browser and can run in Web Workers without server round-trips.
Quick Start
Python Example
After installation, it's dead simple — just one function call:
import pdf_inspector
# Process a single PDF file
result = pdf_inspector.process_pdf("document.pdf")
# View the PDF type classification
print(f"PDF type: {result.pdf_type}")
# Output: "text_based", "scanned", "image_based", or "mixed"
# Get the Markdown output
if result.markdown:
print(result.markdown)
That's it. One line of code completes classification + extraction + Markdown conversion.
Node.js Example
const { processPdf } = require('@firecrawl/pdf-inspector');
const fs = require('fs');
async function analyzePdf(filePath) {
const pdfBuffer = fs.readFileSync(filePath);
const result = await processPdf(pdfBuffer);
console.log('PDF type:', result.pdfType);
console.log('Confidence:', result.confidence);
console.log('--- Markdown output ---');
console.log(result.markdown);
}
analyzePdf('report.pdf');
Rust Native Example
use pdf_inspector::{PdfInspector, ProcessPdfResult};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let inspector = PdfInspector::new();
let result: ProcessPdfResult = inspector.process_pdf_path("document.pdf")?;
println!("PDF type: {:?}", result.pdf_type);
println!("Confidence: {:.2}", result.confidence);
if let Some(markdown) = result.markdown {
println!("Markdown:\n{}", markdown);
}
Ok(())
}
Core Features Explained
1. Smart Classification: Avoid Unnecessary OCR Overhead
One of pdf-inspector's most practical features is automatic classification. By sampling the PDF content stream, it determines the document type in 10–50ms:
| Type | Description | Recommended Strategy |
|---|---|---|
text_based |
Native text PDF with extractable text content streams | Extract directly with pdf-inspector, skip OCR |
scanned |
Scanned document (image-based pages) | Needs an OCR service |
image_based |
Image-heavy PDFs (e.g., screenshot collections) | Needs OCR or image processing |
mixed |
Mixed types (some pages have text, some are scanned) | Decide per page: extract text pages directly, call OCR for scanned pages |
Real-world scenario: Building a smart PDF processing pipeline
import pdf_inspector
# Assuming you have your own OCR function
def smart_pdf_pipeline(pdf_path, ocr_function):
result = pdf_inspector.process_pdf(pdf_path)
if result.pdf_type == "text_based":
# Native text — extract directly: fast, free, accurate
print("✅ Native text PDF, direct extraction")
return result.markdown
elif result.pdf_type == "scanned":
# Scanned — call OCR
print("🔍 Scanned PDF, calling OCR")
return ocr_function(pdf_path)
elif result.pdf_type == "mixed":
# Mixed — process page by page
print("📄 Mixed PDF, per-page routing")
pages = []
for page_info in result.page_results:
if page_info.needs_ocr:
pages.append(ocr_function_for_page(pdf_path, page_info.page_number))
else:
pages.append(page_info.markdown)
return "\n\n".join(pages)
else:
# Pure image-based — cannot extract text
print("❌ Image-based PDF, cannot extract text")
return None
This pipeline lets you completely skip OCR for ~54% of PDFs (the native text ones), significantly cutting processing costs and latency.
2. Markdown Conversion: Preserving Document Structure
pdf-inspector's Markdown output isn't just plain text — it intelligently identifies and preserves document structure:
- Heading hierarchy (H1–H4): Automatically inferred from font size ratios
- Lists: Bullet lists, numbered lists, and letter-numbered lists recognized automatically
- Code blocks: Marked as code blocks via monospace font detection
- Tables: Supports both rectangle detection and heuristic detection, handling financial reports and cross-page tables
- Bold/italic: Formatting preserved
- URL links: Automatically identified and converted to Markdown link format
- Page breaks: Separated with
---between pages
Here's what it looks like in practice. Say you have an academic paper PDF:
# Deep Learning Approaches for Natural Language Processing
## 1. Introduction
Natural language processing (NLP) has seen remarkable progress in recent years...
### 1.1 Background
The transformer architecture, introduced by Vaswani et al., has become...
## 2. Methodology
| Model | BLEU Score | Training Time |
|-------|-----------|---------------|
| Transformer | 38.2 | 12 hours |
| BERT | 41.0 | 24 hours |
| GPT-4 | 45.7 | 72 hours |
## 3. Results
The experimental results demonstrate...
> **Note:** All experiments were conducted on...
Heading hierarchy, tables, and block quotes are all preserved intact. That's a huge step up from traditional PDF extractors that only output plain text.
3. Multi-Column and Reading Order Detection
Many PDFs — like papers, newspapers, and magazines — use multi-column layouts. pdf-inspector automatically detects column structures and extracts in the correct reading order:
result = pdf_inspector.process_pdf("newspaper.pdf")
# pdf-inspector automatically detects two-column / three-column layouts
# and outputs in the correct top-to-bottom, left-to-right reading order
print(result.markdown)
It also supports RTL (right-to-left) text, suitable for Arabic, Hebrew, and other scripts.
4. CID Fonts and Encoding Issue Detection
When handling Chinese, Japanese, or Korean PDFs, font encoding issues are common. pdf-inspector supports ToUnicode CMap decoding and can correctly handle:
- Type0 / Identity-H fonts
- UTF-16BE, UTF-8, and Latin-1 encoding
- Automatic flagging of broken font encodings
This means when processing CJK PDFs, it can tell you which pages might have garbled text risk, letting you decide in advance whether to fall back to OCR.
Advanced Practice: Building a PDF Processing Service
Let's build a complete PDF processing microservice that integrates pdf-inspector's smart classification capability.
Scenario: Document Archiving System
Say you're building an internal enterprise document archiving system that needs to process hundreds of PDFs daily (contracts, invoices, reports, etc.):
# pdf_service.py
import os
import json
from datetime import datetime
import pdf_inspector
class PdfProcessingService:
def __init__(self, output_dir="./processed"):
self.output_dir = output_dir
os.makedirs(output_dir, exist_ok=True)
def process_batch(self, pdf_files):
"""Batch process PDF files"""
results = []
for pdf_path in pdf_files:
result = self.process_single(pdf_path)
results.append(result)
return results
def process_single(self, pdf_path):
"""Process a single PDF file"""
filename = os.path.basename(pdf_path)
pdf_result = pdf_inspector.process_pdf(pdf_path)
# Extract metadata
metadata = {
"filename": filename,
"pdf_type": pdf_result.pdf_type,
"confidence": pdf_result.confidence,
"processed_at": datetime.now().isoformat(),
"pages_count": len(pdf_result.page_results) if hasattr(pdf_result, 'page_results') else 0,
"needs_ocr": pdf_result.pdf_type in ("scanned", "image_based"),
}
# Save Markdown output
if pdf_result.markdown:
md_filename = filename.replace(".pdf", ".md")
md_path = os.path.join(self.output_dir, md_filename)
with open(md_path, "w", encoding="utf-8") as f:
f.write(pdf_result.markdown)
metadata["markdown_path"] = md_path
# Save metadata
meta_path = os.path.join(
self.output_dir,
filename.replace(".pdf", ".meta.json")
)
with open(meta_path, "w", encoding="utf-8") as f:
json.dump(metadata, f, ensure_ascii=False, indent=2)
return metadata
# Usage example
service = PdfProcessingService(output_dir="./output")
# Batch processing
pdf_files = [
"contracts/contract_2026_001.pdf",
"invoices/invoice_aug_2026.pdf",
"reports/q2_financial_report.pdf",
]
results = service.process_batch(pdf_files)
for r in results:
status = "Needs OCR" if r["needs_ocr"] else "Extracted directly"
print(f"{r['filename']}: {r['pdf_type']} ({status})")
Scenario: Integration with LLM Document Analysis Pipelines
The high-quality Markdown output from pdf-inspector can be fed directly to LLMs for analysis:
import pdf_inspector
# Assuming you use an OpenAI-compatible API
from openai import OpenAI
def analyze_pdf_with_llm(pdf_path, prompt="Summarize the main content of this document"):
"""Analyze PDF content using an LLM"""
# 1. Extract Markdown with pdf-inspector
result = pdf_inspector.process_pdf(pdf_path)
if not result.markdown:
return "Could not extract text content; OCR may be needed"
# 2. If the document is long, truncate to the first 8000 characters
markdown_content = result.markdown[:8000]
# 3. Call the LLM for analysis
client = OpenAI(api_key="YOUR_API_KEY")
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a document analysis assistant."},
{"role": "user", "content": f"Please {prompt} based on the following document:\n\n{markdown_content}"}
]
)
return response.choices[0].message.content
# Usage
summary = analyze_pdf_with_llm("annual_report.pdf", "extract key financial metrics")
print(summary)
This kind of pipeline is especially useful in RAG (Retrieval-Augmented Generation) systems — quickly extract structured text with pdf-inspector, then vectorize and store it in a knowledge base.
Comparison with Other Solutions
pdf-inspector vs PyMuPDF4LLM
| Dimension | pdf-inspector | PyMuPDF4LLM |
|---|---|---|
| Language | Rust | Python (C bindings) |
| Composite Score | 0.875 | 0.735 |
| Table Recognition | 0.814 | 0.401 |
| Speed (200 docs) | 0.47s | 17.169s |
| Markdown Structure | ✅ Fully preserved | ⚠️ Partially lost |
| Smart Classification | ✅ Built-in | ❌ None |
| Browser Wasm | ✅ Supported | ❌ Not supported |
pdf-inspector vs MarkItDown (Microsoft)
| Dimension | pdf-inspector | MarkItDown |
|---|---|---|
| Composite Score | 0.875 | 0.589 |
| Heading Recognition | 0.788 | 0.000 |
| Speed (200 docs) | 0.47s | 16.117s |
| Multi-language Bindings | Python/Node/Wasm | Python |
| Focus | Dedicated PDF parsing | General document conversion |
When to Use pdf-inspector vs When to Use OCR?
- Use pdf-inspector: Native text PDFs, reports, papers, contracts, invoices — any document with text content streams
- Use OCR: Scanned documents, pure image PDFs, handwritten documents
- Use both together: Classify with pdf-inspector first, extract
text_baseddirectly, routescanned/image_basedto OCR — that's exactly the routing strategy pdf-inspector was designed for
Project Info & Community
- GitHub: firecrawl/pdf-inspector
- Stars: 7,900+ (launched July 2026, growing fast)
- License: Apache-2.0 (commercial-friendly)
- Language: Rust
- Ecosystem: By Firecrawl (well-known web crawling platform, also the open-source force behind the firecrawl crawler engine)
Installation Channels
| Platform | Installation |
|---|---|
| Rust | crates.io/crates/pdf-inspector |
| Python | Source build (maturin develop --release) |
| Node.js | npm install @firecrawl/pdf-inspector |
| Browser | npm install @firecrawl/pdf-inspector-wasm |
Summary
pdf-inspector fills an important gap — a lightweight, fast, localized PDF parsing solution. It doesn't need ML models or external services. Pure Rust implementation delivers accuracy well beyond comparable tools at a fraction of the time.
Key advantages at a glance:
- 🚀 Blazing fast — 200ms-level parsing, 36× faster than PyMuPDF4LLM
- 🧠 Smart classification — Automatically distinguishes text/scanned/image PDFs to optimize OCR routing
- 📐 Structure preserved — Headings, tables, code blocks, lists all intact as Markdown
- 🔒 100% local — No external dependencies, no API calls, your data never leaves your machine
- 🌐 Multi-platform — Rust/Python/Node.js/browser Wasm, full coverage
If you're building document processing pipelines, RAG systems, or any application that needs to parse PDFs, pdf-inspector deserves a spot in your tech stack. Especially for scenarios dominated by native text PDFs, it can save you a ton of OCR cost and latency.
🔗 Related Links - GitHub: firecrawl/pdf-inspector - Python Docs: docs/python.md - Benchmarks: opendataloader-bench - Firecrawl: firecrawl.dev