Metadata-Version: 2.5
Name: apdf
Version: 2.0
Summary: Low-level PDF text replacement engine: edits Unicode and hex-encoded text directly in content streams while preserving layout.
Author: Abodx9
License-Expression: MIT
Keywords: Hex,PDF,Raw,Redact,Replace,Text,Tools
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
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: Programming Language :: Python :: 3.14
Classifier: Topic :: Text Processing
Requires-Python: >=3.10
Requires-Dist: fonttools<5,>=4.63
Requires-Dist: pypdf<7,>=6.19
Provides-Extra: crypto
Requires-Dist: cryptography; extra == 'crypto'
Provides-Extra: test
Requires-Dist: cryptography; extra == 'test'
Requires-Dist: pypdfium2; extra == 'test'
Requires-Dist: pytest>=8; extra == 'test'
Requires-Dist: reportlab; extra == 'test'
Description-Content-Type: text/markdown

# A-PDF

**A-PDF** is a fast, command-line tool designed to replace Unicode and Hex text directly within PDF content streams.

## Features
- **Direct Stream Replacement**: Modifies the underlying PDF streams directly for fast and precise text replacement.
- **Unicode & Hex Support**: Text is automatically encoded to the document's fonts.
- **Layout Preservation**: Only the matched text is rewritten, so `TJ` spacing, justified gaps and column gaps survive (width compensation can be disabled).
- **Regex & Rules Files**: Regex with groups (`\1`), ignore-case, whole-word, and rules from `.json` / `.csv` / `.tsv`.
- **RTL Support**: `--rtl` reverses each OLD string to match the left-to-right glyph order that RTL producers (e.g. Google Docs / Skia) write into content streams.
- **Dry Run, Reports & Verify**: Preview every match, get a JSON report, and re-check the output.
- **Safe Output**: Atomic write with integrity check before saving. Output is always saved as `Input-Edited.pdf`, keeping the original intact.
- **Wider Coverage**: Form XObjects, annotations/form fields, split text, multi-part `/Contents`.
- **Batch, Encrypted & Damaged PDFs**: Whole folders in parallel, `--password`, automatic repair.
- **Strict Mode / Fallback Font / Page Selection**: As before.

## Installation

```bash
uv add apdf # or As a tool: uv tool install apdf
uv run apdf --help
```
Or with pip:
```bash
pip install apdf              # core
pip install "apdf[crypto]"    # + AES-encrypted PDF support (--password)
```

## Usage As Library

```python
from apdf import Replacement, replace_pdf_text_detailed

rep = replace_pdf_text_detailed(
    "Test1.pdf",
    "Test1-Edited.pdf",
    [
        Replacement("Old Text", "New Text"),
        Replacement(r"INV-(\d+)", r"BILL-\1", regex=True),
        Replacement("draft", "final", ignore_case=True, whole_word=True),
    ],  # a plain {"Old": "New"} dict still works

    # Optional parameters:
    # pages={2, 3, 4},      # one-based; int, set or range; None = all pages
    # dry_run=True,         # analyse only, write nothing (output_pdf may be None)
    # verify=True,          # re-read the output and confirm the replacements
    # password="secret",    # encrypted input
    # strict=True,
    # fallback_font="FONT.ttf", font_fallback=True,
)

print("Replacements:", rep.replacements)
print("Missing:", rep.missing)
print("Encoding failures:", rep.encoding_failures)
print("Warnings:", rep.warnings)
for m in rep.matches:                 # every occurrence with context
    print(m.page, m.before, "->", m.after)
# rep.to_dict() is JSON-serialisable
```
The library is silent; attach a handler to the `apdf` logger for progress.

## Usage As CLI Tool

```bash
apdf [options] input               # output saved as input-Edited.pdf
apdf --batch FILES/DIRS/GLOBS --out-dir DIR [options]
```

### Examples

```bash
# Basic / multiple replacements (saves as input-Edited.pdf)
apdf input.pdf -r "OldText=NewText" -r "2023=2024"

# Preview first (nothing is written)
apdf input.pdf -n -r "foo=bar"

# Regex with a group, case-insensitive, verified, with a JSON report
apdf input.pdf -x -i -r 'INV-(\d+)=BILL-\1' --verify --report report.json

# Arabic / RTL PDF (Google Docs, Skia) — glyphs are stored LTR in the stream
apdf input.pdf -r "جين=جولي" --rtl -i -ff ReadexPro.ttf

# Rules from a file
apdf input.pdf --rules rules.json

# A whole folder, 4 processes, originals untouched (saves as *-Edited.pdf)
apdf --batch ./pdfs --out-dir ./edited --rules rules.json -j 4
```

### Rules file

`rules.json` (per-rule options; backslashes doubled in JSON):
```json
[
  {"old": "Abodx\\d",   "new": "Tester",   "regex": true, "ignore_case": true},
  {"old": "INV-(\\d+)", "new": "BILL-\\1", "regex": true},
  {"old": "12.50",      "new": "99.00"}
]
```
A plain `{"old": "new"}` object also works. `rules.csv` / `.tsv`:
```csv
old,new,regex
Abodx9,Tester,
INV-(\d+),BILL-\1,regex
```

### Arguments

* `input`: Source PDF. Output is saved as `input-Edited.pdf` in the same directory (the original is always kept).
* `-r OLD=NEW`: Replacement pair, repeatable. Use `\=` for a literal `=` in OLD.
* `--rules FILE`: Load rules from `.json`, `.csv` or `.tsv`, repeatable.
* `-x` / `-i` / `-w`: Regex / ignore case / whole word, applied to all rules.
* `--rtl`: Reverse both OLD and NEW strings before matching/replacing — for RTL PDFs (Arabic, Hebrew) where a producer like Google Docs writes glyphs left-to-right in the content stream.
* `-p 2`, `-p 2,3,4`, `-p 1-5`: Pages to edit (default: all).
* `-n`, `--dry-run`: Analyse and list every match, write nothing.
* `--verify`: Re-read the output and check the replacements are present.
* `--report FILE`, `--json`: JSON report to a file / on stdout. `-v` lists matches, `-q` is quiet.
* `--password PW`: Encrypted PDF (output is re-encrypted with the same password).
* `--no-annotations`: Skip annotation / form-field appearance streams.
* `--batch`, `--out-dir DIR`, `--suffix TEXT`, `-j N`: Batch mode — inputs are never overwritten (default suffix is `-Edited`).
* `--extract`: Print the text apdf sees on the selected pages.
* `-nopa`: Do not compensate `TJ` widths after replacements.
* `-s`: Strict. Fail if text is missing, cannot be encoded, or verification fails.
* `-ff FONT.ttf` / `-no-ff`: Fallback font to embed for missing glyphs / refuse missing glyphs.

All rules are matched against the original text at once, so `A=B` plus `B=C` never turns `A` into `C`. Overlaps resolve leftmost, then longest.

## Limitations

- **Digital signatures are invalidated** by any edit (apdf warns).
- Edits using a **fallback font** look different from the surrounding text.
- Text stored as images or outlines can't be found; Type3 and variable-width CJK fonts are untested.
- Longer text can overflow its box (no re-wrapping).