smart-pdf-compare 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- smart_pdf_compare-0.1.0/.github/workflows/tests.yml +16 -0
- smart_pdf_compare-0.1.0/.gitignore +9 -0
- smart_pdf_compare-0.1.0/CHANGELOG.md +5 -0
- smart_pdf_compare-0.1.0/CONTRIBUTING.md +3 -0
- smart_pdf_compare-0.1.0/LICENSE +21 -0
- smart_pdf_compare-0.1.0/PKG-INFO +204 -0
- smart_pdf_compare-0.1.0/README.md +178 -0
- smart_pdf_compare-0.1.0/docs/api.md +7 -0
- smart_pdf_compare-0.1.0/docs/architecture.md +3 -0
- smart_pdf_compare-0.1.0/docs/assets/cover.svg +15 -0
- smart_pdf_compare-0.1.0/examples/basic_comparison.py +4 -0
- smart_pdf_compare-0.1.0/examples/highlight_changes.py +4 -0
- smart_pdf_compare-0.1.0/pyproject.toml +50 -0
- smart_pdf_compare-0.1.0/src/smart_pdf_compare/__init__.py +14 -0
- smart_pdf_compare-0.1.0/src/smart_pdf_compare/__main__.py +3 -0
- smart_pdf_compare-0.1.0/src/smart_pdf_compare/alignment.py +121 -0
- smart_pdf_compare-0.1.0/src/smart_pdf_compare/api.py +82 -0
- smart_pdf_compare-0.1.0/src/smart_pdf_compare/cli.py +51 -0
- smart_pdf_compare-0.1.0/src/smart_pdf_compare/diff.py +53 -0
- smart_pdf_compare-0.1.0/src/smart_pdf_compare/exceptions.py +17 -0
- smart_pdf_compare-0.1.0/src/smart_pdf_compare/extraction.py +59 -0
- smart_pdf_compare-0.1.0/src/smart_pdf_compare/highlighting.py +107 -0
- smart_pdf_compare-0.1.0/src/smart_pdf_compare/normalization.py +39 -0
- smart_pdf_compare-0.1.0/src/smart_pdf_compare/results.py +74 -0
- smart_pdf_compare-0.1.0/tests/conftest.py +24 -0
- smart_pdf_compare-0.1.0/tests/test_alignment.py +27 -0
- smart_pdf_compare-0.1.0/tests/test_cli.py +11 -0
- smart_pdf_compare-0.1.0/tests/test_comparison.py +34 -0
- smart_pdf_compare-0.1.0/tests/test_highlighting.py +22 -0
- smart_pdf_compare-0.1.0/tests/test_normalization.py +15 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
jobs:
|
|
4
|
+
test:
|
|
5
|
+
runs-on: ${{ matrix.os }}
|
|
6
|
+
strategy:
|
|
7
|
+
matrix:
|
|
8
|
+
os: [ubuntu-latest, windows-latest]
|
|
9
|
+
python: ['3.9', '3.12']
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: actions/setup-python@v5
|
|
13
|
+
with: {python-version: ${{ matrix.python }}}
|
|
14
|
+
- run: python -m pip install -e ".[dev]"
|
|
15
|
+
- run: python -m pytest
|
|
16
|
+
- run: python -m build
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Create a virtual environment, install `python -m pip install -e ".[dev]"`, and run `python -m pytest` before opening a pull request. Tests create their own PDFs with PyMuPDF and must not depend on network files. Keep the public API typed and documented. Run `python -m build` and `python -m twine check dist/*` before a release.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SmartPDFCompare contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: smart-pdf-compare
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Text-aware PDF comparison, highlighting, and reports.
|
|
5
|
+
Project-URL: Homepage, https://github.com/SmartPDFCompare/SmartPDFCompare
|
|
6
|
+
Project-URL: Issues, https://github.com/SmartPDFCompare/SmartPDFCompare/issues
|
|
7
|
+
Author-email: Abdelrahman Elsheikh <abdelrahmanelsheikh39@gmail.com>
|
|
8
|
+
Maintainer-email: Abdelrahman Elsheikh <abdelrahmanelsheikh39@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: comparison,diff,pdf,pymupdf
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: pymupdf>=2.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
24
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
<div align="center">
|
|
28
|
+
<a href="https://github.com/SmartPDFCompare/SmartPDFCompare">
|
|
29
|
+
<img src="https://raw.githubusercontent.com/SmartPDFCompare/SmartPDFCompare/main/docs/assets/cover.svg" alt="SmartPDFCompare" width="760">
|
|
30
|
+
</a>
|
|
31
|
+
<br>
|
|
32
|
+
<a href="https://github.com/SmartPDFCompare/SmartPDFCompare">
|
|
33
|
+
<img src="https://readme-typing-svg.demolab.com?font=Inter&weight=700&size=25&duration=3200&pause=900&color=2563EB¢er=true&vCenter=true&width=650&lines=Find+the+words+that+changed+in+your+PDFs;Compare.+Highlight.+Review." alt="Animated SmartPDFCompare introduction">
|
|
34
|
+
</a>
|
|
35
|
+
<br><br>
|
|
36
|
+
<a href="https://github.com/SmartPDFCompare/SmartPDFCompare/actions/workflows/tests.yml"><img src="https://github.com/SmartPDFCompare/SmartPDFCompare/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
|
|
37
|
+
<a href="https://pypi.org/project/smart-pdf-compare/"><img src="https://img.shields.io/pypi/v/smart-pdf-compare.svg?color=2563eb" alt="PyPI"></a>
|
|
38
|
+
<a href="https://pypi.org/project/smart-pdf-compare/"><img src="https://img.shields.io/pypi/pyversions/smart-pdf-compare.svg" alt="Supported Python versions"></a>
|
|
39
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2563eb.svg" alt="MIT license"></a>
|
|
40
|
+
<a href="https://pymupdf.readthedocs.io/en/latest/"><img src="https://img.shields.io/badge/powered%20by-PyMuPDF%202-16a34a.svg" alt="Powered by PyMuPDF 2"></a>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<p align="center">
|
|
44
|
+
<a href="#quick-start"><strong>Quick start</strong></a> ·
|
|
45
|
+
<a href="#features"><strong>Features</strong></a> ·
|
|
46
|
+
<a href="#command-line"><strong>CLI</strong></a> ·
|
|
47
|
+
<a href="docs/api.md"><strong>API</strong></a> ·
|
|
48
|
+
<a href="docs/architecture.md"><strong>Architecture</strong></a> ·
|
|
49
|
+
<a href="#limitations"><strong>Limitations</strong></a>
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
**SmartPDFCompare** is a lightweight Python library for comparing the extractable text in two PDF versions. It gives applications structured word and page changes, then uses the **same coordinates** to write readable highlighted PDFs and an optional side-by-side review report.
|
|
53
|
+
|
|
54
|
+
> [!IMPORTANT]
|
|
55
|
+
> SmartPDFCompare uses the current [PyMuPDF 2](https://pymupdf.readthedocs.io/en/latest/) API exclusively: `import pymupdf`.
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
- **[Structured comparison](docs/api.md)** — added, removed, replaced, added-page, removed-page, and moved-page records with JSON serialization.
|
|
60
|
+
- **[Unicode-aware text extraction](https://pymupdf.readthedocs.io/en/latest/textpage.html)** — PyMuPDF word coordinates, NFKC normalization, case folding, ligatures, NBSPs, and dash/minus variants.
|
|
61
|
+
- **[Practical page alignment](docs/architecture.md)** — content matching for inserted/moved pages, bounded sequential look-ahead, and index-by-index comparison.
|
|
62
|
+
- **[Visual review](#highlighted-output)** — native PDF highlights or boxes in safe output copies; optional side-by-side report pages.
|
|
63
|
+
- **[Small CLI](#command-line)** — terminal summary, JSON result, highlighted output, report, alignment policy, and style controls.
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python -m pip install --upgrade smart-pdf-compare
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The package tracks the current major [PyMuPDF](https://pypi.org/project/PyMuPDF/) release. Create a virtual environment first for a predictable setup:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
python -m venv .venv
|
|
75
|
+
# Windows: .venv\Scripts\activate
|
|
76
|
+
# macOS/Linux: source .venv/bin/activate
|
|
77
|
+
python -m pip install --upgrade smart-pdf-compare
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Quick start
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from smart_pdf_compare import compare_pdfs, highlight_differences
|
|
84
|
+
|
|
85
|
+
result = compare_pdfs("original.pdf", "revised.pdf")
|
|
86
|
+
|
|
87
|
+
print(result.is_identical)
|
|
88
|
+
print(result.summary)
|
|
89
|
+
for change in result.differences:
|
|
90
|
+
print(f"{change.change_type}: {change.old_text!r} -> {change.new_text!r}")
|
|
91
|
+
|
|
92
|
+
outputs = highlight_differences(
|
|
93
|
+
"original.pdf",
|
|
94
|
+
"revised.pdf",
|
|
95
|
+
output_dir="diff-output",
|
|
96
|
+
result=result,
|
|
97
|
+
include_report=True,
|
|
98
|
+
)
|
|
99
|
+
print(outputs)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Work with JSON
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import json
|
|
106
|
+
|
|
107
|
+
with open("comparison.json", "w", encoding="utf-8") as file:
|
|
108
|
+
json.dump(result.to_dict(), file, indent=2)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`result.truncated` is `True` when `max_differences` limits returned detail entries. Aggregate counts in `result.summary` always cover the full comparison.
|
|
112
|
+
|
|
113
|
+
## Highlighted output
|
|
114
|
+
|
|
115
|
+
<table>
|
|
116
|
+
<tr>
|
|
117
|
+
<td width="50%"><strong>Original</strong><br>Removed or replaced text receives red markup.</td>
|
|
118
|
+
<td width="50%"><strong>Revised</strong><br>Added or replaced text receives green markup.</td>
|
|
119
|
+
</tr>
|
|
120
|
+
<tr>
|
|
121
|
+
<td><code>original_highlighted.pdf</code></td>
|
|
122
|
+
<td><code>revised_highlighted.pdf</code></td>
|
|
123
|
+
</tr>
|
|
124
|
+
</table>
|
|
125
|
+
|
|
126
|
+
Use real PDF annotations by default, or boxes where highlights are not desired:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from smart_pdf_compare import highlight_differences
|
|
130
|
+
|
|
131
|
+
highlight_differences(
|
|
132
|
+
"original.pdf", "revised.pdf", "diff-output",
|
|
133
|
+
style="box", # "highlight" is the default
|
|
134
|
+
include_report=True, # creates difference_report.pdf
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Source PDFs are never overwritten. The side-by-side report contains only pages with differences, displays page labels, and leaves a clearly labeled blank side for an inserted or removed page.
|
|
139
|
+
|
|
140
|
+
## Alignment choices
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from smart_pdf_compare import ComparisonOptions, compare_pdfs
|
|
144
|
+
|
|
145
|
+
result = compare_pdfs(
|
|
146
|
+
"original.pdf", "revised.pdf",
|
|
147
|
+
options=ComparisonOptions(
|
|
148
|
+
alignment="content", # default
|
|
149
|
+
similarity_threshold=0.60,
|
|
150
|
+
lookahead=5,
|
|
151
|
+
max_differences=1_000,
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
| Strategy | Best for | Trade-off |
|
|
157
|
+
| --- | --- | --- |
|
|
158
|
+
| `content` | Documents with inserted, deleted, or moved pages | Greedy matching; not globally optimal for highly repetitive pages. |
|
|
159
|
+
| `sequential` | Mostly ordered revisions | Resynchronizes only inside `lookahead`. |
|
|
160
|
+
| `index` | Stable page-for-page documents | An insertion can make later pages appear modified. |
|
|
161
|
+
|
|
162
|
+
Normalization is deliberately conservative: it uses NFKC, case folding, ligature compatibility, non-breaking-space cleanup, and dash/minus unification. Punctuation remains significant by default, so decimal values, negative numbers, negation, and contractions do not silently disappear.
|
|
163
|
+
|
|
164
|
+
## Command line
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
smart-pdf-compare original.pdf revised.pdf \
|
|
168
|
+
--alignment content \
|
|
169
|
+
--output-dir diff-output \
|
|
170
|
+
--style highlight \
|
|
171
|
+
--report \
|
|
172
|
+
--json diff-output/comparison.json
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
| Exit code | Meaning |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| `0` | Textually identical under the chosen policy. |
|
|
178
|
+
| `1` | Changes were found. |
|
|
179
|
+
| `2` | Input, extraction, or configuration error. |
|
|
180
|
+
|
|
181
|
+
## Limitations
|
|
182
|
+
|
|
183
|
+
SmartPDFCompare compares the PDF **text layer**, not rendered pixels. It does not perform OCR, visual-diff analysis, or image comparison. If either document has no extractable text, it raises `MissingTextLayerError` instead of falsely claiming that two scanned PDFs are identical. Complex tables and multi-column PDFs can also expose an unreliable reading order from the source PDF.
|
|
184
|
+
|
|
185
|
+
PyMuPDF is available under [AGPL or commercial terms](https://pymupdf.readthedocs.io/en/latest/about.html#license-and-copyright). Confirm that its license is appropriate for your use case.
|
|
186
|
+
|
|
187
|
+
## Development
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
python -m pip install -e ".[dev]"
|
|
191
|
+
python -m pytest
|
|
192
|
+
python -m build
|
|
193
|
+
python -m twine check dist/*
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Read the [contribution guide](CONTRIBUTING.md), [public API](docs/api.md), [architecture notes](docs/architecture.md), and [changelog](CHANGELOG.md). Before publishing, verify availability of `smart-pdf-compare` on [PyPI](https://pypi.org/), upload to TestPyPI, install the artifact in a clean environment, and use [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/) for the production release.
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
SmartPDFCompare’s own source code is licensed under the [MIT License](LICENSE). Its PyMuPDF dependency has separate licensing, described above.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
<div align="center"><sub>Built for clear, reviewable PDF changes.</sub></div>
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://github.com/SmartPDFCompare/SmartPDFCompare">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/SmartPDFCompare/SmartPDFCompare/main/docs/assets/cover.svg" alt="SmartPDFCompare" width="760">
|
|
4
|
+
</a>
|
|
5
|
+
<br>
|
|
6
|
+
<a href="https://github.com/SmartPDFCompare/SmartPDFCompare">
|
|
7
|
+
<img src="https://readme-typing-svg.demolab.com?font=Inter&weight=700&size=25&duration=3200&pause=900&color=2563EB¢er=true&vCenter=true&width=650&lines=Find+the+words+that+changed+in+your+PDFs;Compare.+Highlight.+Review." alt="Animated SmartPDFCompare introduction">
|
|
8
|
+
</a>
|
|
9
|
+
<br><br>
|
|
10
|
+
<a href="https://github.com/SmartPDFCompare/SmartPDFCompare/actions/workflows/tests.yml"><img src="https://github.com/SmartPDFCompare/SmartPDFCompare/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
|
|
11
|
+
<a href="https://pypi.org/project/smart-pdf-compare/"><img src="https://img.shields.io/pypi/v/smart-pdf-compare.svg?color=2563eb" alt="PyPI"></a>
|
|
12
|
+
<a href="https://pypi.org/project/smart-pdf-compare/"><img src="https://img.shields.io/pypi/pyversions/smart-pdf-compare.svg" alt="Supported Python versions"></a>
|
|
13
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-2563eb.svg" alt="MIT license"></a>
|
|
14
|
+
<a href="https://pymupdf.readthedocs.io/en/latest/"><img src="https://img.shields.io/badge/powered%20by-PyMuPDF%202-16a34a.svg" alt="Powered by PyMuPDF 2"></a>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
<a href="#quick-start"><strong>Quick start</strong></a> ·
|
|
19
|
+
<a href="#features"><strong>Features</strong></a> ·
|
|
20
|
+
<a href="#command-line"><strong>CLI</strong></a> ·
|
|
21
|
+
<a href="docs/api.md"><strong>API</strong></a> ·
|
|
22
|
+
<a href="docs/architecture.md"><strong>Architecture</strong></a> ·
|
|
23
|
+
<a href="#limitations"><strong>Limitations</strong></a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
**SmartPDFCompare** is a lightweight Python library for comparing the extractable text in two PDF versions. It gives applications structured word and page changes, then uses the **same coordinates** to write readable highlighted PDFs and an optional side-by-side review report.
|
|
27
|
+
|
|
28
|
+
> [!IMPORTANT]
|
|
29
|
+
> SmartPDFCompare uses the current [PyMuPDF 2](https://pymupdf.readthedocs.io/en/latest/) API exclusively: `import pymupdf`.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **[Structured comparison](docs/api.md)** — added, removed, replaced, added-page, removed-page, and moved-page records with JSON serialization.
|
|
34
|
+
- **[Unicode-aware text extraction](https://pymupdf.readthedocs.io/en/latest/textpage.html)** — PyMuPDF word coordinates, NFKC normalization, case folding, ligatures, NBSPs, and dash/minus variants.
|
|
35
|
+
- **[Practical page alignment](docs/architecture.md)** — content matching for inserted/moved pages, bounded sequential look-ahead, and index-by-index comparison.
|
|
36
|
+
- **[Visual review](#highlighted-output)** — native PDF highlights or boxes in safe output copies; optional side-by-side report pages.
|
|
37
|
+
- **[Small CLI](#command-line)** — terminal summary, JSON result, highlighted output, report, alignment policy, and style controls.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python -m pip install --upgrade smart-pdf-compare
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The package tracks the current major [PyMuPDF](https://pypi.org/project/PyMuPDF/) release. Create a virtual environment first for a predictable setup:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
python -m venv .venv
|
|
49
|
+
# Windows: .venv\Scripts\activate
|
|
50
|
+
# macOS/Linux: source .venv/bin/activate
|
|
51
|
+
python -m pip install --upgrade smart-pdf-compare
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from smart_pdf_compare import compare_pdfs, highlight_differences
|
|
58
|
+
|
|
59
|
+
result = compare_pdfs("original.pdf", "revised.pdf")
|
|
60
|
+
|
|
61
|
+
print(result.is_identical)
|
|
62
|
+
print(result.summary)
|
|
63
|
+
for change in result.differences:
|
|
64
|
+
print(f"{change.change_type}: {change.old_text!r} -> {change.new_text!r}")
|
|
65
|
+
|
|
66
|
+
outputs = highlight_differences(
|
|
67
|
+
"original.pdf",
|
|
68
|
+
"revised.pdf",
|
|
69
|
+
output_dir="diff-output",
|
|
70
|
+
result=result,
|
|
71
|
+
include_report=True,
|
|
72
|
+
)
|
|
73
|
+
print(outputs)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Work with JSON
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import json
|
|
80
|
+
|
|
81
|
+
with open("comparison.json", "w", encoding="utf-8") as file:
|
|
82
|
+
json.dump(result.to_dict(), file, indent=2)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`result.truncated` is `True` when `max_differences` limits returned detail entries. Aggregate counts in `result.summary` always cover the full comparison.
|
|
86
|
+
|
|
87
|
+
## Highlighted output
|
|
88
|
+
|
|
89
|
+
<table>
|
|
90
|
+
<tr>
|
|
91
|
+
<td width="50%"><strong>Original</strong><br>Removed or replaced text receives red markup.</td>
|
|
92
|
+
<td width="50%"><strong>Revised</strong><br>Added or replaced text receives green markup.</td>
|
|
93
|
+
</tr>
|
|
94
|
+
<tr>
|
|
95
|
+
<td><code>original_highlighted.pdf</code></td>
|
|
96
|
+
<td><code>revised_highlighted.pdf</code></td>
|
|
97
|
+
</tr>
|
|
98
|
+
</table>
|
|
99
|
+
|
|
100
|
+
Use real PDF annotations by default, or boxes where highlights are not desired:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from smart_pdf_compare import highlight_differences
|
|
104
|
+
|
|
105
|
+
highlight_differences(
|
|
106
|
+
"original.pdf", "revised.pdf", "diff-output",
|
|
107
|
+
style="box", # "highlight" is the default
|
|
108
|
+
include_report=True, # creates difference_report.pdf
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Source PDFs are never overwritten. The side-by-side report contains only pages with differences, displays page labels, and leaves a clearly labeled blank side for an inserted or removed page.
|
|
113
|
+
|
|
114
|
+
## Alignment choices
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from smart_pdf_compare import ComparisonOptions, compare_pdfs
|
|
118
|
+
|
|
119
|
+
result = compare_pdfs(
|
|
120
|
+
"original.pdf", "revised.pdf",
|
|
121
|
+
options=ComparisonOptions(
|
|
122
|
+
alignment="content", # default
|
|
123
|
+
similarity_threshold=0.60,
|
|
124
|
+
lookahead=5,
|
|
125
|
+
max_differences=1_000,
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
| Strategy | Best for | Trade-off |
|
|
131
|
+
| --- | --- | --- |
|
|
132
|
+
| `content` | Documents with inserted, deleted, or moved pages | Greedy matching; not globally optimal for highly repetitive pages. |
|
|
133
|
+
| `sequential` | Mostly ordered revisions | Resynchronizes only inside `lookahead`. |
|
|
134
|
+
| `index` | Stable page-for-page documents | An insertion can make later pages appear modified. |
|
|
135
|
+
|
|
136
|
+
Normalization is deliberately conservative: it uses NFKC, case folding, ligature compatibility, non-breaking-space cleanup, and dash/minus unification. Punctuation remains significant by default, so decimal values, negative numbers, negation, and contractions do not silently disappear.
|
|
137
|
+
|
|
138
|
+
## Command line
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
smart-pdf-compare original.pdf revised.pdf \
|
|
142
|
+
--alignment content \
|
|
143
|
+
--output-dir diff-output \
|
|
144
|
+
--style highlight \
|
|
145
|
+
--report \
|
|
146
|
+
--json diff-output/comparison.json
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
| Exit code | Meaning |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| `0` | Textually identical under the chosen policy. |
|
|
152
|
+
| `1` | Changes were found. |
|
|
153
|
+
| `2` | Input, extraction, or configuration error. |
|
|
154
|
+
|
|
155
|
+
## Limitations
|
|
156
|
+
|
|
157
|
+
SmartPDFCompare compares the PDF **text layer**, not rendered pixels. It does not perform OCR, visual-diff analysis, or image comparison. If either document has no extractable text, it raises `MissingTextLayerError` instead of falsely claiming that two scanned PDFs are identical. Complex tables and multi-column PDFs can also expose an unreliable reading order from the source PDF.
|
|
158
|
+
|
|
159
|
+
PyMuPDF is available under [AGPL or commercial terms](https://pymupdf.readthedocs.io/en/latest/about.html#license-and-copyright). Confirm that its license is appropriate for your use case.
|
|
160
|
+
|
|
161
|
+
## Development
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
python -m pip install -e ".[dev]"
|
|
165
|
+
python -m pytest
|
|
166
|
+
python -m build
|
|
167
|
+
python -m twine check dist/*
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Read the [contribution guide](CONTRIBUTING.md), [public API](docs/api.md), [architecture notes](docs/architecture.md), and [changelog](CHANGELOG.md). Before publishing, verify availability of `smart-pdf-compare` on [PyPI](https://pypi.org/), upload to TestPyPI, install the artifact in a clean environment, and use [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/) for the production release.
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
SmartPDFCompare’s own source code is licensed under the [MIT License](LICENSE). Its PyMuPDF dependency has separate licensing, described above.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
<div align="center"><sub>Built for clear, reviewable PDF changes.</sub></div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
`compare_pdfs(old_pdf, new_pdf, options=None)` returns `ComparisonResult`. Configure `ComparisonOptions(alignment, similarity_threshold, lookahead, max_differences, normalization)`. Call `result.to_dict()` for JSON data.
|
|
4
|
+
|
|
5
|
+
`highlight_differences(old_pdf, new_pdf, output_dir, result=None, options=None, style="highlight", include_report=False)` returns paths for highlighted copies and optionally the report. `create_side_by_side_report` accepts an existing result and output path.
|
|
6
|
+
|
|
7
|
+
Invalid files raise `InvalidPDFError`; documents with no text layer raise `MissingTextLayerError`; unsupported choices raise `ConfigurationError`.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
`extraction` reads positioned words from PyMuPDF. `normalization` produces conservative comparison keys. `alignment` creates one-to-one page pairs. `diff` uses those same word streams for structured text records and annotation coordinates. `api` aggregates the result; `highlighting` consumes its private geometry, so summaries and marks cannot diverge. `cli` calls only the public API.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="310" viewBox="0 0 1200 310" role="img" aria-labelledby="title description">
|
|
2
|
+
<title id="title">SmartPDFCompare</title>
|
|
3
|
+
<desc id="description">A blue document icon with highlighted differences and the SmartPDFCompare title.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="background" x1="0" x2="1" y1="0" y2="1"><stop stop-color="#eff6ff"/><stop offset="1" stop-color="#dbeafe"/></linearGradient>
|
|
6
|
+
<linearGradient id="blue" x1="0" x2="1"><stop stop-color="#2563eb"/><stop offset="1" stop-color="#0ea5e9"/></linearGradient>
|
|
7
|
+
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%"><feDropShadow dx="0" dy="8" stdDeviation="10" flood-color="#1e3a8a" flood-opacity=".17"/></filter>
|
|
8
|
+
</defs>
|
|
9
|
+
<rect width="1200" height="310" rx="30" fill="url(#background)"/>
|
|
10
|
+
<g filter="url(#shadow)"><rect x="88" y="48" width="174" height="214" rx="16" fill="white"/><path d="M214 48v50h48" fill="#bfdbfe"/><path d="M214 48v50h48" fill="none" stroke="#2563eb" stroke-width="7" stroke-linejoin="round"/><path d="M124 126h92M124 154h92M124 210h92" stroke="#93c5fd" stroke-width="11" stroke-linecap="round"/><path d="M124 182h58" stroke="#ef4444" stroke-width="15" stroke-linecap="round"/><path d="M124 234h76" stroke="#22c55e" stroke-width="15" stroke-linecap="round"/></g>
|
|
11
|
+
<circle cx="309" cy="150" r="42" fill="white" stroke="#2563eb" stroke-width="11"/><path d="m341 182 35 35" stroke="#2563eb" stroke-width="16" stroke-linecap="round"/>
|
|
12
|
+
<text x="425" y="143" fill="#0f172a" font-family="Arial, Helvetica, sans-serif" font-size="69" font-weight="700">SmartPDFCompare</text>
|
|
13
|
+
<text x="429" y="196" fill="#475569" font-family="Arial, Helvetica, sans-serif" font-size="29">Clear, coordinate-aware PDF text comparison</text>
|
|
14
|
+
<rect x="429" y="220" width="172" height="10" rx="5" fill="url(#blue)"/>
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "smart-pdf-compare"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Text-aware PDF comparison, highlighting, and reports."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
|
|
13
|
+
authors = [
|
|
14
|
+
{name = "Abdelrahman Elsheikh", email = "abdelrahmanelsheikh39@gmail.com"}
|
|
15
|
+
]
|
|
16
|
+
maintainers = [
|
|
17
|
+
{name = "Abdelrahman Elsheikh", email = "abdelrahmanelsheikh39@gmail.com"}
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
keywords = ["pdf", "comparison", "diff", "pymupdf"]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
]
|
|
30
|
+
dependencies = ["PyMuPDF>=2.0"]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = ["build>=1.0", "pytest>=7.4", "twine>=5.0"]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/SmartPDFCompare/SmartPDFCompare"
|
|
37
|
+
Issues = "https://github.com/SmartPDFCompare/SmartPDFCompare/issues"
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
smart-pdf-compare = "smart_pdf_compare.cli:main"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/smart_pdf_compare"]
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
addopts = "-ra"
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
line-length = 100
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""SmartPDFCompare: structured text comparison and PDF highlighting."""
|
|
2
|
+
from .api import ComparisonOptions, compare_pdfs
|
|
3
|
+
from .exceptions import ConfigurationError, InvalidPDFError, MissingTextLayerError, PDFComparisonError
|
|
4
|
+
from .highlighting import HighlightOutputs, create_side_by_side_report, highlight_differences
|
|
5
|
+
from .normalization import NormalizationOptions
|
|
6
|
+
from .results import ChangeType, ComparisonResult, ComparisonSummary, Difference
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ChangeType", "ComparisonOptions", "ComparisonResult", "ComparisonSummary", "ConfigurationError",
|
|
12
|
+
"Difference", "HighlightOutputs", "InvalidPDFError", "MissingTextLayerError", "NormalizationOptions",
|
|
13
|
+
"PDFComparisonError", "compare_pdfs", "create_side_by_side_report", "highlight_differences",
|
|
14
|
+
]
|