Metadata-Version: 2.5
Name: localtools-pdf-diff
Version: 1.0.0
Summary: Compare two PDFs offline: visual pixel diff with highlights, word diff on the page images, metadata/structure diff (localtools, PDFium)
Project-URL: Source, https://github.com/chayprabs/localtools
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: multipart==2.0.1
Requires-Dist: pillow==13.0.0
Requires-Dist: pypdfium2==4.30.0
Description-Content-Type: text/markdown

# PDF Diff

Compare two PDFs locally, three ways:

- **Visual**: both PDFs are rendered with PDFium at the DPI you choose, pages are paired (by position when the page counts match, otherwise by content so inserted/removed pages line up), and pixels are compared. Changed areas are highlighted (red = only in A, green = only in B) with a per-page change score. A tolerance option ignores anti-aliasing and 1-pixel shifts; a threshold ignores small colour differences. Views: side by side, diff overlay, slider.
- **Text**: words are extracted with their positions and diffed word by word in reading order; deleted and inserted words are highlighted on the page images. Reflowed lines and line-end hyphenation don't count as changes; ligatures are normalised.
- **Structure**: page count, size, rotation and labels; document info and XMP; fonts (embedded or not); annotations; form fields and values; bookmarks; attachments (size + SHA-256).

Encrypted PDFs work if you enter the password. Exports: a diff report PDF (highlighted pages), a ZIP of PNGs of the changed pages, and a JSON summary. Runs offline.

## Install (no administrator)

```powershell
irm https://raw.githubusercontent.com/chayprabs/localtools/main/pdf-diff/install.ps1 | iex
```

```sh
curl -fsSL https://raw.githubusercontent.com/chayprabs/localtools/main/pdf-diff/install.sh | bash
# Or, with Python 3.12+ on any OS:
pipx install localtools-pdf-diff
```

Portable ZIPs: [Windows, macOS and Linux releases](https://github.com/chayprabs/localtools/releases/tag/pdf-diff-v1.0.0).

## Use

Run `localtools-pdf-diff` and the UI opens at <http://127.0.0.1:47103>. Choose PDF A (original) and PDF B (revised), pick a mode, click Compare, then open any page pair from the list.

```sh
localtools-pdf-diff a.pdf b.pdf                          # visual diff, summary on stdout
localtools-pdf-diff a.pdf b.pdf --mode text --json       # word diff as JSON
localtools-pdf-diff a.pdf b.pdf --report diff.pdf --png-zip pages.zip --dpi 150
localtools-pdf-diff a.pdf b.pdf --mode structure
localtools-pdf-diff locked.pdf b.pdf --password-a secret
```

Exit code: 0 = no differences, 1 = different, 2 = error (one line on stderr). Options: `--mode visual|text|structure`, `--dpi 36-300` (default 100), `--threshold 0-254` (default 24), `--no-aa`, `--password-a`, `--password-b`, `--json`, `--report FILE.pdf`, `--png-zip FILE.zip`. Exports are written to a temporary file and then renamed.

API: `POST /api/diff` (multipart: files `a` and `b`; fields `mode`, `dpi`, `threshold`, `aa` = `1`/`0`, `password_a`, `password_b`) returns the JSON summary with a `job` id. `GET /api/file?job=<id>&name=<file>` serves that job's page images (`a-N.png`, `b-N.png`, `d-N.png`), `report.pdf`, `pages.zip` and `summary.json`; the last three finished jobs stay available while the tool runs.

```sh
curl -F a=@a.pdf -F b=@b.pdf -F mode=text http://127.0.0.1:47103/api/diff
```

## Limits

64 MiB and 2,000 pages per PDF, 400,000 words per PDF, 300 megapixels rendered per PDF per comparison (100 DPI is about 0.94 MP per Letter page, so about 320 pages; the error tells you which DPI fits), pages larger than 40 megapixels are rendered at a lower DPI (noted in the result), at most 16 form parts per request, 120 seconds per comparison, two comparisons at a time (more get HTTP 503). Each comparison runs in a separate worker process that is killed on timeout, when the browser cancels, or when the tool exits, because PDFium can hang on malicious files. Workers get a 2 GiB memory limit on Windows (job object) and Linux (address space). macOS does not allow that limit, so there the pixel and page caps are the memory bound. Temporary folders are deleted after each job; leftovers from a crash are swept at the next start.

## Engine

[PDFium](https://pdfium.googlesource.com/pdfium/) (the PDF engine in Chrome) through [pypdfium2](https://github.com/pypdfium2-team/pypdfium2) 4.30.0 (PDFium 6462) for opening, decryption, rendering, text with character boxes and structure, all under Apache-2.0/BSD licenses. It renders pages the way Chrome does and repairs broken cross-reference tables. [Pillow](https://python-pillow.org/) does the pixel diff in C. Python's `difflib` does the word diff. The newer pypdfium2 5.x wheels need macOS 11/13 platform tags that the portable build does not target, and 4.30.1 was withdrawn because of a text-extraction regression, so 4.30.0 is pinned. Tests compare the word diffs against PyMuPDF as an independent reference (PyMuPDF is used only in tests, never shipped).

## Not included

- XFA-only forms (no AcroForm fallback): this PDFium build has no XFA, so those pages show PDFium's placeholder.
- OCR: scanned (image-only) PDFs can be compared visually, but text mode finds no words in them.
- XMP is read from the last uncompressed metadata packet. Compressed XMP streams are not decoded.
- Page ranges: to compare part of a very large PDF, split it first.
- Moved-text detection: text that moved shows up as a deletion plus an insertion.

## Update / uninstall

`localtools-pdf-diff update` / `localtools-pdf-diff uninstall`. If you installed with pipx: `pipx upgrade localtools-pdf-diff` / `pipx uninstall localtools-pdf-diff`.

MIT license. Bundled components: see `THIRD_PARTY_NOTICES.md`.
