edgeparse
Library Listed
EdgeParse core library — PDF parsing and structured data extraction
- Downloads
- 6,764
- Dependents
- 2
- Stars
- 155
- Forks
- 20
- Contributors
- 2
- Commits
- 135
- Latest release
- 0.3.2 ·
About
Fastest PDF extraction engine. Rust-native. No ML stack required for born-digital PDFs; optional in-browser OCR for image tables.
Extract Markdown, JSON (with bounding boxes), and HTML from any born-digital PDF — deterministically, in milliseconds, on CPU.
- How accurate is it? — Two labeled boards (2026-10-01, Apple M4 Max, 200 docs):
- Official odl-bench (
mean(NID,TEDS,MHS)): EdgeParse hybrid 0.900 (leads every finished metric), deterministic 0.857, WASM 0.854. - EdgeParse harness: deterministic 0.782 overall at ~0.019 s/doc. Never claim a win for engines not finished (Marker, MinerU, Nutrient, Unstructured partial). Benchmark details
- Official odl-bench (
- How fast? — ~0.02 s/doc deterministic on Apple M4 Max for the 200-document corpus. Hybrid mode is slower (~0.81 s/doc) when pages route to a local Docling Fast backend.
- Does it need a GPU or Java? — No. No JVM, no GPU, no Python runtime for the CLI. Born-digital PDFs need no OCR models; optional PP-OCRv6 runs in-browser or on Node for image tables. Single ~15 MB binary.
- RAG / LLM pipelines? — Yes. Outputs structured Markdown for chunking, JSON with bounding boxes for citations, preserves reading order across multi-column layouts. See integration examples
Read the full README
Available as a Rust library, CLI binary, Python package, Node.js package, WebAssembly module, and @edgeparse/web observable Web SDK.
Table of Contents
- Get Started in 30 Seconds
- What Problems Does This Solve?
- Release Channels
- Benchmark
- Capability Matrix
- Installation
- Python SDK
- Node.js SDK
- WebAssembly SDK
- CLI Reference
- Output Formats
- RAG / LLM Integration
- Agent Skill
- Architecture
- FAQ
- Tutorials
- Documentation
- Project Layout
- Contributing
- License
Get Started in 30 Seconds
Python (Python 3.9+):
pip install edgeparse
import edgeparse
# Markdown — ready for LLM context or RAG chunking
md = edgeparse.convert("report.pdf", format="markdown")
# JSON with bounding boxes — for citations and element-level control
data = edgeparse.convert("report.pdf", format="json")
CLI (Rust 1.85+):
cargo install edgeparse-cli
edgeparse report.pdf --format markdown --output-dir output/
Node.js (Node 18+):
npm install edgeparse
import { convert } from 'edgeparse';
const md = convert('report.pdf', { format: 'markdown' });
Release Channels
Tagged releases publish every supported distribution target through GitHub Actions:
| Channel | Artifact | Install / Pull |
|---|---|---|
| Rust crates | pdf-cos, edgeparse-core, edgeparse-cli | cargo install edgeparse-cli |
| Python SDK | edgeparse wheels + sdist | pip install edgeparse |
| Node.js SDK | edgeparse + 5 platform addons | npm install edgeparse |
| WebAssembly SDK | edgeparse-wasm | npm install edgeparse-wasm |
| CLI binaries | GitHub Release archives for macOS, Linux, Windows | GitHub Releases |
| Homebrew | raphaelmansuy/edgeparse tap | brew tap raphaelmansuy/edgeparse && brew install edgeparse |
| Containers | GHCR + Docker Hub multi-arch images | docker pull ghcr.io/raphaelmansuy/edgeparse:0.2.1 |
Release automation and registry details: docs/07-cicd-publishing.md
What Problems Does This Solve?
| Problem | EdgeParse Solution | Status |
|---|---|---|
| PDF text loses reading order in multi-column layouts | XY-Cut++ algorithm preserves correct reading sequence across columns, sidebars, and mixed layouts | ✅ Shipped |
| Table extraction is broken (merged cells, borderless tables) | Ruling-line table detection + borderless cluster method; --table-method cluster for complex cases | ✅ Shipped |
| OCR/ML tools add 500 MB+ of dependencies to a simple PDF pipeline | Zero GPU, zero OCR models, zero JVM — single 15 MB binary, pure Rust | ✅ Shipped |
| Heading hierarchy is lost (all text looks the same) | Font-metric + outline/section-number heading levels; MHS score 0.552 on the current benchmark | ✅ Shipped |
| PDFs can carry hidden prompt injection payloads | AI safety filters: hidden text, off-page content, tiny-text, invisible OCG layers detected and stripped | ✅ Shipped |
| Need bounding boxes to cite sources in RAG answers | Every element (paragraph, heading, table, image) has [left, bottom, right, top] coordinates in PDF points | ✅ Shipped |
| In-browser PDF parsing uploads data to a server | WebAssembly build — full Rust engine in the browser, PDF data never leaves the device | ✅ Shipped |
Benchmark
Evaluated on 200 real-world PDFs — academic papers, financial reports, multi-column layouts, complex tables, mixed-language documents — running on Apple M4 Max.
Current comparison set
Measured 2026-10-01 on Apple M4 Max, 200 documents, EdgeParse harness (benchmark/). Marker / MinerU / Unstructured hi_res were not finished in this run (install/time). Nutrient and Hancom were out of scope.
| Engine | NID ↑ | TEDS ↑ | MHS ↑ | PBF ↑ | TQS ↑ | TD F1 ↑ | Speed ↓ | Overall ↑ |
|---|---|---|---|---|---|---|---|---|
| EdgeParse | 0.886 | 0.564 | 0.552 | 0.562 | 0.917 | 0.897 | ~0.02 s/doc | 0.782 |
| EdgeParse [hybrid] | 0.863 | 0.603 | 0.506 | 0.546 | 0.909 | 0.941 | 0.810 s/doc | 0.767 |
| Docling | 0.875 | 0.568 | 0.450 | 0.563 | 0.916 | 0.903 | 2.332 s/doc | 0.758 |
| OpenDataLoader | 0.873 | 0.320 | 0.441 | 0.536 | 0.917 | 0.621 | 0.022 s/doc | 0.733 |
| PyMuPDF4LLM | 0.860 | 0.509 | 0.411 | 0.503 | 0.912 | 0.857 | 0.640 s/doc | 0.732 |
| OpenDataLoader [hybrid] | 0.869 | 0.422 | 0.411 | 0.531 | 0.904 | 0.941 | 2.537 s/doc | 0.731 |
| MarkItDown | 0.808 | 0.193 | 0.001 | 0.362 | 0.861 | 0.558 | 0.189 s/doc | 0.564 |
Deterministic EdgeParse leads overall. Hybrid mode routes table / sparse-image pages to a local Docling Fast server (opendataloader-pdf-hybrid) and leads TEDS and table-detection F1 while still beating every finished third-party engine on overall score.
When to choose what:
| Use case | Recommendation |
|---|---|
| Born-digital PDFs, latency matters, production deployment | EdgeParse — best overall accuracy/speed, zero ML stack |
| Complex tables, local Docling Fast available | EdgeParse --hybrid docling-fast — best TEDS on this board |
| Scanned documents requiring full OCR | Dedicated OCR pipeline or hybrid with OCR-enabled backend |
Metrics
| Metric | What it measures |
|---|---|
| NID | Reading order accuracy — normalised index distance |
| TEDS | Table structure accuracy — tree-edit distance vs. ground truth |
| MHS | Heading hierarchy accuracy |
| PBF | Paragraph boundary F1 |
| TQS | Text quality score |
| TD F1 | Table detection F1 |
| Overall | Normalized aggregate benchmark score |
| Speed | Wall-clock seconds per document (full pipeline, 200 docs, parallel) |
Running the benchmark
cargo build --release
cd benchmark
uv sync
uv run python run.py # EdgeParse on all 200 docs
uv run python compare_all.py # Compare against 9 engines
Results → benchmark/prediction/edgeparse/ · HTML reports → benchmark/reports/
Regression thresholds
benchmark/thresholds.json defines minimum acceptable scores for CI:
{
"nid": 0.85,
"teds": 0.40,
"mhs": 0.55,
"table_detection_f1": 0.55,
"elapsed_per_doc": 2.0
}
Capability Matrix
| Capability | Available | Notes |
|---|---|---|
| Extraction | ||
| Text with correct reading order | ✅ | XY-Cut++ across columns and sidebars |
| Bounding boxes for every element | ✅ | [left, bottom, right, top] in PDF points |
| Table extraction — ruling-line borders | ✅ | Default mode |
| Table extraction — borderless/cluster | ✅ | --table-method cluster |
| Heading hierarchy detection | ✅ | Numbered + unnumbered, all levels |
| List detection (numbered, bulleted, nested) | ✅ | |
| Image extraction with coordinates | ✅ | PNG or JPEG, embedded or external |
| Header / footer / watermark filtering | ✅ | |
| Multi-column layout support | ✅ | |
| CMap / ToUnicode font decoding | ✅ | Handles non-standard encodings |
| Tagged PDF structure tree | ✅ | --use-struct-tree preserves author intent |
| Safety | ||
| Hidden text filtering | ✅ | Prompt injection protection |
| Off-page content filtering | ✅ | |
| Tiny-text / invisible OCG layer filtering | ✅ | |
| PII sanitization | ✅ | --sanitize flag |
| Output | ||
| Markdown (GFM tables) | ✅ | |
| JSON with bounding boxes | ✅ | |
| HTML5 | ✅ | |
| Plain text (reading order preserved) | ✅ | |
| SDKs | ||
| Python (PyO3 native extension) | ✅ | Python 3.9+, pre-built wheels |
| Node.js (NAPI-RS native addon) | ✅ | Node 18+, pre-built addons |
| WebAssembly | ✅ | In-browser, no server required |
| Rust library | ✅ | edgeparse-core crate |
| CLI binary | ✅ | edgeparse-cli on crates.io |
| Runtime | ||
| GPU required | ❌ No | CPU only |
| JVM required | ❌ No | Pure Rust |
| OCR models required | ❌ No | Born-digital PDFs only |
| Parallel processing | ✅ | Rayon per-page parallelism |
| Deterministic / reproducible | ✅ | Same input → same output, always |
Installation
Python
pip install edgeparse
Python 3.9+. Pre-built wheels for macOS (arm64, x64), Linux (x64, arm64), Windows (x64).
CLI
cargo install edgeparse-cli
Requires Rust 1.85+.
Rust library
[dependencies]
edgeparse-core = "0.2.1"
Docs: docs.rs/edgeparse-core · docs.rs/edgeparse-cli
Node.js
npm install edgeparse
Node 18+. Pre-built native addons for macOS (arm64, x64), Linux (x64, arm64), Windows (x64).
Build from source
git clone https://github.com/raphaelmansuy/edgeparse.git
cd edgeparse
cargo build --release
# Binary: target/release/edgeparse
System requirements
- macOS 12+, Linux (glibc 2.17+), or Windows 10+
- ~15 MB binary (stripped release build)
- No Java, no Python (for the CLI), no GPU
Python SDK
Package: edgeparse · Requires: Python 3.9+ · Source: sdks/python/
edgeparse.convert()
def convert(
input_path: str | Path,
*,
format: str = "markdown", # "markdown", "json", "html", "text"
pages: str | None = None, # e.g. "1,3,5-7"
password: str | None = None,
reading_order: str = "xycut", # "xycut" or "off"
table_method: str = "default", # "default" or "cluster"
image_output: str = "off", # "off", "embedded", "external"
) -> str: ...
edgeparse.convert_file()
def convert_file(
input_path: str | Path,
output_dir: str | Path = "output",
*,
format: str = "markdown",
pages: str | None = None,
password: str | None = None,
) -> str: ... # returns output file path
Examples
import edgeparse
# Basic Markdown extraction
md = edgeparse.convert("report.pdf", format="markdown")
# JSON with bounding boxes
json_str = edgeparse.convert("report.pdf", format="json")
# Password-protected, specific pages, borderless tables
md = edgeparse.convert(
"secure.pdf",
format="markdown",
pages="1,3,5-7",
password="secret",
reading_order="xycut",
table_method="cluster",
)
# Write output file
out_path = edgeparse.convert_file("report.pdf", output_dir="output/", format="markdown")
CLI entry point (Python package)
edgeparse report.pdf -f markdown -o output/
edgeparse *.pdf --format json --output-dir out/ --pages "1-3"
Node.js SDK
Package: edgeparse · Requires: Node.js 18+ · Source: sdks/node/
convert()
import { convert } from 'edgeparse';
// Markdown
const md = convert('report.pdf', { format: 'markdown' });
// JSON with bounding boxes
const json = convert('report.pdf', { format: 'json' });
// With options
const result = convert('report.pdf', {
format: 'markdown',
pages: '1-5',
readingOrder: 'xycut',
tableMethod: 'cluster',
});
ConvertOptions
interface ConvertOptions {
format?: 'markdown' | 'json' | 'html' | 'text'; // default: "markdown"
pages?: string; // e.g. "1,3,5-7"
password?: string;
readingOrder?: 'xycut' | 'off'; // default: "xycut"
tableMethod?: 'default' | 'cluster'; // default: "default"
imageOutput?: 'off' | 'embedded' | 'external'; // default: "off"
}
CLI (npm)
npx edgeparse report.pdf -f markdown -o output.md
npx edgeparse report.pdf --format json --pages "1-5"
WebAssembly SDK
EdgeParse compiles to WebAssembly — client-side PDF extraction in any modern browser with no server, no uploads, and no backend infrastructure.
- Same Rust engine, identical output to CLI/Python/Node
- PDF data never leaves the user's device (privacy by design)
- Works offline after initial WASM load (~4 MB cached)
- Optional PP-OCRv6 for image-embedded tables via
@edgeparse/web(consent + OPFS/Cache) - Zero infrastructure cost — static hosting only
Preferred: @edgeparse/web (observable SDK)
npm install @edgeparse/web edgeparse-wasm
import { EdgeParse } from '@edgeparse/web';
const ep = await EdgeParse.create({
models: 'lazy',
ocr: 'small',
onBeforeDownload: async (m) =>
confirm(`Download OCR model ${m.id} (~${(m.bytes / 1e6).toFixed(1)} MB)?`),
});
const job = ep.parse(pdfBytes, { format: 'markdown' });
job.on('progress', (p) => console.log(p.label, p.fraction));
const { markdown, quality, warnings } = await job.result;
Docs: quick-start Web SDK · OCR models · API
Low-level WASM
import init, { convert_to_string, ParseSession } from 'edgeparse-wasm';
await init();
const bytes = new Uint8Array(await file.arrayBuffer());
const markdown = convert_to_string(bytes, 'markdown');
Live demo
edgeparse.com/demo/ — drag-and-drop any PDF, all processing runs locally in your browser.
Build from source
make wasm-build # --target web → crates/edgeparse-wasm/pkg/
make wasm-build-node # --target nodejs → pkg-node/ (bench)
Full documentation: docs/09-wasm-sdk.md
CLI Reference
edgeparse [OPTIONS] <PDF_FILE>...
Core options
| Flag | Default | Description |
|---|---|---|
-o, --output-dir <DIR> | — | Write output files to this directory |
-f, --format <FMT> | json | Output format(s), comma-separated |
-p, --password <PW> | — | Password for encrypted PDFs |
--pages <RANGE> | — | Page range e.g. "1,3,5-7" |
-q, --quiet | false | Suppress log output |
Output format values
| Value | Description |
|---|---|
json | Structured JSON with bounding boxes and element types (default) |
markdown | Standard Markdown with GFM tables |
markdown-with-html | Markdown with HTML table fallback for complex tables |
markdown-with-images | Markdown with embedded or linked images |
html | Full HTML5 document with semantic elements |
text | Plain UTF-8 text, reading order preserved |
Multiple formats: --format markdown,json
Layout & extraction options
| Flag | Default | Description |
|---|---|---|
--reading-order <ALGO> | xycut | Reading order: xycut or off |
--table-method <METHOD> | default | Table detection: default (ruling lines) or cluster (borderless) |
--keep-line-breaks | false | Preserve original line breaks within paragraphs |
--use-struct-tree | false | Use tagged PDF structure tree when available |
--include-header-footer | false | Include headers and footers in output |
--sanitize | false | Enable PII sanitization |
--replace-invalid-chars <CH> | " " | Replacement for invalid Unicode characters |
Image options
| Flag | Default | Description |
|---|---|---|
--image-output <MODE> | external | off, embedded (base64), or external (files) |
--image-format <FMT> | png | png or jpeg |
--image-dir <DIR> | — | Directory for extracted image files |
Separator options
| Flag | Description |
|---|---|
--markdown-page-separator <STR> | String inserted between Markdown pages |
--text-page-separator <STR> | String inserted between plain-text pages |
--html-page-separator <STR> | String inserted between HTML pages |
Content safety options
| Flag | Default | Description |
|---|---|---|
--content-safety-off <FLAGS> | — | Disable safety filters: all, hidden-text, off-page, tiny, hidden-ocg |
Hybrid backend options
| Flag | Default | Description |
|---|---|---|
--hybrid <BACKEND> | off | Hybrid backend: off or docling-fast |
--hybrid-mode <MODE> | auto | Triage mode: auto or full |
--hybrid-url <URL> | — | Hybrid backend service URL |
--hybrid-timeout <MS> | 30000 | Timeout in milliseconds |
--hybrid-fallback | false | Fall back to local extraction on hybrid error |
Output Formats
| Format | Flag value | Best for |
|---|---|---|
| JSON with bounding boxes | json | RAG citations, element-level processing, source highlighting |
| Markdown (GFM) | markdown | LLM context windows, chunking pipelines, readable output |
| Markdown + HTML tables | markdown-with-html | Complex tables that don't render well in pure Markdown |
| Markdown + images | markdown-with-images | Documents where figures matter |
| HTML5 | html | Web display, accessibility pipelines |
| Plain text | text | Keyword search, simple NLP, legacy pipelines |
Combine formats: --format markdown,json
JSON output example
{
"type": "heading",
"id": 42,
"level": "Title",
"page number": 1,
"bounding box": [72.0, 700.0, 540.0, 730.0],
"heading level": 1,
"font": "Helvetica-Bold",
"font size": 24.0,
"content": "Introduction"
}
| Field | Description |
|---|---|
type | Element type: heading, paragraph, table, list, image, caption |
id | Unique identifier for cross-referencing |
page number | 1-indexed page reference |
bounding box | [left, bottom, right, top] in PDF points (72 pt = 1 inch) |
heading level | Heading depth (1+) |
content | Extracted text |
RAG / LLM Integration
EdgeParse is designed for AI pipelines. Every element has a bounding box and page number, so you can cite exact sources in answers.
Extract Markdown for chunking
import edgeparse
md = edgeparse.convert("report.pdf", format="markdown")
# Feed directly into an LLM
response = llm.invoke(f"Summarize this document:\n\n{md}")
Extract JSON for source citations
import json, edgeparse
data = json.loads(edgeparse.convert("report.pdf", format="json"))
for element in data["kids"]:
if element["type"] == "paragraph":
# element["bounding box"] → highlight location in original PDF
# element["page number"] → link back to source page
print(f"p.{element['page number']}: {element['content'][:80]}")
LangChain integration
EdgeParse has no official LangChain loader yet, but integrates trivially:
from langchain.schema import Document
import edgeparse, json
def load_pdf(path: str) -> list[Document]:
data = json.loads(edgeparse.convert(path, format="json"))
docs = []
for el in data["kids"]:
if el["type"] in ("paragraph", "heading"):
docs.append(Document(
page_content=el["content"],
metadata={
"source": path,
"page": el["page number"],
"bbox": el["bounding box"],
"type": el["type"],
}
))
return docs
LlamaIndex integration
from llama_index.core import Document
import edgeparse, json
def edgeparse_reader(path: str) -> list[Document]:
data = json.loads(edgeparse.convert(path, format="json"))
return [
Document(
text=el["content"],
metadata={"page": el["page number"], "source": path}
)
for el in data["kids"]
if el.get("content")
]
Which output format for which use case?
| Use case | Recommended format | Why |
|---|---|---|
| Feed PDF to LLM | markdown | Clean structure, fits in context window |
| RAG with source citations | json | Bounding boxes enable "click-to-source" UX |
| Semantic chunking by section | markdown | Headings make natural chunk boundaries |
| Element-level filtering | json | Filter by type, page number, heading level |
| Web display | html | Styled output with semantic elements |
Agent Skill
EdgeParse ships as a Claude agent skill — a structured description that teaches Claude (and any compatible AI agent) how to extract PDF content on behalf of users.
# Add the EdgeParse skill to your agent environment
npx skills add raphaelmansuy/edgeparse --skill edgeparse
# Install the Python package
pip install edgeparse
The npx skills add command registers the skill in skills-lock.json:
{
"version": 1,
"skills": {
"edgeparse": {
"source": "raphaelmansuy/edgeparse",
"sourceType": "github"
}
}
}
Once installed, the agent reads skills/edgeparse/SKILL.md and knows when to call edgeparse.convert(), which format to use for different tasks, and how to handle edge cases like encrypted PDFs, borderless tables, and multi-column layouts.
See docs/08-agent-skill.md for the full integration guide (LangChain, LlamaIndex, MCP, CrewAI patterns).
Architecture
Crate structure
edgeparse/
├── crates/
│ ├── pdf-cos/ # Low-level PDF object model (fork of lopdf 0.39)
│ ├── edgeparse-core/ # Core extraction engine (~90 source files)
│ │ └── src/
│ │ ├── api/ # ProcessingConfig, FilterConfig, BatchResult
│ │ ├── pdf/ # Loader, content stream parser, font/CMap decoding
│ │ ├── models/ # ContentElement, BoundingBox, TextChunk, PdfDocument
│ │ ├── pipeline/ # 20-stage orchestrator + Rayon parallel helpers
│ │ ├── output/ # Renderers: JSON, Markdown, HTML, text, CSV
│ │ ├── tagged/ # Tagged-PDF structure tree → McidMap
│ │ └── utils/ # XY-Cut++ algorithm, sanitizer, layout analysis
│ ├── edgeparse-cli/ # CLI binary (clap derive, 25+ flags)
│ ├── edgeparse-python/ # PyO3 native extension
│ └── edgeparse-node/ # NAPI-RS native addon
└── sdks/
├── python/ # Python packaging (maturin, pyproject.toml)
└── node/ # npm packaging (TypeScript wrapper, tsup)
Processing pipeline (20 stages)
PDF file
│
▼
pdf-cos ← xref parsing, object graph, encrypted streams
│
▼
edgeparse-core::pdf ← page tree, content stream operators, font decoding,
│ CMap/ToUnicode, image extraction, tagged PDF
▼
edgeparse-core::pipeline ← 20 sequential/parallel stages:
│ [page range] → [watermark] → [filter] → [table borders] →
│ [cell assignment] → [boxed headings] → [column detection] →
│ [TextLine assembly] → [TextBlock grouping] → [table clustering] →
│ [header/footer] → [list detection] → [paragraph] → [figure] →
│ [heading classification] → [XY-Cut++ reading order] →
│ [list pass 2] → [caption/footnote/TOC] → [cross-page tables] →
│ [element nesting] → [final reading order] → [sanitize]
▼
edgeparse-core::output ← render to JSON / Markdown / HTML / text
Stages marked par_map_pages run in parallel via Rayon; cross-page stages run sequentially.
FAQ
What is the best PDF parser for RAG?
For RAG pipelines, you need a parser that preserves document structure, maintains correct reading order, and provides element coordinates for citations. EdgeParse outputs structured JSON with bounding boxes for every element, handles multi-column layouts with XY-Cut++, and runs locally on CPU without a GPU or JVM. On the current 200-document benchmark it leads the finished comparison set in overall score (0.782) with ~0.02 s/doc deterministic latency. See RAG integration examples.
How do I cite PDF sources in RAG answers?
Every element in JSON output includes a bounding box ([left, bottom, right, top] in PDF points, 72 pt = 1 inch) and page number. Map the source chunk back to its bounding box to highlight the exact location in the original PDF — enabling "click-to-source" UX. No other non-OCR open-source parser provides bounding boxes for every element by default.
How do I extract tables from PDF?
EdgeParse detects tables using border (ruling-line) analysis by default. For complex or borderless tables, add --table-method cluster (CLI) or table_method="cluster" (Python). This uses a text-clustering algorithm to detect table structure without visible borders. On the current benchmark it reaches 0.596 TEDS and 0.901 table-detection F1, both best in the published comparison set.
Does it work without sending data to the cloud?
Yes. EdgeParse runs 100% locally. No API calls, no data transmission — your documents never leave your environment. The WebAssembly SDK also runs entirely in the browser: the PDF is processed client-side and never uploaded to any server. Ideal for legal, healthcare, and financial documents.
Does it handle multi-column layouts?
Yes. XY-Cut++ reading order analysis correctly sequences text across multi-column pages, sidebars, and mixed layouts. This works without any configuration change; it is the default reading order algorithm.
Does it need a GPU or Java?
No. EdgeParse is a pure Rust implementation. It requires no JVM, no GPU, no OCR models, and no Python runtime for the CLI binary. The CLI binary is ~15 MB stripped. On Apple M4 Max, it processes the 200-document benchmark corpus in about 12.7 seconds total.
How does it compare to Docling, MinerU, and Marker?
| vs. | EdgeParse advantage | Tradeoff |
|---|---|---|
| OpenDataLoader | Faster deterministic path with stronger table structure and heading recovery | OpenDataLoader remains close on text quality; use EdgeParse hybrid for complex tables |
| IBM Docling | Faster and higher overall score in the current harness snapshot | Docling remains useful as the hybrid backend for hard table pages |
| Marker | Faster and materially better on every published metric in this benchmark family | Marker supports scanned PDFs via Surya OCR |
| PyMuPDF4LLM | Faster with stronger tables, headings, and reading order | PyMuPDF4LLM is simpler if you only need lightweight text extraction |
Does it support scanned PDFs?
Not directly — EdgeParse is built for born-digital PDFs with embedded fonts. It does not include an OCR engine. For scanned documents, use EdgeParse's hybrid backend option (--hybrid docling-fast) which routes complex pages to a Docling-Fast backend running locally.
What does "deterministic" mean?
Same input PDF → same output, every time. No stochastic models, no floating-point non-determinism from ML inference. This makes EdgeParse safe to use in CI pipelines, regression tests, and compliance workflows where reproducibility is required.
How do I chunk PDFs for semantic search?
Use format="markdown". EdgeParse preserves heading hierarchy and table structure in Markdown output — headings make natural chunk boundaries for RecursiveCharacterTextSplitter (LangChain) or heading-based splitters. For element-level control, use format="json" and split on heading level boundaries or page number changes.
Does the Python SDK run on Windows?
Yes. Pre-built wheels are available for Windows (x64). The Python package installs from PyPI with no compilation needed:
pip install edgeparse
Tutorials
Step-by-step guides with working examples live in tutorials/:
| Tutorial | Description |
|---|---|
| tutorials/01-cli.md | All CLI flags with working examples and output samples |
| tutorials/02-python-sdk.md | pip install edgeparse — full API, batch processing, JSON parsing |
| tutorials/03-nodejs-sdk.md | npm install edgeparse — TypeScript, CJS, and worker threads |
| tutorials/04-rust-library.md | edgeparse-core in your Rust project — config, models, Rayon |
| tutorials/05-output-formats.md | JSON schema, bounding boxes, Markdown variants, HTML, plain text |
Documentation
Technical documentation lives in docs/:
| Document | Description |
|---|---|
| docs/00-overview.md | Project overview, goals, and design philosophy |
| docs/01-architecture.md | Crate structure, module map, data-flow diagram |
| docs/02-pipeline.md | All 20 pipeline stages with ASCII diagrams |
| docs/03-data-model.md | Type hierarchy: ContentElement, BoundingBox, PdfDocument |
| docs/04-pdf-extraction.md | PDF loader, chunk parser, font/CMap decoding |
| docs/05-output-formats.md | JSON schema, Markdown renderer, HTML/text/CSV output |
| docs/06-sdk-integration.md | CLI flag reference, Python SDK API, Node.js SDK API, Batch API |
| docs/07-cicd-publishing.md | CI/CD publishing pipeline — how it works and how to configure it |
| docs/08-agent-skill.md | EdgeParse agent skill — npx skills add, SKILL.md structure, SDK patterns |
| docs/09-wasm-sdk.md | WebAssembly SDK — objectives, API, use cases, build instructions |
Project Layout
edgeparse/
├── LICENSE
├── CONTRIBUTING.md
├── README.md
├── Cargo.toml # Rust workspace (5 members)
├── Cargo.lock
│
├── crates/
│ ├── pdf-cos/ # lopdf 0.39 fork — low-level PDF object model
│ ├── edgeparse-core/ # Core extraction engine (~90 source files)
│ ├── edgeparse-cli/ # CLI binary (clap, 25+ flags)
│ ├── edgeparse-wasm/ # WebAssembly build for browsers (wasm-bindgen)
│ ├── edgeparse-python/ # PyO3 native Python extension
│ └── edgeparse-node/ # NAPI-RS native Node.js addon
│
├── sdks/
│ ├── python/ # Python wheel packaging (maturin + pyproject.toml)
│ │ └── edgeparse/ # Pure-Python wrapper + CLI entry point
│ └── node/ # npm packaging (TypeScript + tsup + vitest)
│ └── src/ # index.ts, types.ts, cli.ts
│
├── benchmark/ # Evaluation suite
│ ├── run.py # Benchmark runner (EdgeParse)
│ ├── compare_all.py # Multi-engine comparison (9 engines)
│ ├── pyproject.toml
│ ├── thresholds.json # Regression thresholds
│ ├── pdfs/ # Benchmark PDFs (200 docs)
│ ├── ground-truth/ # Reference Markdown and JSON annotations
│ ├── prediction/ # Per-engine output directories
│ ├── reports/ # HTML benchmark reports
│ └── src/ # Python evaluators and engine adapters
│
├── docs/ # Technical documentation (Markdown)
│
├── demo/ # Interactive WASM demo (Vite + TypeScript)
│ └── src/ # Demo application source
│
├── examples/
│ └── pdf/ # Sample PDFs for quick testing
│ ├── lorem.pdf
│ ├── 1901.03003.pdf # Academic paper (multi-column)
│ ├── 2408.02509v1.pdf # Academic paper
│ └── chinese_scan.pdf # CJK + scan example
│
├── benches/ # Rust micro-benchmarks (criterion)
├── docker/ # Dockerfile and Dockerfile.dev
├── scripts/ # bench.sh, publish-crates.sh
└── tests/
└── fixtures/ # Rust integration test fixtures
Contributing
See CONTRIBUTING.md. In short:
- Fork, branch from
main cargo fmt && cargo clippy -- -D warnings- Run the benchmark to check for regressions:
cd benchmark && uv run python run.py --engine edgeparse - Open a PR
Star History
License
EdgeParse is licensed under the Apache License 2.0. See LICENSE for the full text.
The crates/pdf-cos/ directory is a fork of lopdf (MIT/Apache-2.0 dual-licensed).
Benchmark PDF documents (benchmark/pdfs/) are sourced from publicly available documents and are used solely for evaluation purposes.
Compatibility
- Runs on
- GPU support unknown
What shows it
- no dependency or feature matches a backend row
- Native code
- None found
- No native code found
- Python bindings
- No Python binding crate found
- License
- Apache-2.0
Compare with similar projects
Order: strongest tier first, then most downloads, then name, with unmeasured values last. A library counts downloads over 30 days. An app counts binary downloads per release. An app with none counts its crate downloads over 30 days. ML Rust runs this catalogue, so its closest project always gets a row.
| Project | Downloads | Dependents | Stars | Latest release | Runs on (GPU) | Native code | License |
|---|---|---|---|---|---|---|---|
|
EdgeParse core library — PDF parsing and structured data extraction |
6,764 downloads over 30 days | 2 | 155 | 0.3.2 | GPU support unknown | No native code found | Apache-2.0 |
|
pdf_oxide Featured
The fastest Rust PDF library — 0.8ms mean, 5× faster than the industry leaders, 100% pass rate on 3,830 real-world PDFs. Text extraction, Markdown/HTML conversion, PDF creation and editing. Python bindings Also in Build LLM apps and agents. |
316,729 downloads over 30 days | 30 | 1,180 | 0.3.78 | GPU support unknown | Binds native code: aws-lc-rs (feature fips), lcms2-sys (feature icc-lcms2), ort-sys (feature gpu) | Apache-2.0 OR MIT |
|
Pure Rust PDF library for AI/RAG: structure-aware chunking, no ML, no C deps. Also in Build LLM apps and agents. |
62,285 downloads over 30 days | Not applicable | 194 | 5.5.7 | GPU support unknown | No native code found | MIT |
|
kreuzberg-lts Listed
High-performance document intelligence library for Rust. Extract text, metadata, and structured data from PDFs, Office documents, images, and 91+ formats and 248 programming languages via tree-sitter code intelligence with async/sync APIs. Also in Documents and OCR. |
54,366 downloads over 30 days | 7 | 21 | 4.10.4 | GPU support unknown | Binds native code: kreuzberg_tesseract, clipper-sys, ort-sys, tree-sitter (feature tree-sitter), tree-sitter-language (feature tree-sitter). Build uses a native toolchain: pkg-config, cc (feature build-tesseract), cmake (feature build-tesseract) | MIT |
|
serde_toon Listed
A Serde-compatible TOON (Token-Oriented Object Notation) serialization library Also in Build LLM apps and agents. |
2,384 downloads over 30 days | 4 | 13 | 0.3.0 | GPU support unknown | No native code found | Apache-2.0 OR MIT |
|
An open protocol that gives AI agents a secure, sandboxed place to run code and use tools — with lean context usage. AXP works alongside MCP Also in Build LLM apps and agents. |
Not measured | Not applicable | 3 | Not measured | Not measured | Not measured | Apache-2.0 |
|
xberg New
High-performance document intelligence library for Rust. Extract text, metadata, and structured data from PDFs, Office documents, images, and 107 formats and 371 programming languages via tree-sitter code intelligence with async/sync APIs. Also in Documents and OCR. |
26,095 downloads over 30 days | 2 | 9,565 | 1.3.10 | CUDA, Metal | Binds native code: libheif-sys, libz-sys, xberg_libwpd, xberg_tesseract, clipper-sys and 4 more. Build uses a native toolchain: cc, bindgen (feature bindings), cmake (feature build-tesseract), pkg-config (feature dynamic-linking), vcpkg (feature dynamic-linking) | MIT |
All projects in Build LLM apps and agentsAll projects in Documents and OCR
Why ListedNo archive, New or Featured rule places it, so it is Listed.
What it lacks for Featured
- It has 1 active dependents and 1 new dependents in 365 days. Featured needs 11 active dependents or 9 new dependents in 365 days. The repository route reads 155 stars, 20 forks and 2 contributors. It needs 4,539 stars, 434 forks and 79 contributors together.
What else the check found
- The last activity is on 9 Oct 2026, 31 days ago. The rules expect an update within 365 days. Dormancy rule
- The latest release requires the deprecated dependency ttf-parser. RUSTSEC-2026-0192 Rot signals rule
Exact rules: Listed, Featured library. The check on applied thresholds version 9.
Every rule outcome, as the check records it
expected-interval: version 0.3.2 base 300, raised to the 365-day floor: E = 365 daysdormancy: last activity 2026-10-09, 31 days ago, E = 365: activerot: deprecated requirement `ttf-parser (RUSTSEC-2026-0192, https://rustsec.org/advisories/RUSTSEC-2026-0192.html)`featured: not featured: active_users 1 < 11 and new_adopters 1 < 9 and repository route stars 155 (needs 4539), forks 20 (needs 434), contributors 2 (needs 79)listed: listed: no archive, new or featured rule applies
Crates2 crates
- edgeparse-core Primary
- edgeparse-cli
Measurements and sources
Each signal row shows the raw value, its source and the fetch date. The Sources list gathers every distinct source on this page. Movement needs more history.
Sources6 sources
- Description: the crates.io description
- crates.io database dump, fetched
- RustSec advisory database, fetched
- GitHub repository, fetched
- crates.io package, fetched
- GitHub contributors, fetched
Adoption1 active dependents, 1 new dependents in 365 days1 not measured
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Active dependents | 1 | crates.io database dump | Yes | |
| New dependents (365 days) | 1 | crates.io database dump | Yes | |
| Peak active dependents | 1 | crates.io database dump | Yes | |
| Active dependents as a share of the peak | 1 | crates.io database dump | Yes | |
| Binary downloads per release | Not measured | Yes |
MaintenanceLatest human commit: 31 days ago3 not measured
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Latest human commit | 2026-10-09 | GitHub repository | Yes | |
| Latest release | 2026-10-02 | crates.io database dump | Yes | |
| First release | 2026-03-23 | crates.io database dump | Yes | |
| Latest version | 0.3.2 | crates.io database dump | Yes | |
| Days the oldest outside issue waits for a reply | 229 | GitHub repository | Yes | |
| Maintainer replies over 365 days | 7 | GitHub repository | Yes | |
| Repository archived | false | GitHub repository | Yes | |
| Repository gone | false | GitHub repository | Yes | |
| Release commit or manifest in the repo | true | crates.io package | Yes | |
| Published docs | true | crates.io database dump | Yes | |
| Latest release fails cargo check | Not measured | Yes | ||
| Build check date | Not measured | Yes | ||
| Build check toolchain | Not measured | Yes |
SecurityUnpatched vulnerabilities: none
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Unpatched vulnerabilities past the grace window | none | RustSec advisory database | Yes | |
| RustSec unmaintained advisory | none | RustSec advisory database | Yes | |
| Deprecation notice | none | crates.io database dump | Yes | |
| Deprecated requirement | ttf-parser (RUSTSEC-2026-0192, https://rustsec.org/advisories/RUSTSEC-2026-0192.html) | crates.io database dump | Yes |
Distribution11 releases, 6,764 downloads over 30 days5 not measured
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| Releases | 11 | crates.io database dump | Yes | |
| Downloads over 30 days | 6764 | crates.io database dump | Yes | |
| Binary downloads over the last 3 releases | Not measured | Yes | ||
| Homebrew installs on request over 365 days | Not measured | Yes | ||
| Repology repository families | Not measured | Yes | ||
| Docker pull growth over 90 days | Not measured | Yes | ||
| Docker pulls at the growth baseline | Not measured | Yes | ||
| Every version a prerelease | false | crates.io database dump | Yes | |
| Every version 0.0.x | false | crates.io database dump | Yes | |
| Every version yanked | false | crates.io database dump | Yes | |
| Crate deleted from crates.io | false | crates.io database dump | Yes |
Repository155 stars, 20 forks, 2 contributors, 135 commits. Stars alone never qualify a project.
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| GitHub stars | 155 | GitHub repository | Yes — one part of the repository route | |
| GitHub forks | 20 | GitHub repository | Yes — one part of the repository route | |
| Contributors with a GitHub account | 2 | GitHub contributors | Yes — one part of the repository route | |
| Commits on the default branch | 135 | GitHub repository | No | |
| Repository's main crate left out of the entry | none | crates.io database dump | Yes |
Facts that never count
| Signal | Value | Source | Fetched | Counts |
|---|---|---|---|---|
| License | Apache-2.0 | crates.io database dump | No | |
| Runs on, from listed crates and their direct dependencies | GPU support unknown | crates.io database dump | No | |
| What shows each backend | no dependency or feature matches a backend row | crates.io database dump | No | |
| Native code, from listed crates and their dependencies | No native code found | crates.io database dump | No | |
| Python bindings, from direct dependencies of listed crates | No Python binding crate found | crates.io database dump | No | |
| Crates that depend on a listed crate directly | 2 | crates.io database dump | No |
Corrections and review history
No correction applies to this entry.
Judgments
A judgment enters only through an approved issue and a merged pull request.
No judgment applies to this entry.