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
TJspacing, 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:
--rtlreverses 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
uv add apdf # or As a tool: uv tool install apdf
uv run apdf --help
Or with pip:
pip install apdf # core
pip install "apdf[crypto]" # + AES-encrypted PDF support (--password)
Usage As Library
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
apdf [options] input # output saved as input-Edited.pdf
apdf --batch FILES/DIRS/GLOBS --out-dir DIR [options]
Examples
# 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):
[
{"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:
old,new,regex
Abodx9,Tester,
INV-(\d+),BILL-\1,regex
Arguments
input: Source PDF. Output is saved asinput-Edited.pdfin 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,.csvor.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.-vlists matches,-qis 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 compensateTJwidths 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).
Metadata
Release files for apdf 2.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 | Provenance | |
|---|---|---|---|---|
| apdf-2.0.tar.gz | 42.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| apdf-2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 91.5 kB
Release files / apdf-2.0.tar.gz
| Download URL | apdf-2.0.tar.gz |
|---|---|
| Size | 42.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
425526417a8a0c57cc11bb598d915625ead8982fb2c47cb069da75fa37916be7
|
|
BLAKE2b-256 checksum How to use checksums |
b1c2054a0f7e107ee5aa39f9501100b0e86d13db16836191f1b69bb826d64258
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / apdf-2.0-py3-none-any.whl
| Download URL | apdf-2.0-py3-none-any.whl |
|---|---|
| Size | 49.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ccaf27621e3f398a4cadd7cf477bb1f5cbf60c20a3f44d4116bb33dbc05ecf1d
|
|
BLAKE2b-256 checksum How to use checksums |
591a522ff3840233414253307ec652bfd9cd976b6dbafc16062ab46ac7897f44
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.4 {"installer":{"name":"uv","version":"0.13.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for apdf 2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.