An idiomatic Rust PDF toolkit -- create, read, manipulate, convert, encrypt, and sign.
Inspired by Alibaba EasyExcel's builder-pattern API design.
Version:
0.1.1· MSRV: Rust1.88· Edition:2024· License: Apache-2.0
9-crate workspace with clean separation of concerns:
flowchart TB
facade["<b>easypdf</b>\nEasyPdf facade + builders"]
core["<b>easypdf-core</b>\ntypes + errors + crypto + model + io + layout"]
derive["<b>easypdf-derive</b>\n#[derive(PdfModel)]"]
reader["<b>easypdf-reader</b>\nread + manipulate + streaming"]
writer["<b>easypdf-writer</b>\nwrite + template + backends"]
markdown["<b>easypdf-markdown</b>\npipeline + table + OCR + render"]
ocr["<b>easypdf-ocr</b>\ncloud OCR engines"]
runtime["<b>easypdf-runtime</b>\nMCP server + resident daemon"]
test["<b>easypdf-test</b>\nintegration tests"]
facade --> reader & writer & markdown & ocr
runtime --> reader & writer & markdown
markdown --> reader & core
reader --> core
writer --> core
ocr --> markdown & core
derive --> core
test --> facade
style facade fill:#e1f5fe
style core fill:#fff3e0
style runtime fill:#f3e5f5
| Capability | Status | Details |
|---|---|---|
| PDF creation | Stable | Builder pattern, text/images/shapes, custom fonts, metadata |
| PDF reading | Stable | 3 strategies (Full/Lazy/Streaming), session reuse (~129x faster) |
| Page manipulation | Stable | Merge, split, rotate, reorder, watermark, extract |
| Form filling | Stable | AcroForm field mapping via #[derive(PdfModel)] |
| PDF to Markdown | Preview | Pipeline with profiles, table detection, OCR fallback |
| Cloud OCR | Preview | GLM, HunyuanOCR, Baidu -- synchronous HTTP |
| Encryption | Stable | AES-128/256, permission control, ISO 32000 compliant |
| Digital signing | Stable | PKCS#7/CMS, RSA-PKCS#1v1.5 + SHA-256, X.509 |
| MCP server | Preview | 7 tools for LLM agent integration |
| Resident daemon | Preview | In-memory sessions via TCP / Unix socket |
# Cargo.toml
[dependencies]
easypdf = "0.1.1"Create a PDF:
use easypdf::prelude::*;
EasyPdf::create("output.pdf")
.page(PageSize::A4)
.add_text("Hello, world!")
.font(PdfFont::helvetica(12.0))
.position(72.0, 700.0)
.do_write()?;
# Ok::<(), easypdf::PdfError>(())Read a PDF:
use easypdf::prelude::*;
let text = EasyPdf::read("input.pdf")
.pages(0..10)
.extract_text()?;
# Ok::<(), easypdf::PdfError>(())Merge PDFs:
use easypdf::prelude::*;
EasyPdf::merge(&["a.pdf", "b.pdf", "c.pdf"], "merged.pdf")?;
# Ok::<(), easypdf::PdfError>(())Fill a form:
use easypdf::prelude::*;
#[derive(PdfModel)]
struct MyData {
#[pdf(field = "name")]
name: String,
}
EasyPdf::fill_form("template.pdf", &MyData { name: "Alice".into() })
.save("filled.pdf")?;
# Ok::<(), easypdf::PdfError>(())| Crate | Role | Key Types |
|---|---|---|
| easypdf | Facade + builder API | EasyPdf, PdfCreateBuilder, PdfReadBuilder, PdfManipulateBuilder |
| easypdf-core | Core types, traits, crypto, model, IO, layout | PdfError, PdfBlock, PdfDocumentModel, PdfEncryption, PdfSigner |
| easypdf-derive | #[derive(PdfModel)] proc-macro |
PdfModel derive, field attributes |
| easypdf-reader | PDF parsing, text extraction, page operations | PdfReader, PdfManipulator, ReadStrategy |
| easypdf-writer | PDF creation, template filling, backend selection | PdfWriter, PdfTemplateFiller, WriteBackend |
| easypdf-markdown | PDF to Markdown conversion pipeline | ProcessorPipeline, MarkdownRenderer, MarkdownProfile |
| easypdf-ocr | Cloud OCR engine collection | GlmConfig, HunyuanConfig, BaiduConfig |
| easypdf-runtime | MCP server + resident daemon | McpServer, ResidentServer, ResidentClient |
| easypdf-test | Integration tests + golden samples | Test harness |
The writer supports text, images, shapes, custom fonts, and metadata:
use easypdf::prelude::*;
let writer = EasyPdf::writer("My Report")
.backend(WriteBackend::auto(10 * 1024 * 1024)) // 10 MB threshold
.build()?;
// WriteBackend::InMemory -- default, fast for small docs
// WriteBackend::Spill -- page-level temp files, constant memory
// WriteBackend::Auto -- auto-select by threshold
# Ok::<(), easypdf::PdfError>(())PdfReader automatically selects the optimal strategy based on file size:
| File Size | Strategy | Behavior |
|---|---|---|
| 0 -- 5 MB | Full |
Load entire document into memory |
| 5 -- 100 MB | Lazy |
Parse headers, load pages on demand |
| > 100 MB | Streaming |
Byte-stream scan, no Document construction |
Session reuse parses the document once and reuses the in-memory representation -- ~129x faster than re-opening for repeated access.
PDF to Markdown with profiles, table detection, and OCR fallback:
use easypdf::prelude::*;
EasyPdf::export_markdown("input.pdf", "output.md")
.pages(0..20)
.profile(MarkdownProfile::Llm)
.tables(TablePolicy::Detect)
.ocr(OcrPolicy::Auto)
.do_export()?;
# Ok::<(), easypdf::PdfError>(())| Profile | Use Case |
|---|---|
MarkdownProfile::Gfm |
GitHub/GitLab rendering with GFM tables |
MarkdownProfile::Llm |
Token-efficient markup for LLM context |
MarkdownProfile::Plain |
Human-readable plain text |
Pipeline flow: PDF -> PdfReader -> PdfDocumentModel -> ProcessorPipeline -> MarkdownRenderer -> String
AES-128/256 encryption with permission control:
use easypdf::prelude::*;
let enc = PdfEncryption::new("user_pass", "owner_pass")
.with_algorithm(PdfEncryptionAlgorithm::Aes256)
.with_permissions(PdfPermissions::PRINT | PdfPermissions::COPY);
let encrypted = encrypt_pdf(&pdf_bytes, &enc)?;
# Ok::<(), easypdf::PdfError>(())PKCS#7 digital signatures with RSA-PKCS#1v1.5 + SHA-256 (via ring):
use easypdf::prelude::*;
let signer = PdfSigner::new(cert_pem, key_pem)
.with_reason("Document approval")
.with_location("Beijing");
let signed = sign_pdf(&pdf_bytes, &signer)?;
let info = verify_pdf_signature(&signed)?;
# Ok::<(), easypdf::PdfError>(())Resident daemon keeps PDF sessions in memory across requests:
use easypdf::EasyPdf;
// Start daemon (blocks):
EasyPdf::serve(None)?;
// Attach from another process:
if let Some(client) = EasyPdf::attach() {
// use client to interact with the daemon
}MCP server exposes 7 tools for LLM agent integration:
| Tool | Description |
|---|---|
pdf_read_text |
Extract text from PDF |
pdf_to_markdown |
Convert PDF to Markdown |
pdf_create_text |
Create text PDF |
pdf_merge |
Merge multiple PDFs |
pdf_split |
Split PDF into pages |
pdf_metadata |
Extract document metadata |
pdf_page_count |
Get page count |
use easypdf::EasyPdf;
let server = EasyPdf::mcp_server();
server.run()?;Benchmarked against pdftotext (Poppler) on Apple M4 Pro:
| Metric | easypdf | pdftotext | Result |
|---|---|---|---|
| 100-page extraction | 2.4 ms | 17 ms | ~7x faster |
| Peak memory (small files) | ~7 MB | ~10 MB | 29% less |
| Peak memory (100 pages) | 8.7 MB | 10.5 MB | 17% less |
| Text accuracy (avg) | 89% | baseline | 92--98% on structured PDFs |
| Session reuse | ~1,047 ns | ~135,011 ns | ~129x faster |
| Metric | Value |
|---|---|
| Tests passed | 136 |
| Code coverage | 91.61% |
| Total Rust code | ~52,626 lines |
| Crates | 9 |
| Feature | Enables | Default |
|---|---|---|
markdown |
PDF to Markdown pipeline | Yes |
markdown-table |
Table detection in markdown | No |
markdown-ocr |
OCR fallback for scanned pages | No |
ocr |
Cloud OCR (GLM/Hunyuan/Baidu) | No |
render |
PDF page rendering to PNG | No |
html |
HTML to PDF (requires Chromium) | No |
runtime |
Resident daemon + MCP server | No |
mcp |
MCP server only | No |
resident |
Resident daemon only | No |
full |
Everything enabled | No |
# Default: markdown enabled
easypdf = "0.1.1"
# Minimal build (no markdown)
easypdf = { version = "0.1.1", default-features = false }
# Enable everything
easypdf = { version = "0.1.1", features = ["full"] }| Item | Value |
|---|---|
| MSRV | Rust 1.88 |
| Edition | 2024 |
| Resolver | 3 |
| unsafe | forbid (workspace-wide) |
| Platform | macOS / Linux / Windows |
| Document | Description |
|---|---|
| Architecture (EN) | Architecture design document |
| Architecture (中文) | 架构设计文档 |
| Usage Guide | Complete API guide with 12 chapters |
| Benchmark Report | Performance baseline vs pdftotext |
| Compatibility | Feature matrix + coverage report |
| 版本规划 | Version plan and roadmap |
| Changelog | Version history |
| Contributing | Development setup and conventions |
| Metric | Current |
|---|---|
| Tests passing | 1522 |
| Test coverage | 91.61% |
| Cargo audit CVEs | 0 |
| Clippy warnings | 0 |
| Rustdoc warnings | 0 |
| Fuzz targets | 6 |
| crates.io published | v0.1.1 (8 crates) |
| Workspace crates | 9 (consolidated from 22) |
| Version | Focus | Status |
|---|---|---|
| v0.1 | Foundation: core types, read/write/manipulate/template, derive macro, 22-crate-to-9-crate consolidation, EasyPdf builder, #[derive(PdfModel)], PDF read/write/merge/split/rotate/reorder, AcroForm template fill, PDF-to-Markdown, atomic output, resource limits, #![forbid(unsafe_code)], 136 tests |
Done |
| v0.2 | Architecture Consolidation: 22-to-9 crate consolidation, Streaming ReadStrategy, CMap/ToUnicode (CJK), WriteBackend selection (InMemory/Spill/Auto), ConverterRegistry, 4 cloud OCR engines (GLM/Hunyuan/Baidu/DeepSeek), Resident daemon (Unix socket + Windows TCP), MCP server (7 tools), PdfBlock IR expanded to 14 variants, ProcessorPipeline, ISO 32000 encryption (AES-128/256), PKCS#7 signature, tracing observability, security fixes (rsa-to-ring, SSRF IPv6, API key redact), cargo-fuzz (6 targets), 91.61% coverage, v0.1.0 published to crates.io | Done |
| v0.3 | Rich Content: add_table Builder API, table border style enhancements (zebra striping, custom borders), image insertion (JPEG/PNG) with size/position control, vector shapes (lines, rectangles, circles), custom TTF/OTF font registration and embedding, multi-page writer with automatic page breaks |
In Progress |
| v0.4 | Security: AES-256 encryption/decryption, password protection (user + owner), permission flags (print/copy/modify/annotate), PDF-to-Markdown OCR real integration | Mostly Done |
| v0.5 | Compliance: PDF/A-1b, PDF/A-2b, PDF/A-3b validation, XMP metadata, document info dictionary standardization | Planned |
| v0.6 | Converters: HTML-to-PDF (Chromium-based, feature-gated), Markdown-to-PDF optimization, SVG-to-PDF, PDF-to-image rasterize | Partial |
| v1.0 | Stable: public API on crates.io, semver guarantees (0.2.x to 0.3.x to 1.0), CI matrix (Linux + macOS), Windows MSRV testing, property-based testing, complete migration guide | Planned |
详见 docs/superpowers/version-plan.md。
Before submitting, run all quality gates:
cargo check -p easypdf --no-default-features
cargo check -p easypdf --all-features
cargo test --workspace --quiet
cargo doc --workspace --no-depsNew public API must include docs, examples, tests, and SemVer impact notes.
Licensed under Apache-2.0.
- easyexcel-rs -- Rust port of Alibaba EasyExcel
- easyexcel -- Original Java library
- lopdf -- Pure Rust PDF manipulation
- printpdf -- Pure Rust PDF generation
Back to top · docs.rs · crates.io · Issues