Skip to main content

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(), and dump_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 the EXIS_PDF_LICENSE_KEY environment 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

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

exis_pdfeditor-3.4.1-py3-none-win_amd64.whl (4.0 MB view details)

Uploaded Python 3Windows x86-64

exis_pdfeditor-3.4.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.6 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

exis_pdfeditor-3.4.1-py3-none-macosx_11_0_arm64.whl (4.5 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

File details

Details for the file exis_pdfeditor-3.4.1-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for exis_pdfeditor-3.4.1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 48268680e9fee19aa3e003eed26b693ce78139d726390adff8fc105d70dbe3e7
MD5 da5a77074eb3c33131240666cdb03129
BLAKE2b-256 28891f9bba9b60cb485ea8ec029551d8b93c539a7d78c8a253b562f29dc92c20

See more details on using hashes here.

File details

Details for the file exis_pdfeditor-3.4.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for exis_pdfeditor-3.4.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 3e72ae522b13a9456c5efa45e405c26914a57ba42b61d963704fcd737bc08a65
MD5 980f51298f5f99abbf480777d9830989
BLAKE2b-256 77df90562f790e59f0fa636c2275d8e02d2b9148c118eb43230d752c0099601f

See more details on using hashes here.

File details

Details for the file exis_pdfeditor-3.4.1-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for exis_pdfeditor-3.4.1-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8a10055b862f2225b36f24a00ba70514f84c1425d8c55754c803a46c3fcd7c1c
MD5 fca1b28d4c042a26a9892142eabb0ac8
BLAKE2b-256 58b3339961abae98da24151e840a366a57b974dfe0a1258eec520cceb6d49e5c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page