pdfluent
Source on GitHub · Documentation · Report an issue
PDFluent for Python: a pure-Rust PDF SDK with no system dependencies, from a single pip install.
Render pages, extract text, fill forms, annotate, redact, encrypt, merge, and validate PDF/A — all from a single pip install.
Installation
pip install pdfluent
# Optional extras
pip install pdfluent[pillow] # PIL Image support
pip install pdfluent[numpy] # NumPy array support
Requires Python ≥ 3.8. Pre-built wheels for Linux (x86_64, aarch64), macOS (x86_64, arm64), and Windows (x86_64).
Quick Start
from pdfluent import Document
# Open, inspect, render
with Document("invoice.pdf") as doc:
print(f"{doc.page_count} pages — {doc.metadata.title}")
img = doc[0].render(dpi=150)
img.save("page_0.png") # requires Pillow
# Extract text
doc = Document("report.pdf")
for page in doc:
print(page.extract_text())
# Fill a form field and save
doc = Document("form.pdf")
doc.set_form_field("Name", "Jane Doe")
# Multi-select list box: select several options at once
doc.set_multi_select("Languages", ["EN", "NL"])
doc.save("form_filled.pdf")
# Search-and-redact
doc = Document("contract.pdf")
report = doc.redact_text("Confidential")
print(f"Redacted {report.areas_redacted} areas on {report.pages_affected} pages")
doc.save("contract_redacted.pdf")
# PDF/A validation
from pdfluent import validate_pdfa
report = validate_pdfa("archive.pdf")
if report.is_compliant:
print(f"✓ {report.pdfa_level} compliant")
else:
for issue in report.issues:
print(f"[{issue.severity}] {issue.rule}: {issue.message}")
# Merge PDFs
from pdfluent import merge_pdfs
merge_pdfs(["a.pdf", "b.pdf", "c.pdf"], "merged.pdf")
# Encrypt / decrypt
doc = Document("sensitive.pdf")
doc.encrypt("sensitive_enc.pdf", password="s3cr3t")
from pdfluent import decrypt_pdf
decrypt_pdf("sensitive_enc.pdf", "sensitive_dec.pdf", password="s3cr3t")
Features
| Feature | Description |
|---|---|
| Render | Pages to RGBA pixels, PIL Images, or NumPy arrays at any DPI |
| Text extraction | Plain text or structured TextBlock/TextSpan with position |
| Text search | Find pages containing a query string |
| Forms (AcroForm) | Read and fill text, checkbox, radio, combo/list fields; multi-select list boxes (set_multi_select); hierarchical names; appearance-stream regeneration |
| Annotations | Read existing annotations; add highlights and free-text notes |
| Redaction | Search-and-redact: black-box all occurrences of a string |
| Encryption | AES-256 (PDF 2.0) encrypt/decrypt with user + owner passwords |
| Merge / split | Merge multiple PDFs; split into individual pages (via page slicing) |
| PDF/A validation | Validate against PDF/A-1B, 2B, 3B with issue-level reporting. Validation only: PDF/A conversion is not exposed here, and where it is exposed it delivers 1B, 2B and 3B |
| Metadata | Read title, author, subject, keywords, creator, producer |
| Bookmarks | Traverse the document outline tree |
| Thumbnails | Fast downscaled preview images |
API Overview
Document(source, password=None)
Opens a PDF from a file path (str) or raw bytes.
doc = Document("file.pdf") # from path
doc = Document(open("file.pdf","rb").read()) # from bytes
doc = Document("encrypted.pdf", password="pw")
Properties: page_count, metadata, bookmarks
Methods: render_all(dpi), search(query), extract_text(page_num), save(path),
get_form_fields(), set_form_field(name, value), get_annotations(page),
add_annotation(page, type, rect, content), redact_text(term, page=None),
encrypt(path, password), decrypt(path, password)
Protocols: len(doc), doc[0], for page in doc, with Document(...) as doc
AcroForm fill behaviour
set_form_field(name, value) supports text, checkbox, radio, and choice
(combo/list) fields. Hierarchical names ("parent.child") are resolved
through /Kids recursion. Every call keeps /V, /AS, and /AP consistent
so the fill is visible in all PDF viewers without needing /NeedAppearances
processing.
doc.set_form_field("Name", "Jane Doe") # text field
doc.set_form_field("Agree", "On") # checkbox — pass on-state name
doc.set_form_field("Country", "NL") # radio group — export value
doc.set_form_field("Category", "Urgent") # combo / list — option value
doc.save("form_filled.pdf")
Read-only fields raise PdfluentError.
Page
Properties: index, width, height, rotation, geometry
Methods: render(dpi, width, height, background), thumbnail(max_dimension),
extract_text(), extract_text_blocks()
RenderedImage
Properties: width, height, pixels (raw RGBA bytes)
Methods: to_pil(), to_numpy(), save(path)
TextSpan
Structured text with position data.
Properties: text, x, y, font_size
Font metadata (optional): font_name, is_bold, is_italic, color
These fields return
Nonein the current release. They are typed asOptionalso downstream code handles theNonecase correctly today and will receive data once font-metadata extraction lands.
for block in page.extract_text_blocks():
for span in block.spans:
if span.font_name is not None:
print(f"{span.font_name} {'bold' if span.is_bold else ''}")
print(f" '{span.text}' @ ({span.x:.1f}, {span.y:.1f})")
Module-level functions
| Function | Description |
|---|---|
open_pdf(path, password=None) |
Alias for Document(path) |
merge_pdfs(paths, output) |
Merge a list of PDFs |
validate_pdfa(path) → ComplianceReport |
Run PDF/A validation |
decrypt_pdf(input, output, password) |
Decrypt to a new file |
Exception Hierarchy
Every pdfluent-specific error derives from PdfluentError, so a single
except PdfluentError: clause catches all library errors:
from pdfluent import PdfluentError, PdfluentParseError, PdfluentEncryptedError
try:
with Document("broken.pdf") as doc:
doc.render_all()
except PdfluentParseError as exc:
print(f"Not a valid PDF: {exc}")
except PdfluentEncryptedError:
print("PDF is password-protected")
except PdfluentError as exc:
print(f"PDF error: {exc}")
Full hierarchy:
PdfluentError — base; catch all pdfluent errors
├── PdfluentParseError — corrupt / non-PDF bytes
├── PdfluentValidationError — schema / compliance failures
├── PdfluentRenderError — rendering and XFA flatten failures
├── PdfluentEncryptedError — operation blocked by encryption
├── PdfluentPageRangeError — page index out of range
├── PdfluentIoError — file-system I/O errors
├── PdfluentGeometryError — invalid page geometry
└── PdfluentLimitError — processing-limit exceeded
Typing Support
pdfluent ships with hand-written .pyi stub files for IDE completion and
mypy --strict compatibility:
pdfluent/__init__.pyi— full public API stubspdfluent/_native.pyi— native extension stubs (for mypy without a build)
Verifying with mypy
pip install mypy
cd crates/pdf-python
mypy --strict --python-path python tests/test_pdfluent_typing.py
Example with typed annotations
from __future__ import annotations
from typing import Optional
from pdfluent import Document, TextSpan, PdfluentError
def get_font(span: TextSpan) -> Optional[str]:
"""Return the font name if available."""
return span.font_name # Optional[str] — mypy knows this may be None
def safe_open(path: str) -> Optional[Document]:
try:
return Document(path)
except PdfluentError:
return None
Licence
AGPL-3.0 or a commercial licence, at your option. The AGPL is the default and
the complete product: there is no licence key, no activation call and no tier,
and every feature works in every build. If you cannot accept the copyleft
obligation, the commercial licence is sold yearly and self-service at
pdfluent.com/sdk/pricing, in four options: Commercial (per
organisation), OEM Startup and OEM (per product), and Priority support (an
add-on). The two texts are LICENSE and LICENSE-COMMERCIAL in this crate.
Building from Source
Requires a Rust toolchain and maturin.
pip install maturin
cd crates/pdf-python
maturin develop --release # install in current venv
maturin build --release # build wheel in ./dist/
Links
- Documentation: https://pdfluent.com/docs
- Pricing: https://pdfluent.com/sdk/pricing
- PDFluent editor (source-available): https://github.com/pdfluent/pdfluent. The free desktop app this SDK powers.
- Built by Innovation Trigger BV
Metadata
Release files for pdfluent 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pdfluent-1.0.0.tar.gz | 7.5 MB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pdfluent-1.0.0-cp38-abi3-win_amd64.whl | CPython 3.8 | abi3 | Windows x86-64 | Details |
| pdfluent-1.0.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.8 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| pdfluent-1.0.0-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl | CPython 3.8 | abi3 | macOS 11.0+ ARM64, macOS 10.12+ x86-64, macOS 10.12+ universal2 (ARM64, x86-64) | Details |
Total release size: 51.4 MB
Release files / pdfluent-1.0.0.tar.gz
| Download URL | pdfluent-1.0.0.tar.gz |
|---|---|
| Size | 7.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
01e0311061f64af1705373b8d63287dae3e455fa943c9cf2a0ac940aebdb41fc
|
|
BLAKE2b-256 checksum How to use checksums |
15c5eacca1153535af940477c529416bbe69d84b13f4b710a4df9d00105aedc4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.7
|
Release files / pdfluent-1.0.0-cp38-abi3-win_amd64.whl
| Download URL | pdfluent-1.0.0-cp38-abi3-win_amd64.whl |
|---|---|
| Size | 11.1 MB |
| Tags | CPython 3.8 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
3356e28cc317c0661194dbb4e6b7117b2b0d3fe6e820058c743558898dee139b
|
|
BLAKE2b-256 checksum How to use checksums |
66ac7f3ca3073d3b23db33e7101b4279316f2dd41175e3a264ed95680bd04602
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.7
|
Release files / pdfluent-1.0.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | pdfluent-1.0.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 11.1 MB |
| Tags | CPython 3.8 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
6d6868e93f2fd1f47c3958d69765757904709c45d656968e69544847722f9ffa
|
|
BLAKE2b-256 checksum How to use checksums |
9cf0a14b5b8cd900b467cd0a0a7c98da8552f25a5dc5cf475b7664f701a5c8ab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.7
|
Release files / pdfluent-1.0.0-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
| Download URL | pdfluent-1.0.0-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl |
|---|---|
| Size | 21.8 MB |
| Tags | CPython 3.8 abi3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
b3b93f1cfc31563de6ad842341852797bfcbdb859a89de773ecc4cf539604d6e
|
|
BLAKE2b-256 checksum How to use checksums |
a5c0192d399992665efcd28e023bf525703fb43156b42663927d3c5694142169
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.7
|