Python wrapper for Exis.PdfEditor — comprehensive PDF toolkit (find/replace, merge, split, forms, redaction, watermark, encryption, signatures, PDF/A, and more)
Project description
Exis.PdfEditor for Python
Comprehensive PDF toolkit for Python — find/replace, merge, split, form filling, redaction, image editing, watermark, page editing, encryption, optimization, digital signatures, PDF/A compliance, and more.
Powered by the Exis.PdfEditor .NET library, compiled to a native binary via .NET Native AOT. No .NET runtime required.
Installation
pip install exis-pdfeditor
Platform-specific wheels are available for:
- Windows x64 (
win_amd64) - Linux x64 (
manylinux_2_17_x86_64) - macOS ARM64 / Apple Silicon (
macosx_11_0_arm64)
Quick start
import exis_pdfeditor
# Optional: set a license key (or use EXIS_PDF_LICENSE_KEY env var).
# Without a key, a 14-day trial is activated automatically.
exis_pdfeditor.initialize("XXXX-XXXX-XXXX-XXXX")
# Inspect a PDF (free — no license required)
info = exis_pdfeditor.inspect("document.pdf")
print(f"{info.pageCount} pages, title: {info.title}")
# Find and replace text
result = exis_pdfeditor.find_replace(
"input.pdf", "output.pdf",
"old text", "new text",
case_sensitive=False,
)
print(f"{result.totalReplacements} replacements made")
Features
| Feature | Function |
|---|---|
| Inspect | inspect(path) — metadata, fonts, pages, encryption status |
| Text extraction | extract_text(path), extract_text_structured(path) |
| Find & replace | find_replace(...) — regex, case-insensitive, whole-word, styling |
| Merge | merge(paths, output) |
| Split | split(path, output_dir) |
| Extract pages | extract_pages(path, output, pages=[1,3,5]) |
| Form fields | list_fields(path), fill_form(path, output, fields, flatten=True) |
| Redaction | redact(path, output, redactions) |
| Watermark | watermark(path, output, "DRAFT", position="across") |
| Stamp | stamp(path, output, stamp_pdf, mode="overlay") |
| Optimize | optimize(path, output, downsample_images=True) |
| Encrypt / Decrypt | encrypt(...), decrypt(...) |
| Page editing | rotate(...), crop(...), reorder(...), delete_pages(...) |
| Images | find_images(path), replace_image(...) |
| Digital signatures | sign(...), verify(...) |
| PDF/A | pdfa_validate(path), pdfa_convert(path, output) |
Inspect
Retrieve metadata, page info, fonts, encryption status, and form field counts. No license required.
info = exis_pdfeditor.inspect("document.pdf")
print(f"Version: {info.version}")
print(f"Pages: {info.pageCount}")
print(f"Title: {info.title}")
print(f"Author: {info.author}")
print(f"Encrypted: {info.isEncrypted}")
print(f"Has forms: {info.hasFormFields} ({info.formFieldCount} fields)")
print(f"Fonts: {info.fontsUsed}")
for page in info.pages:
print(f" Page {page.pageNumber}: {page.widthInPoints}x{page.heightInPoints} pt, "
f"{page.characterCount} chars")
Text extraction
Plain text
result = exis_pdfeditor.extract_text("document.pdf")
print(result.fullText)
for page in result.pages:
print(f"Page {page.pageNumber}: {page.text[:100]}...")
Specific pages only
result = exis_pdfeditor.extract_text("document.pdf", pages=[1, 3, 5])
Structured text (with positions and font data)
result = exis_pdfeditor.extract_text_structured("document.pdf")
for page in result.pages:
for block in page.textBlocks:
print(f" [{block.x:.0f}, {block.y:.0f}] {block.fontName} {block.fontSize}pt: "
f"{block.text}")
Find & replace
Single pair
result = exis_pdfeditor.find_replace(
"input.pdf", "output.pdf",
"old text", "new text",
)
print(f"{result.totalReplacements} replacements across {result.pagesModified} pages")
Multiple pairs
result = exis_pdfeditor.find_replace(
"input.pdf", "output.pdf",
pairs=[
{"search": "foo", "replace": "bar"},
{"search": "hello", "replace": "world"},
],
)
Regex
result = exis_pdfeditor.find_replace(
"input.pdf", "output.pdf",
pairs=[
{"search": r"\d{3}-\d{2}-\d{4}", "replace": "XXX-XX-XXXX", "isRegex": True},
],
)
All options
result = exis_pdfeditor.find_replace(
"input.pdf", "output.pdf",
"confidential", "[REDACTED]",
case_sensitive=False,
whole_word=True,
use_regex=False,
page_range=[1, 2, 3],
text_fitting="adaptive", # none, preserve_width, fit_to_page, adaptive
min_horizontal_scale=70,
max_font_size_reduction=1.5,
replacement_text_color={"r": 1, "g": 0, "b": 0},
replacement_highlight_color={"r": 1, "g": 1, "b": 0},
replacement_bold=True,
replacement_underline=False,
replacement_strikethrough=False,
preserve_form_fields=True,
use_incremental_update=True,
)
for detail in result.details:
print(f" Page {detail.pageNumber}: '{detail.originalText}' -> '{detail.replacementText}'")
Merge
exis_pdfeditor.merge(["part1.pdf", "part2.pdf", "part3.pdf"], "merged.pdf")
Split
Split a PDF into one file per page:
result = exis_pdfeditor.split("document.pdf", "output_folder/")
print(f"Split into {result.pageCount} files")
for path in result.files:
print(f" {path}")
Extract pages
Extract specific pages into a new PDF:
exis_pdfeditor.extract_pages("document.pdf", "pages_1_and_3.pdf", pages=[1, 3])
Form fields
List all fields
fields = exis_pdfeditor.list_fields("form.pdf")
for field in fields:
print(f" {field.name} ({field.type}): {field.value}")
if field.displayName:
print(f" Label: {field.displayName}")
if field.options:
print(f" Options: {field.options}")
Fill a form
result = exis_pdfeditor.fill_form(
"form.pdf", "filled.pdf",
fields={
"FirstName": "John",
"LastName": "Doe",
"Email": "john@example.com",
"AgreeToTerms": "Yes",
},
)
print(f"Filled {result.fieldsFilled} fields, {result.fieldsNotFound} not found")
Fill and flatten (make fields non-editable)
result = exis_pdfeditor.fill_form(
"form.pdf", "filled_flat.pdf",
fields={"Name": "Jane Doe"},
flatten=True,
)
Redaction
Redact text
result = exis_pdfeditor.redact(
"document.pdf", "redacted.pdf",
redactions=[
{"text": "John Doe", "replaceWith": "[NAME REDACTED]"},
{"text": r"\d{3}-\d{2}-\d{4}", "isRegex": True, "replaceWith": "[SSN]"},
],
)
print(f"{result.redactionsApplied} redactions applied")
Redact a specific area on a page
result = exis_pdfeditor.redact(
"document.pdf", "redacted.pdf",
redactions=[
{
"area": {"x": 100, "y": 700, "width": 200, "height": 20},
"pageNumber": 1,
},
],
)
Combined text + area redaction
result = exis_pdfeditor.redact(
"document.pdf", "redacted.pdf",
redactions=[
{"text": "confidential", "caseSensitive": False},
{"area": {"x": 50, "y": 50, "width": 500, "height": 30}, "pageNumber": 2},
],
)
Watermark
Basic diagonal watermark
result = exis_pdfeditor.watermark("input.pdf", "watermarked.pdf", "DRAFT")
print(f"Watermarked {result.pagesWatermarked} of {result.totalPages} pages")
All watermark options
result = exis_pdfeditor.watermark(
"input.pdf", "watermarked.pdf",
"CONFIDENTIAL",
position="across", # top, bottom, center, across
font_size=72,
text_color={"r": 1, "g": 0, "b": 0},
opacity=0.15,
page_range=[1, 2],
)
Stamp (PDF overlay / underlay)
Overlay a letterhead on top
result = exis_pdfeditor.stamp(
"document.pdf", "stamped.pdf",
"letterhead.pdf",
mode="overlay",
)
print(f"Stamped {result.pagesStamped} pages")
Underlay a background behind content
result = exis_pdfeditor.stamp(
"document.pdf", "stamped.pdf",
"background.pdf",
mode="underlay",
opacity=0.5,
page_range=[1],
)
Optimize
Default optimization (compress + deduplicate)
result = exis_pdfeditor.optimize("large.pdf", "smaller.pdf")
print(f"Reduced {result.originalSize:,} -> {result.optimizedSize:,} bytes "
f"({result.reductionPercent:.1f}% smaller)")
print(f" Streams compressed: {result.streamsCompressed}")
print(f" Duplicates removed: {result.duplicatesRemoved}")
With image downsampling
result = exis_pdfeditor.optimize(
"large.pdf", "smaller.pdf",
downsample_images=True,
max_image_dpi=150,
remove_metadata=True,
)
print(f"Images downsampled: {result.imagesDownsampled}")
Encrypt & decrypt
Encrypt with a password
exis_pdfeditor.encrypt(
"document.pdf", "protected.pdf",
user_password="openme",
owner_password="secret",
permissions=["Print", "CopyText"],
)
Available permissions: Print, ModifyContents, CopyText, AddAnnotations,
FillForms, PrintHighQuality, All.
Decrypt
exis_pdfeditor.decrypt("protected.pdf", "unlocked.pdf", password="openme")
Page editing
Rotate pages
result = exis_pdfeditor.rotate("input.pdf", "rotated.pdf", angle=90)
print(f"Rotated {result.pagesModified} pages")
# Rotate only specific pages
result = exis_pdfeditor.rotate("input.pdf", "rotated.pdf", angle=180, pages=[2, 4])
Crop pages
result = exis_pdfeditor.crop(
"input.pdf", "cropped.pdf",
rect={"x": 50, "y": 50, "width": 500, "height": 700},
)
Reorder pages
exis_pdfeditor.reorder("input.pdf", "reordered.pdf", order=[3, 1, 2])
Delete pages
exis_pdfeditor.delete_pages("input.pdf", "trimmed.pdf", pages=[2, 4])
Images
Find all images
result = exis_pdfeditor.find_images("document.pdf")
print(f"Found {result.totalImages} images across {result.pagesSearched} pages")
for img in result.images:
print(f" Image {img.index}: {img.pixelWidth}x{img.pixelHeight} "
f"{img.colorSpace} {img.format}")
print(f" Pages: {img.pageNumbers}")
Find images and save to disk
result = exis_pdfeditor.find_images("document.pdf", output_dir="extracted_images/")
Replace all images
result = exis_pdfeditor.replace_image(
"document.pdf", "replaced.pdf",
"new_logo.png",
)
print(f"Replaced {result.imagesReplaced} of {result.imagesFound} images")
Replace specific images by index or page
result = exis_pdfeditor.replace_image(
"document.pdf", "replaced.pdf",
"new_logo.jpg",
image_indices=[0, 2],
page_range=[1],
scale_mode="scale_to_fit", # match_original_size, preserve_aspect_ratio, scale_to_fit
)
Digital signatures
Sign a PDF
exis_pdfeditor.sign(
"document.pdf", "signed.pdf",
cert_path="certificate.pfx",
cert_password="certpass",
reason="Approved",
location="New York, NY",
signer_name="John Doe",
)
Sign with a visible signature
exis_pdfeditor.sign(
"document.pdf", "signed.pdf",
cert_path="certificate.pfx",
cert_password="certpass",
visible=True,
page=1,
rect={"x": 50, "y": 50, "width": 200, "height": 60},
reason="Reviewed and approved",
)
Verify signatures
info = exis_pdfeditor.verify("signed.pdf")
print(f"Signed: {info.isSigned}")
print(f"Signer: {info.signerName}")
print(f"Valid: {info.isValid}")
print(f"Reason: {info.reason}")
print(f"Date: {info.signDate}")
# Verify all signatures in a multi-signed document
signatures = exis_pdfeditor.verify("multi_signed.pdf", all_signatures=True)
for sig in signatures:
print(f" {sig.signerName}: valid={sig.isValid}")
PDF/A compliance
Validate
result = exis_pdfeditor.pdfa_validate("document.pdf", level="2b")
print(f"Compliant: {result.isCompliant}")
if not result.isCompliant:
for v in result.violations:
print(f" [{v.code}] {v.message} (auto-fixable: {v.canAutoFix})")
Convert to PDF/A
exis_pdfeditor.pdfa_convert("document.pdf", "archive.pdf", level="2b")
Supported levels: 1b, 2b, 2u, 3b, 3u.
Diagnostic dump
Low-level structure dump for troubleshooting. No license required.
dump = exis_pdfeditor.dump_structure("problem.pdf")
print(f"Objects: {dump.totalObjects}, Streams: {dump.streamObjectCount}")
print(f"Encrypted: {dump.isEncrypted}")
Licensing
- Free:
inspect(),pdfa_validate(), anddump_structure()work without a license. - Trial: Call
exis_pdfeditor.initialize()with no key for a 14-day full-feature trial. - Licensed: Pass your key to
exis_pdfeditor.initialize("XXXX-XXXX-XXXX-XXXX")or set theEXIS_PDF_LICENSE_KEYenvironment variable. - Evaluation: After trial expiry, all features work on documents up to 3 pages.
Purchase a license at pdfbatcheditor.com/developers.
Requirements
- Python 3.9+
- No external dependencies — the native binary is bundled in the wheel.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file exis_pdfeditor-3.4.1-py3-none-win_amd64.whl.
File metadata
- Download URL: exis_pdfeditor-3.4.1-py3-none-win_amd64.whl
- Upload date:
- Size: 4.0 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48268680e9fee19aa3e003eed26b693ce78139d726390adff8fc105d70dbe3e7
|
|
| MD5 |
da5a77074eb3c33131240666cdb03129
|
|
| BLAKE2b-256 |
28891f9bba9b60cb485ea8ec029551d8b93c539a7d78c8a253b562f29dc92c20
|
File details
Details for the file exis_pdfeditor-3.4.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: exis_pdfeditor-3.4.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 4.6 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e72ae522b13a9456c5efa45e405c26914a57ba42b61d963704fcd737bc08a65
|
|
| MD5 |
980f51298f5f99abbf480777d9830989
|
|
| BLAKE2b-256 |
77df90562f790e59f0fa636c2275d8e02d2b9148c118eb43230d752c0099601f
|
File details
Details for the file exis_pdfeditor-3.4.1-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: exis_pdfeditor-3.4.1-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 4.5 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a10055b862f2225b36f24a00ba70514f84c1425d8c55754c803a46c3fcd7c1c
|
|
| MD5 |
fca1b28d4c042a26a9892142eabb0ac8
|
|
| BLAKE2b-256 |
58b3339961abae98da24151e840a366a57b974dfe0a1258eec520cceb6d49e5c
|