pdf-html 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 (35) hide show
  1. pdf_html-0.1.0/.gitignore +244 -0
  2. pdf_html-0.1.0/.vscode/settings.json +3 -0
  3. pdf_html-0.1.0/LICENSE +21 -0
  4. pdf_html-0.1.0/PKG-INFO +296 -0
  5. pdf_html-0.1.0/README.md +266 -0
  6. pdf_html-0.1.0/TUTORIAL.md +87 -0
  7. pdf_html-0.1.0/arch.mmd +44 -0
  8. pdf_html-0.1.0/logo.png +0 -0
  9. pdf_html-0.1.0/pyproject.toml +53 -0
  10. pdf_html-0.1.0/src/pdf_html/__init__.py +7 -0
  11. pdf_html-0.1.0/src/pdf_html/ast.py +124 -0
  12. pdf_html-0.1.0/src/pdf_html/callout_detector.py +6 -0
  13. pdf_html-0.1.0/src/pdf_html/cli.py +147 -0
  14. pdf_html-0.1.0/src/pdf_html/extractor.py +272 -0
  15. pdf_html-0.1.0/src/pdf_html/header_footer.py +118 -0
  16. pdf_html-0.1.0/src/pdf_html/list_parser.py +158 -0
  17. pdf_html-0.1.0/src/pdf_html/reading_order.py +154 -0
  18. pdf_html-0.1.0/src/pdf_html/renderer.py +220 -0
  19. pdf_html-0.1.0/src/pdf_html/structure.py +218 -0
  20. pdf_html-0.1.0/src/pdf_html/style_profiler.py +117 -0
  21. pdf_html-0.1.0/src/pdf_html/table_reconstructor.py +237 -0
  22. pdf_html-0.1.0/tests/fixtures/brochure.pdf +80 -0
  23. pdf_html-0.1.0/tests/fixtures/make_fixtures.py +195 -0
  24. pdf_html-0.1.0/tests/fixtures/report.pdf +93 -0
  25. pdf_html-0.1.0/tests/fixtures/slides.pdf +93 -0
  26. pdf_html-0.1.0/tests/fixtures/table.pdf +74 -0
  27. pdf_html-0.1.0/tests/fixtures/two_column.pdf +74 -0
  28. pdf_html-0.1.0/tests/test_extractor.py +121 -0
  29. pdf_html-0.1.0/tests/test_header_footer.py +72 -0
  30. pdf_html-0.1.0/tests/test_pipeline.py +134 -0
  31. pdf_html-0.1.0/tests/test_reading_order.py +68 -0
  32. pdf_html-0.1.0/tests/test_structure.py +108 -0
  33. pdf_html-0.1.0/tests/test_style_profiler.py +58 -0
  34. pdf_html-0.1.0/tests/test_table_reconstructor.py +162 -0
  35. pdf_html-0.1.0/uv.lock +375 -0
@@ -0,0 +1,244 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ *.lcov
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ # Pipfile.lock
97
+
98
+ # UV
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ # uv.lock
103
+
104
+ # poetry
105
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
106
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
107
+ # commonly ignored for libraries.
108
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
109
+ # poetry.lock
110
+ # poetry.toml
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
115
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
116
+ # pdm.lock
117
+ # pdm.toml
118
+ .pdm-python
119
+ .pdm-build/
120
+
121
+ # pixi
122
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
123
+ # pixi.lock
124
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
125
+ # in the .venv directory. It is recommended not to include this directory in version control.
126
+ .pixi/*
127
+ !.pixi/config.toml
128
+
129
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
130
+ __pypackages__/
131
+
132
+ # Celery stuff
133
+ celerybeat-schedule*
134
+ celerybeat.pid
135
+
136
+ # Redis
137
+ *.rdb
138
+ *.aof
139
+ *.pid
140
+
141
+ # RabbitMQ
142
+ mnesia/
143
+ rabbitmq/
144
+ rabbitmq-data/
145
+
146
+ # ActiveMQ
147
+ activemq-data/
148
+
149
+ # SageMath parsed files
150
+ *.sage.py
151
+
152
+ # Environments
153
+ .env
154
+ .envrc
155
+ .venv
156
+ env/
157
+ venv/
158
+ ENV/
159
+ env.bak/
160
+ venv.bak/
161
+
162
+ # Spyder project settings
163
+ .spyderproject
164
+ .spyproject
165
+
166
+ # Rope project settings
167
+ .ropeproject
168
+
169
+ # mkdocs documentation
170
+ /site
171
+
172
+ # mypy
173
+ .mypy_cache/
174
+ .dmypy.json
175
+ dmypy.json
176
+
177
+ # Pyre type checker
178
+ .pyre/
179
+
180
+ # pytype static type analyzer
181
+ .pytype/
182
+
183
+ # Cython debug symbols
184
+ cython_debug/
185
+
186
+ # PyCharm
187
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
188
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
189
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
190
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
191
+ # .idea/
192
+
193
+ # Abstra
194
+ # Abstra is an AI-powered process automation framework.
195
+ # Ignore directories containing user credentials, local state, and settings.
196
+ # Learn more at https://abstra.io/docs
197
+ .abstra/
198
+
199
+ # Visual Studio Code
200
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
201
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
202
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
203
+ # you could uncomment the following to ignore the entire vscode folder
204
+ # .vscode/
205
+ # Temporary file for partial code execution
206
+ tempCodeRunnerFile.py
207
+
208
+ # Ruff stuff:
209
+ .ruff_cache/
210
+
211
+ # PyPI configuration file
212
+ .pypirc
213
+
214
+ # Marimo
215
+ marimo/_static/
216
+ marimo/_lsp/
217
+ __marimo__/
218
+
219
+ # Streamlit
220
+ .streamlit/secrets.toml
221
+
222
+ # Internal development files (workflow guide ยง4)
223
+ REQS.md
224
+ .github/instructions/workflow-instructions.md
225
+ .github/*instructions.md
226
+
227
+ tests/fixtures/*.html
228
+ # Only the deterministic generated fixtures are committed; ad-hoc test PDFs
229
+ # (often copyrighted course material) stay local.
230
+ tests/fixtures/*.pdf
231
+ tests/fixtures/*.PDF
232
+ !tests/fixtures/report.pdf
233
+ !tests/fixtures/two_column.pdf
234
+ !tests/fixtures/slides.pdf
235
+ !tests/fixtures/brochure.pdf
236
+ !tests/fixtures/table.pdf
237
+ chain*
238
+
239
+ # Local conversion artifacts at repo root (often copyrighted sources โ€” never commit)
240
+ /*.pdf
241
+ /*.html
242
+
243
+ # macOS
244
+ .DS_Store
@@ -0,0 +1,3 @@
1
+ {
2
+ "liveServer.settings.port": 5501
3
+ }
pdf_html-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Omar (Top Gear Technologies LLC)
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,296 @@
1
+ Metadata-Version: 2.5
2
+ Name: pdf-html
3
+ Version: 0.1.0
4
+ Summary: Early-stage geometry-first PDF-to-HTML converter for text-based PDFs โ€” verbatim text, source typography, zero images.
5
+ Project-URL: Homepage, https://github.com/OmarMWarraich/pdf-html
6
+ Project-URL: Repository, https://github.com/OmarMWarraich/pdf-html
7
+ Project-URL: Issues, https://github.com/OmarMWarraich/pdf-html/issues
8
+ Project-URL: Documentation, https://github.com/OmarMWarraich/pdf-html#readme
9
+ Author: Omar Warraich
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: converter,document-conversion,geometry-first,html,pdf,pdf-to-html,pymupdf,semantic-html,text-based-pdf
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: End Users/Desktop
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Office/Business
25
+ Classifier: Topic :: Text Processing :: Markup :: HTML
26
+ Classifier: Topic :: Utilities
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: pymupdf>=1.24
29
+ Description-Content-Type: text/markdown
30
+
31
+ <div align="center">
32
+
33
+ <img src="logo.png" alt="pdf-html logo" width="120" height="120" />
34
+
35
+ # pdf-html
36
+
37
+ **Early-stage geometry-first PDF โ†’ HTML converter for text-based PDFs โ€” verbatim text, source typography, zero images.**
38
+
39
+ [![Python](https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/)
40
+ [![PyMuPDF](https://img.shields.io/badge/powered%20by-PyMuPDF-orange)](https://pymupdf.readthedocs.io/)
41
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](#-license)
42
+ [![Tests](https://img.shields.io/badge/tests-56%20passing-brightgreen?logo=pytest&logoColor=white)](#-testing)
43
+ [![Typed](https://img.shields.io/badge/typing-fully%20typed-blue)](#)
44
+ [![No LLM](https://img.shields.io/badge/NLP%2FLLM-none%2C%20pure%20geometry-purple)](#-design-principles)
45
+
46
+ *One command in. One elegant, dependency-free HTML file out.*
47
+
48
+ </div>
49
+
50
+ ---
51
+
52
+ ## ๐ŸŽฏ What it does
53
+
54
+ `pdf-html` is an **early-stage, geometry-first PDF-to-HTML converter** for **text-based PDFs**. It rebuilds the document as a **single self-contained HTML5 file** that mirrors the source document's look and structure:
55
+
56
+ - โœ… Best for: text-based PDFs with real text layers, headings, lists, tables, and multi-column layouts
57
+ - โš ๏ธ Not a universal OCR-first converter: scanned/image-heavy PDFs still need an OCR pre-pass or a dedicated workflow
58
+
59
+ - ๐Ÿท๏ธ **Heading hierarchy** โ€” font-size tiers become real `<h1>`โ€“`<h6>`
60
+ - ๐ŸŽจ **Typography & color** โ€” the page CSS is derived from the document's own fonts, sizes, and palette
61
+ - ๐Ÿ“Š **Tables** โ€” ruled tables are rebuilt as real `<table>` elements, including lists *inside* cells and rows that continue across page breaks
62
+ - ๐Ÿ“ **Lists** โ€” bullets and numbered items become nested `<ul>`/`<ol>` (indent decides nesting)
63
+ - ๐Ÿงญ **Reading order** โ€” multi-column layouts are re-linearized column by column
64
+ - โœ‚๏ธ **Page furniture** โ€” repeated running headers/footers and page numbers are stripped
65
+ - ๐Ÿ”’ **Text is verbatim** โ€” never summarized, reworded, or reordered within a block
66
+ - ๐Ÿšซ **No images, ever** โ€” image content is dropped by design; text alone carries the document
67
+
68
+ ## ๐Ÿงญ Early-stage scope
69
+
70
+ This is an **early-stage, geometry-first conversion tool for text-based PDFs**. It is designed to be reliable and deterministic where the PDF has a usable text layer, but it is not a universal OCR-first converter for scanned documents, forms, or arbitrary image-heavy PDFs.
71
+
72
+ ### What it does well
73
+
74
+ - text-based PDFs with real text layers
75
+ - styled HTML with verbatim text
76
+ - heading hierarchy, lists, and multi-column layout reconstruction
77
+ - ruled table reconstruction with cell-aware content flow
78
+
79
+ ### What it does not yet do well
80
+
81
+ - OCR and scanned-document support are planned as separate, opt-in paths
82
+ - arbitrary image-heavy PDFs or forms with weak text layers
83
+ - pixel-perfect visual reconstruction of every layout edge case
84
+
85
+ ## โšก Quick start
86
+
87
+ ```bash
88
+ # install (Python 3.10+)
89
+ uv pip install . # or: pip install .
90
+
91
+ # convert
92
+ pdf-html report.pdf -o report.html
93
+
94
+ # open report.html in any browser โ€” no external assets needed
95
+ ```
96
+
97
+ ## ๐Ÿ–ฅ๏ธ CLI reference
98
+
99
+ ```bash
100
+ pdf-html INPUT.pdf -o out.html [--extractor pymupdf|pdftotext]
101
+ [--style auto|default] [--paginate] [--no-tables] [--no-callouts]
102
+ [--keep-headers] [--allow-scanned]
103
+ ```
104
+
105
+ | Flag | Default | What it does |
106
+ |---|---|---|
107
+ | `-o, --output` | *(required)* | Path of the HTML file to write |
108
+ | `--extractor` | `pymupdf` | Text extraction backend (`pdftotext` fallback planned) |
109
+ | `--style` | `auto` | `auto` derives CSS from the document's own fonts/sizes/colors; `default` uses a clean built-in theme |
110
+ | `--paginate` | off | Wrap each PDF page in a `<section class="sheet">` |
111
+ | `--no-tables` | off | Disable table reconstruction (table text flows as paragraphs) |
112
+ | `--keep-headers` | off | Keep repeated running headers/footers |
113
+ | `--allow-scanned` | off | Convert scanned/image PDFs instead of exiting with an OCR hint |
114
+
115
+ ## ๐Ÿ—๏ธ Architecture
116
+
117
+ A deterministic pipeline โ€” every stage is a small, pure, individually testable module:
118
+
119
+ <details>
120
+ <summary>๐Ÿ” <b>Pipeline diagram</b> โ€” click to enlarge (click again to close) ยท <a href="https://mermaid.live/view#pako:eNqNlV9vmzAUxb_KFQ97WVnzp1nbbKq2No3ykDYIMqnS2IOxL8GqsZExa7uq330GQgismZIHJzb28Tm_XJtXhyqGzhQc13VDSZWM-WYaSgBBXlRhpoDiseyaBFOcgsTCaCJCWU2PhXqiCdEGln45CSAvoo0mWQLB8GfohAW7GDPbUpzAEMJiMIjO4fbZSlATOr_qNeWHcY3UcCVhfd2OerP5t1fIE5LZrZmiJ9ZVhGIK-9L0rJwXOvDWLrx9aNeVwnsLt7sr_TXSp1d5RmQOHyFW0kCKhjBiSFfLioPrXlnNegwl62cdNVnRtiy-hFGT9UYgkfCBpNkX8LSKucAjci_mB-0vkDDUp3OlDG4jGM2zjMtN13bgtRoJPu9JBOZFYOOm1ogUe4Gc_8HGdzIMi9FgOE4-NyMZEWgMdjdZ1GgC7yCacQcNMhg3gsuqwI6g4a8O0vAtDZscVpptk1AlilQCFUVuAf1DZe23WrF2e3JrElkyPtpjYKkWuyrJULsUhYCMZyi43KPwTuSzJjK1bczOwyLGQQxnTfCgki70MZUQzPYOAP_dN7yTghka3PlNaix5-99pUpnbjQiem7yLZukdxLy0s8EjOt9ClmjhMijEqRJdke_BulWJ1FPfsH1eKcwULVK0Z86W2fByZMU32Py-Foo-9swFs6rQll71ZVUO0p906JMLmDSZfTsb9TEFd_-fgis1mpOHKZGGU1is75YTe4twWRYH3ARB1_3qxx4VQzbuO3dZZXc4AHsmPiUm7XH176vgVqgX_PaherCY192gBuSvOt319nb2V_VxndXdtd_pWqr14nvnBJwUdUo4s6-GV6e6_MuXxPb6d97e_gLHTOOG">open full screen โ†—</a></summary>
121
+ <br/>
122
+
123
+ ```mermaid
124
+ ---
125
+ config:
126
+ layout: elk
127
+ theme: neutral
128
+ ---
129
+ flowchart LR
130
+ subgraph S1["๐Ÿ“ฅ 1 ยท Extract"]
131
+ direction TB
132
+ PDF@{ shape: doc, label: "๐Ÿ“„ PDF" }
133
+ EX@{ shape: rect, label: "Extractor<br/>spans + font metadata" }
134
+ PDF --> EX
135
+ end
136
+ subgraph S2["๐Ÿงน 2 ยท Clean &amp; Profile"]
137
+ direction TB
138
+ HF@{ shape: rect, label: "Header/Footer<br/>stripping" }
139
+ SP@{ shape: hex, label: "Style Profiler<br/>body size ยท h1โ€“h6 ยท palette" }
140
+ HF --> SP
141
+ end
142
+ subgraph S3["๐Ÿงญ 3 ยท Layout"]
143
+ direction TB
144
+ RO@{ shape: rect, label: "Reading Order<br/>column clustering" }
145
+ TR@{ shape: fr-rect, label: "Table Reconstructor<br/>per-cell pipeline" }
146
+ end
147
+ subgraph S4["๐Ÿ—๏ธ 4 ยท Structure"]
148
+ direction TB
149
+ SD@{ shape: div-rect, label: "Structure Detector<br/>headings ยท paragraphs ยท lists" }
150
+ LP@{ shape: rect, label: "List Parser<br/>nested ul/ol" }
151
+ AST@{ shape: bow-rect, label: "AST<br/>Document โ†’ Page โ†’ Blocks" }
152
+ SD --> LP --> AST
153
+ end
154
+ subgraph S5["๐ŸŽจ 5 ยท Render"]
155
+ direction TB
156
+ RN@{ shape: rect, label: "Renderer<br/>semantic HTML5 + inline CSS" }
157
+ OUT@{ shape: tag-doc, label: "๐ŸŒ out.html" }
158
+ RN --> OUT
159
+ end
160
+ EX --> HF
161
+ SP --> RO
162
+ SP --> TR
163
+ RO --> SD
164
+ TR --> SD
165
+ AST --> RN
166
+ ```
167
+
168
+ </details>
169
+
170
+ | Module | Responsibility |
171
+ |---|---|
172
+ | `extractor.py` | `TextExtractor` ABC; PyMuPDF backend reads per-span size/weight/color/bbox and detects table regions |
173
+ | `header_footer.py` | Strips spans repeating on โ‰ฅ 60% of pages in the top/bottom 10% bands |
174
+ | `style_profiler.py` | Character-weighted font-size histogram โ†’ body size, heading tiers, color palette |
175
+ | `reading_order.py` | Column detection via x-gap clustering, with a card-grid fallback and straddle guard |
176
+ | `table_reconstructor.py` | Assigns spans to detected cells, runs the full pipeline *inside each cell*, merges cross-page rows |
177
+ | `structure.py` | Classifies lines into headings/paragraphs/list items from geometry + font cues |
178
+ | `list_parser.py` | Indent-based nesting; glyph style only picks `ul` vs `ol`; markers stripped, text verbatim |
179
+ | `ast.py` | Typed document model โ€” `Document โ†’ Page โ†’ Block`, runs carry inline style |
180
+ | `renderer.py` | Single-file HTML5 with one `<style>` block and CSS variables from the profile |
181
+
182
+ ## ๐Ÿ“Š Table reconstruction highlights
183
+
184
+ The hardest part of PDF โ†’ HTML is tables. `pdf-html`:
185
+
186
+ 1. ๐Ÿ” Detects ruled tables geometrically (PyMuPDF `find_tables()`) at extraction time
187
+ 2. ๐Ÿ“Œ Assigns the page's *styled* spans to cells by bounding box โ€” inline bold/color/size survive
188
+ 3. ๐Ÿ”„ Runs the normal line โ†’ paragraph โ†’ list pipeline **inside every cell**, so bullets in cells become real nested lists
189
+ 4. ๐Ÿงต Merges rows that continue across page breaks (empty-first-cell fragments) back into one row โ€” even resuming mid-list-item
190
+ 5. ๐Ÿท๏ธ Promotes a bold-only first row to a `<th>` header row
191
+
192
+ ## ๐Ÿงญ Design principles
193
+
194
+ | Principle | Meaning |
195
+ |---|---|
196
+ | ๐Ÿงฎ **Pure geometry, no AI** | All structure is inferred from font metadata and bounding boxes. No NLP, no LLM, no document-specific regexes |
197
+ | ๐Ÿ”’ **Text is sacred** | Output text is verbatim; only `& < >` are escaped |
198
+ | ๐Ÿšซ **No images** | Spans overlapping image rects are dropped; `<img>` is never emitted |
199
+ | ๐Ÿช‚ **Graceful degradation** | Heuristic failures only affect styling โ€” never text content or order |
200
+ | ๐Ÿ”ง **Tunable & testable** | Every heuristic threshold is a named module-level constant with focused unit tests |
201
+
202
+ ## โš–๏ธ How it compares
203
+
204
+ Every PDF converter picks a trade-off. `pdf-html` optimizes for **semantic, reflowable, styled HTML with a verbatim-text guarantee** โ€” a square none of the established tools occupy:
205
+
206
+ | Tool | Output | Semantic structure | Keeps typography | Deterministic | Footprint |
207
+ |---|---|:---:|:---:|:---:|---|
208
+ | **pdf-html** | Self-contained HTML5 | โœ… `h1โ€“h6`, `ul/ol`, `table` | โœ… CSS derived from the source | โœ… | ~30 MB (PyMuPDF only) |
209
+ | [pdf2htmlEX](https://github.com/pdf2htmlEX/pdf2htmlEX) | Pixel-faithful HTML | โŒ positioned glyphs | โœ… visually | โœ… | C++ toolchain |
210
+ | [Poppler pdftohtml](https://poppler.freedesktop.org/) | Positioned divs / bare text | โŒ | โš ๏ธ partial | โœ… | system package |
211
+ | [pymupdf4llm](https://pypi.org/project/pymupdf4llm/) | Markdown for LLM ingestion | โš ๏ธ headings & lists | โŒ discarded | โœ… | ~30 MB |
212
+ | [marker-pdf](https://pypi.org/project/marker-pdf/) | Markdown/JSON via ML | โœ… | โŒ discarded | โŒ model-dependent | GB-scale models, GPU-friendly |
213
+ | [docling](https://pypi.org/project/docling/) | Markdown/HTML/JSON via ML | โœ… | โŒ discarded | โŒ model-dependent | GB-scale models |
214
+ | [unstructured](https://pypi.org/project/unstructured/) | Element JSON for RAG | โš ๏ธ element types | โŒ | โš ๏ธ | heavy optional deps |
215
+ | Adobe PDF Services | Structured JSON/HTML | โœ… | โš ๏ธ | โŒ | cloud API, paid |
216
+
217
+ **When to choose pdf-html** โ€” you want a *readable, reflowable* document that still looks like the original, produced offline, reproducibly, with text you can trust character-for-character (text-based PDFs, tables included, even across page breaks).
218
+
219
+ **When to choose something else** โ€” you need pixel-perfect visual replicas (pdf2htmlEX), OCR-heavy scanned document conversion (marker, docling), or RAG-oriented element JSON (unstructured).
220
+
221
+ ## ๐Ÿงช Testing
222
+
223
+ 56 tests cover every pipeline stage plus end-to-end CLI runs over deterministic fixture PDFs
224
+ (report, two-column paper, slide deck, brochure, ruled table):
225
+
226
+ ```bash
227
+ uv sync # dev deps (pytest, reportlab)
228
+ uv run pytest # run the suite
229
+ uv run python tests/fixtures/make_fixtures.py # regenerate fixture PDFs
230
+ ```
231
+
232
+ Verbatim-ness is asserted mechanically: every source string drawn into a fixture must appear in the rendered HTML.
233
+
234
+ ## ๐Ÿ“ Project structure
235
+
236
+ ```
237
+ pdf-html/
238
+ โ”œโ”€โ”€ src/pdf_html/
239
+ โ”‚ โ”œโ”€โ”€ cli.py # argparse CLI โ†’ pipeline โ†’ HTML
240
+ โ”‚ โ”œโ”€โ”€ extractor.py # PyMuPDF span + table-region extraction
241
+ โ”‚ โ”œโ”€โ”€ header_footer.py # repeated page-furniture stripping
242
+ โ”‚ โ”œโ”€โ”€ style_profiler.py # font-size histogram โ†’ style profile
243
+ โ”‚ โ”œโ”€โ”€ reading_order.py # column clustering & span ordering
244
+ โ”‚ โ”œโ”€โ”€ table_reconstructor.py # cell assignment, per-cell pipeline, row merging
245
+ โ”‚ โ”œโ”€โ”€ structure.py # heading / paragraph / list classification
246
+ โ”‚ โ”œโ”€โ”€ list_parser.py # nested list folding
247
+ โ”‚ โ”œโ”€โ”€ ast.py # typed document model
248
+ โ”‚ โ””โ”€โ”€ renderer.py # semantic HTML5 + derived CSS
249
+ โ”œโ”€โ”€ tests/ # 56 tests + deterministic PDF fixtures
250
+ โ”œโ”€โ”€ README.md
251
+ โ””โ”€โ”€ TUTORIAL.md # step-by-step usage guide
252
+ ```
253
+
254
+ ## ๐Ÿ›ฃ๏ธ Roadmap
255
+
256
+ - [x] Ruled-table reconstruction with cross-page row merging
257
+ - [x] Column-aware reading order with card-grid detection
258
+ - [x] Repeated header/footer stripping
259
+ - [ ] Borderless-table detection (whitespace-gap heuristic)
260
+ - [ ] Callout/aside detection (`--no-callouts` flag already reserved)
261
+ - [ ] Dependency-free `pdftotext` fallback extractor
262
+ - [ ] `colspan`/`rowspan` from merged-cell geometry
263
+
264
+ ## ๐Ÿค Contributing
265
+
266
+ Contributions welcome! Ground rules:
267
+
268
+ - ๐Ÿ Python 3.10+, type hints throughout
269
+ - ๐Ÿ“ฆ PyMuPDF is the **only** hard runtime dependency
270
+ - ๐Ÿงฉ Keep heuristics small, pure, and individually testable; thresholds as named constants
271
+ - โœ… One feature per commit (`feat|fix|docs|refactor|chore: ...`); update README/TUTORIAL with any user-facing change
272
+ - ๐Ÿงช `uv run pytest` must stay green โ€” fixtures are the contract
273
+
274
+ ## ๐Ÿš€ Releases
275
+
276
+ This project is intentionally published as an **early-stage v0.x** tool: the core pipeline is solid for text-based PDFs, but it is not a universal PDF converter for scanned pages, forms, or OCR-heavy corpora.
277
+
278
+ ```bash
279
+ # 1. bump version in pyproject.toml
280
+ uv build # 2. artifacts land in dist/
281
+ uv publish # 3. push to PyPI (or twine upload dist/*)
282
+ ```
283
+
284
+ ## ๐Ÿ“„ License
285
+
286
+ MIT โ€” see [pyproject.toml](pyproject.toml).
287
+
288
+ ---
289
+
290
+ <div align="center">
291
+
292
+ **Built with ๐Ÿ + ๐Ÿ“ โ€” early-stage geometry over guesswork.**
293
+
294
+ *If this project helped you, consider giving it a โญ!*
295
+
296
+ </div>