Skip to main content

PDF-Compare

A powerful tool for comparing PDF files. Generates vector-based side-by-side comparison reports with content-aware highlighting.

Buy Me A Coffee

Features

  • Vector-Based Rendering: Preserves text quality and keeps file sizes small (no image conversion)
  • Searchable Output: Generated PDFs maintain searchable, selectable text
  • Visual Comparison: Side-by-side view of two PDFs with intelligent page alignment
  • Content-Aware Highlighting: Detects text changes based on content, ignoring layout shifts
  • Smart Page Alignment: Automatically detects inserted/deleted pages
  • Color-Coded Differences:
    • Red: Deleted text (on the original document)
    • Green: Added text (on the modified document)
  • Multiple Interfaces: CLI, GUI Desktop App, and Python API
  • Cross-Platform: Works on Windows, macOS, and Linux

Installation

The package is published on PyPI as py-pdf-compare:

pip install py-pdf-compare

Or, to install it as a standalone command-line tool with uv (recommended — no virtual environment to manage):

uv tool install py-pdf-compare

To add it as a dependency of your own project instead:

uv add py-pdf-compare

Note on names: the distribution is py-pdf-compare, but the import name is pdf_compare and the commands are pdf-compare and pdf-compare-gui.

Prerequisites

  • Python 3.12+ is required

Windows: Download from python.org and check "Add Python to PATH" during installation.

macOS:

brew install python@3.12

Linux (Ubuntu/Debian):

sudo apt install python3.12 python3.12-venv

Note: No additional dependencies (like Poppler) are required. PyMuPDF handles all PDF operations natively.

Quick Start

CLI Usage

# Compare two PDFs
pdf-compare original.pdf modified.pdf -o diff.pdf

# Get a machine-readable summary instead of a PDF
pdf-compare original.pdf modified.pdf --json result.json

# Compare two folders: diff documents plus an HTML report
pdf-compare ./version-1 ./version-2 --out ./diffs

# Compare two folders, report only
pdf-compare ./version-1 ./version-2 --report changes.html

# Launch GUI application
pdf-compare-gui

# Show help
pdf-compare --help

Python API

from pdf_compare import PDFComparator

# Create comparator instance
comparator = PDFComparator('original.pdf', 'modified.pdf')

# Generate comparison report (None when both documents are identical)
pdf_bytes = comparator.compare_visuals()

if pdf_bytes is None:
    print('No differences found')
else:
    # Save to file
    with open('report.pdf', 'wb') as f:
        f.write(pdf_bytes)

API Reference

PDFComparator(file_a, file_b)

Main class for comparing PDF files.

Parameters:

  • file_a (str): Path to the first PDF (Original)
  • file_b (str): Path to the second PDF (Modified)

Methods:

compare_visuals() -> bytes | None

Generate a vector-based visual comparison report.

Returns: PDF report as bytes, or None if no differences found. "No differences" means no page was added or removed and no word changed on any matched page.

Attributes:

  • missing_text_layer (bool): set after calling compare_visuals(). True when either document has no extractable text (a scan, for example), which makes the comparison unreliable — the report is still returned in that case, since the absence of differences cannot be confirmed.

Example:

from pdf_compare import PDFComparator

comparator = PDFComparator('a.pdf', 'b.pdf')
result = comparator.compare_visuals()

if comparator.missing_text_layer:
    print("Warning: no text layer, differences cannot be detected reliably")

if result is not None:
    with open('diff.pdf', 'wb') as f:
        f.write(result)
    print("Report generated successfully")
else:
    print("No differences found")

analyze() -> dict

Run the same comparison without composing the PDF, and return the result as plain data. Intended for automation: which files were compared and how much changed.

{
  "files": {
    "original": {"name": "a.pdf", "path": "/abs/a.pdf", "pages": 3},
    "modified": {"name": "b.pdf", "path": "/abs/b.pdf", "pages": 4}
  },
  "identical": false,
  "missing_text_layer": false,
  "changes": {
    "pages_added": 1, "pages_removed": 0, "pages_modified": 0,
    "words_added": 78, "words_removed": 0
  }
}

Counts are reported rather than a similarity percentage, deliberately: a percentage needs a denominator nobody agrees on (words of the original? of both? what is a whole added page worth?), while counts are facts the caller can turn into whatever ratio they need.

identical: true is only trustworthy when missing_text_layer is false. A scan has no extractable text, so it looks unchanged — always check both fields together.

The same comparison, literally: analyze() and compare_visuals() share the page alignment and the word-level diff, so they can never disagree. It is not meaningfully faster, though — composing the report references the source pages as vector objects rather than rendering them, so building the PDF costs almost nothing. The reason to use it is the format, and not writing a large file you do not need.

Comparing folders

Pass two directories instead of two files and every document is compared against its counterpart in the other folder:

pdf-compare ./version-1 ./version-2 --out ./diffs      # diffs + report
pdf-compare ./version-1 ./version-2 --report r.html    # report only

Pairing. Documents are matched by file name. Identical names (ignoring case) are matched first, so a perfect name can never be taken by a similar one; the rest are matched by name similarity, best score first and one-to-one, above a threshold. A file whose counterpart cannot be found is never compared silently — it is listed in the report, since a document with no counterpart usually means one was added or withdrawn.

Output. --out DIR writes the diff of every differing pair into DIR; identical pairs produce no document. An HTML report is always written: to report.html inside --out, or to the path given by --report. Without --out nothing but the report is produced.

The report is a single self-contained file — no external requests — listing every pair with its page and word changes, linking each diff document, and calling out the two cases that need a human: documents with no counterpart, and pairs with no text layer, whose result cannot be trusted.

How It Works

  1. Text Extraction: Extracts text and layout information from each page using PyMuPDF
  2. Similarity Scoring: Calculates similarity between pages using sequence matching
  3. Smart Alignment: Detects insertions, deletions, and shifts between documents
  4. Vector-Based Report: Creates a new PDF that preserves the original vector content
  5. Visual Highlighting: Adds vector-based highlights over text differences (no rasterization)
  6. Optimized Output: Maintains searchable text and small file sizes

Example: Inserted Page

If you insert a page in the middle of a document:

  • The inserted page is shown with a blank page on the left, labeled "Added"
  • Subsequent pages are correctly aligned and labeled as "Shifted"

Limitations and edge cases

  • Identical documents: no report is produced. The CLI says so and exits with code 0; the API returns None.
  • Scanned documents (no text layer): differences are detected from extractable text, so a scan cannot be compared. The tool detects this and warns instead of reporting "no differences"; there is no OCR.
  • GUI preview: only the first 20 pages are rendered in the window, to keep memory bounded. The generated report file always contains every page.
  • Large page shifts: alignment looks at most 3 pages ahead, so a block of more than 3 consecutive inserted or deleted pages in the middle of a document may not be recovered, and the pages after it can be reported as changed.

Project Structure

Py-PDF-Compare/
├── pdf_compare/
│   ├── __init__.py         # Package initialization
│   ├── comparator.py       # Core comparison logic
│   ├── batch.py            # Folder pairing and HTML report
│   ├── cli.py              # Command-line interface
│   ├── gui.py              # Desktop GUI application
│   └── config.py           # Configuration
├── tests/                  # Test suite (pytest)
├── .claude/skills/         # Claude Code skill shipped with the project
├── scripts/
│   ├── build_windows.py    # Build Windows executable
│   ├── build_linux.py      # Build Linux executable
│   └── build_macos.py      # Build macOS application
├── sample-files/           # Test PDFs for development
│   ├── original.pdf
│   ├── modified.pdf
│   ├── modified_extra_page.pdf
│   └── modified_removed_page.pdf
└── pyproject.toml          # Python package configuration

Development

From Source

git clone https://github.com/grananda/Py-PDF-Compare.git
cd Py-PDF-Compare
uv sync

uv sync installs the project into .venv in editable mode, but it does not put pdf-compare on your PATH. From source, run the commands through uv run — that way every change to the code takes effect immediately, with no reinstall step:

# Compare sample files
uv run pdf-compare sample-files/original.pdf sample-files/modified.pdf -o test-output.pdf

# Launch the GUI
uv run pdf-compare-gui

# From outside the repository
uv run --project /path/to/Py-PDF-Compare pdf-compare a.pdf b.pdf -o diff.pdf

Careful: uv tool install py-pdf-compare installs the published version from PyPI, so it will never pick up your local changes. To get a global pdf-compare command that tracks your working copy, install it from the repository instead:

uv tool install --editable .

Sample files included for testing:

  • sample-files/original.pdf - Base document
  • sample-files/modified.pdf - Document with text changes
  • sample-files/modified_extra_page.pdf - Document with added page
  • sample-files/modified_removed_page.pdf - Document with removed page

GUI Application

# From source
uv run pdf-compare-gui

# Or after installing the package
pdf-compare-gui

The GUI needs the Tk system libraries, which are not part of the Python package. If it fails with ImportError: libtk8.6.so, install them:

sudo pacman -S tk          # Arch / CachyOS
sudo apt install python3-tk # Debian / Ubuntu
brew install python-tk      # macOS

They are already included in the standard Python installers for Windows and macOS.

Building Standalone Executables

Windows Executable:

uv run python scripts/build_windows.py
# Result: dist/PDF Compare.exe

Linux Binary:

uv run python scripts/build_linux.py
# Result: dist/pdf-compare

macOS Application:

uv run python scripts/build_macos.py
# Result: dist/PDF Compare.app

Using as Git Submodule

This package can be integrated into other projects as a Git submodule:

git submodule add https://github.com/grananda/Py-PDF-Compare.git

Then import in your Python code:

from pdf_compare import PDFComparator

Claude Code skill

The repository ships a Claude Code skill in .claude/skills/pdf-compare/. It is picked up automatically when Claude Code runs in this project — nothing to install — and turns a request like "compare these two PDFs" or "compare these folders" into the right invocation. If you do not use Claude Code, the directory is inert.

What it encodes is the operational knowledge that is otherwise learned the hard way:

  • Never run a bare pdf-compare. A copy installed with uv tool install is frozen at whatever version was current that day. The skill resolves the build explicitly — the working copy when there is one, uvx --from "py-pdf-compare@latest" otherwise — and states which one it ran, so a surprising result is never confused with a stale binary.
  • Argument order decides the colours: the first document is drawn on the left, with its differences in red.
  • An identical pair writes no file at all, so there is no report to go looking for.
  • A missing text layer makes the result untrustworthy — and in a batch of two hundred documents, easy to miss among the pairs that worked.
  • Documents with no counterpart were never compared, which usually means one was added or withdrawn: often the most important finding in a batch.
  • A wall of differences starting at one specific page points at the alignment lookahead window being exceeded, before it points at a genuinely rewritten document.
  • Document contents never get pasted into the conversation. The PDFs compared in practice are contracts, policies and invoices.

Ask for it in plain language, or invoke it explicitly with /pdf-compare.

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues, questions, or contributions, visit: https://github.com/grananda/Py-PDF-Compare

Metadata

Release files for py-pdf-compare 2026.8.24

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for py-pdf-compare 2026.8.24
File Size Uploaded
py_pdf_compare-2026.8.24.tar.gz 660.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for py-pdf-compare 2026.8.24
File Interpreter ABI Platform
py_pdf_compare-2026.8.24-py3-none-any.whl Python 3 none any Details

Total release size: 698.9 kB

Release files / py_pdf_compare-2026.8.24.tar.gz

Download URL py_pdf_compare-2026.8.24.tar.gz
Size 660.0 kB
Tags Source
SHA-256 checksum
How to use checksums
697b344d15b150a2b167fafb63637d76637e007dc7e50faa386deda967ab8eef
BLAKE2b-256 checksum
How to use checksums
eaf38d568f38b22bd002a8d2312aa6ebcf56eebcf00505130e231776adf75e1c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 24, 2026.

Transparency log

Release files / py_pdf_compare-2026.8.24-py3-none-any.whl

Download URL py_pdf_compare-2026.8.24-py3-none-any.whl
Size 38.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
83b38ad31c74e413456cca3df817d34f77f31173ed919c5567d3fb528de2a510
BLAKE2b-256 checksum
How to use checksums
e513e5a8f28c387098cecfbe8d90e1d30400121c9c4176843b2985e2f09897de
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2026.8.24 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page