Metadata-Version: 2.4
Name: pdfluent
Version: 1.3.0
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Office/Business
Classifier: Topic :: Text Processing
Requires-Dist: numpy ; extra == 'numpy'
Requires-Dist: pillow>=11.2.1,<13 ; extra == 'pillow'
Requires-Dist: pytest>=7.0 ; extra == 'test'
Requires-Dist: pypng ; extra == 'test'
Provides-Extra: numpy
Provides-Extra: pillow
Provides-Extra: test
License-File: LICENSE
License-File: LICENSE-COMMERCIAL
License-File: NOTICE
License-File: THIRD_PARTY_LICENSES.txt
Summary: PDF SDK for Python - find and replace PDF text while preserving fonts and layout, plus render, extract, annotate, sign, and validate PDFs. Pure Rust, zero system dependencies.
Keywords: pdf-text-replace,find-and-replace,edit-pdf-text,pdf,xfa,acroform,render,text-extraction,forms,pdf-a,annotations,redaction,rust,pdfluent
Author-email: "Innovation Trigger B.V." <team@pdfluent.com>
License-Expression: AGPL-3.0-only OR LicenseRef-PDFluent-Commercial
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Bug Tracker, https://pdfluent.com/support
Project-URL: Documentation, https://pdfluent.com/docs
Project-URL: Homepage, https://pdfluent.com
Project-URL: Repository, https://github.com/pdfluent/pdfluent-sdk

# pdfluent

[Source on GitHub](https://github.com/pdfluent/pdfluent-sdk) · [Documentation](https://pdfluent.com/docs) · [Report an issue](https://github.com/pdfluent/pdfluent-sdk/issues)

**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

```bash
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

```python
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.

```python
doc = Document("file.pdf")             # from path
doc = Document(open("file.pdf","rb").read())  # from bytes
doc = Document("encrypted.pdf", password="pw")
```

**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.

```python
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 `None` in the current release. They are typed as `Optional`
> so downstream code handles the `None` case correctly today and will
> receive data once font-metadata extraction lands.

```python
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})")
```

### Quality tiers

`Document.extract_markdown` runs `quality="auto"` by default (since 1.3.0): the
balanced tier with TATR tables, and OCR for pages that have no text layer. When
the models are not installed it falls back to the `fast` tier and raises a
`UserWarning` saying so. Pass `quality="fast"` for the 1.2 output, or
`quality="balanced"` for the balanced tier alone (an error without the models);
`model_dir=` and `table_model_dir=` point the tiers at directories outside the
default. Install the models with `pdfluent models install layout tables`. See
[Quality tiers](../../docs/STRUCTURED_EXTRACTION.md#quality-tiers).

The wheel ships no model weights. `pip install pdfluent` adds a `pdfluent`
command; `pdfluent models install layout tables` downloads the layout and table
models and the ONNX Runtime library for your platform into the per-user model dir
(`PDFLUENT_MODEL_DIR` overrides it), each file checked against its pinned sha256.
`pdfluent models status` shows what is there.
`pdfluent models install formula` installs the published formula model, its
NOTICE and training-data provenance. Enable it with `formula_model=True`
(Python) or `formulaModel: true` (Node).

The region model is opt-in and its assets are not published. Automatic
installation is unavailable; supply a complete model directory with its NOTICE.
With that package installed:
`extract_markdown(quality="balanced", formula_latex=True)` writes the layout
model's formula regions as `$...$` / `$$...$$` LaTeX, and `region_model=True`
sends table regions the cheaper stages did not settle to the same model.
Without the model the output is unchanged and no error is raised. See
[Quality tiers](../../docs/STRUCTURED_EXTRACTION.md#quality-tiers).

### 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:

```python
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 stubs
- `pdfluent/_native.pyi` - native extension stubs (for mypy without a build)

### Verifying with mypy

```bash
pip install mypy
cd crates/pdf-python
mypy --strict --python-path python tests/test_pdfluent_typing.py
```

### Example with typed annotations

```python
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](https://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`.

```bash
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 B.V.](https://pdfluent.com)

## Trademarks

PyMuPDF and MuPDF are trademarks of Artifex Software, Inc. PDFluent is an independent product of Innovation Trigger B.V. and is not affiliated with or endorsed by Artifex.

