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.
Files changed (30) hide show
  1. smart_pdf_compare-0.1.0/.github/workflows/tests.yml +16 -0
  2. smart_pdf_compare-0.1.0/.gitignore +9 -0
  3. smart_pdf_compare-0.1.0/CHANGELOG.md +5 -0
  4. smart_pdf_compare-0.1.0/CONTRIBUTING.md +3 -0
  5. smart_pdf_compare-0.1.0/LICENSE +21 -0
  6. smart_pdf_compare-0.1.0/PKG-INFO +204 -0
  7. smart_pdf_compare-0.1.0/README.md +178 -0
  8. smart_pdf_compare-0.1.0/docs/api.md +7 -0
  9. smart_pdf_compare-0.1.0/docs/architecture.md +3 -0
  10. smart_pdf_compare-0.1.0/docs/assets/cover.svg +15 -0
  11. smart_pdf_compare-0.1.0/examples/basic_comparison.py +4 -0
  12. smart_pdf_compare-0.1.0/examples/highlight_changes.py +4 -0
  13. smart_pdf_compare-0.1.0/pyproject.toml +50 -0
  14. smart_pdf_compare-0.1.0/src/smart_pdf_compare/__init__.py +14 -0
  15. smart_pdf_compare-0.1.0/src/smart_pdf_compare/__main__.py +3 -0
  16. smart_pdf_compare-0.1.0/src/smart_pdf_compare/alignment.py +121 -0
  17. smart_pdf_compare-0.1.0/src/smart_pdf_compare/api.py +82 -0
  18. smart_pdf_compare-0.1.0/src/smart_pdf_compare/cli.py +51 -0
  19. smart_pdf_compare-0.1.0/src/smart_pdf_compare/diff.py +53 -0
  20. smart_pdf_compare-0.1.0/src/smart_pdf_compare/exceptions.py +17 -0
  21. smart_pdf_compare-0.1.0/src/smart_pdf_compare/extraction.py +59 -0
  22. smart_pdf_compare-0.1.0/src/smart_pdf_compare/highlighting.py +107 -0
  23. smart_pdf_compare-0.1.0/src/smart_pdf_compare/normalization.py +39 -0
  24. smart_pdf_compare-0.1.0/src/smart_pdf_compare/results.py +74 -0
  25. smart_pdf_compare-0.1.0/tests/conftest.py +24 -0
  26. smart_pdf_compare-0.1.0/tests/test_alignment.py +27 -0
  27. smart_pdf_compare-0.1.0/tests/test_cli.py +11 -0
  28. smart_pdf_compare-0.1.0/tests/test_comparison.py +34 -0
  29. smart_pdf_compare-0.1.0/tests/test_highlighting.py +22 -0
  30. 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,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ .venv/
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ *.pdf
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-10-28
4
+
5
+ Initial public release: text extraction through the current PyMuPDF API (`import pymupdf`), Unicode-aware word diffs, three page alignment strategies, highlighted copies, a side-by-side report, CLI, packaging, and tests.
@@ -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&center=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&center=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,4 @@
1
+ from smart_pdf_compare import compare_pdfs
2
+
3
+ result = compare_pdfs("original.pdf", "revised.pdf")
4
+ print(result.to_dict())
@@ -0,0 +1,4 @@
1
+ from smart_pdf_compare import highlight_differences
2
+
3
+ outputs = highlight_differences("original.pdf", "revised.pdf", "diff-output", include_report=True)
4
+ print(outputs)
@@ -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
+ ]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())