docling 0.1.2__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.
- docling-0.1.2/LICENSE +21 -0
- docling-0.1.2/PKG-INFO +132 -0
- docling-0.1.2/README.md +99 -0
- docling-0.1.2/docling/__init__.py +0 -0
- docling-0.1.2/docling/backend/__init__.py +0 -0
- docling-0.1.2/docling/backend/abstract_backend.py +55 -0
- docling-0.1.2/docling/backend/pypdfium2_backend.py +223 -0
- docling-0.1.2/docling/datamodel/__init__.py +0 -0
- docling-0.1.2/docling/datamodel/base_models.py +247 -0
- docling-0.1.2/docling/datamodel/document.py +351 -0
- docling-0.1.2/docling/datamodel/settings.py +32 -0
- docling-0.1.2/docling/document_converter.py +207 -0
- docling-0.1.2/docling/models/__init__.py +0 -0
- docling-0.1.2/docling/models/ds_glm_model.py +82 -0
- docling-0.1.2/docling/models/easyocr_model.py +77 -0
- docling-0.1.2/docling/models/layout_model.py +318 -0
- docling-0.1.2/docling/models/page_assemble_model.py +160 -0
- docling-0.1.2/docling/models/table_structure_model.py +114 -0
- docling-0.1.2/docling/pipeline/__init__.py +0 -0
- docling-0.1.2/docling/pipeline/base_model_pipeline.py +18 -0
- docling-0.1.2/docling/pipeline/standard_model_pipeline.py +40 -0
- docling-0.1.2/docling/utils/__init__.py +0 -0
- docling-0.1.2/docling/utils/layout_utils.py +806 -0
- docling-0.1.2/docling/utils/utils.py +41 -0
- docling-0.1.2/pyproject.toml +74 -0
docling-0.1.2/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) [year] [fullname]
|
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.
|
docling-0.1.2/PKG-INFO
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: docling
|
3
|
+
Version: 0.1.2
|
4
|
+
Summary: Docling PDF conversion package
|
5
|
+
Home-page: https://github.com/DS4SD/docling
|
6
|
+
License: MIT
|
7
|
+
Keywords: docling,convert,document,pdf,layout model,segmentation,table structure,table former
|
8
|
+
Author: Christoph Auer
|
9
|
+
Author-email: cau@zurich.ibm.com
|
10
|
+
Requires-Python: >=3.11,<4.0
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
15
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
21
|
+
Requires-Dist: deepsearch-glm (>=0.18.4,<1)
|
22
|
+
Requires-Dist: deepsearch-toolkit (>=0.47.0,<1)
|
23
|
+
Requires-Dist: docling-core (>=0.2.0,<0.3.0)
|
24
|
+
Requires-Dist: docling-ibm-models (>=0.2.0,<0.3.0)
|
25
|
+
Requires-Dist: filetype (>=1.2.0,<2.0.0)
|
26
|
+
Requires-Dist: huggingface_hub (>=0.23,<1)
|
27
|
+
Requires-Dist: pydantic (>=2.0.0,<3.0.0)
|
28
|
+
Requires-Dist: pydantic-settings (>=2.3.0,<3.0.0)
|
29
|
+
Requires-Dist: pypdfium2 (>=4.30.0,<5.0.0)
|
30
|
+
Project-URL: Repository, https://github.com/DS4SD/docling
|
31
|
+
Description-Content-Type: text/markdown
|
32
|
+
|
33
|
+
<p align="center">
|
34
|
+
<a href="https://github.com/ds4sd/docling"> <img loading="lazy" alt="Docling" src="https://github.com/DS4SD/docling/raw/main/logo.png" width="150" /> </a>
|
35
|
+
</p>
|
36
|
+
|
37
|
+
# Docling
|
38
|
+
|
39
|
+
Docling bundles PDF document conversion to JSON and Markdown in an easy, self-contained package.
|
40
|
+
|
41
|
+
## Features
|
42
|
+
* ⚡ Converts any PDF document to JSON or Markdown format, stable and lightning fast
|
43
|
+
* 📑 Understands detailed page layout, reading order and recovers table structures
|
44
|
+
* 📝 Extracts metadata from the document, such as title, authors, references and language
|
45
|
+
* 🔍 Optionally applies OCR (use with scanned PDFs)
|
46
|
+
|
47
|
+
## Setup
|
48
|
+
|
49
|
+
You need Python 3.11 and poetry. Install poetry from [here](https://python-poetry.org/docs/#installing-with-the-official-installer).
|
50
|
+
|
51
|
+
Once you have `poetry` installed, create an environment and install the package:
|
52
|
+
|
53
|
+
```bash
|
54
|
+
poetry env use $(which python3.11)
|
55
|
+
poetry shell
|
56
|
+
poetry install
|
57
|
+
```
|
58
|
+
|
59
|
+
**Notes**:
|
60
|
+
* Works on macOS and Linux environments. Windows platforms are currently not tested.
|
61
|
+
|
62
|
+
|
63
|
+
## Usage
|
64
|
+
|
65
|
+
For basic usage, see the [convert.py](https://github.com/DS4SD/docling/blob/main/examples/convert.py) example module. Run with:
|
66
|
+
|
67
|
+
```
|
68
|
+
python examples/convert.py
|
69
|
+
```
|
70
|
+
The output of the above command will be written to `./scratch`.
|
71
|
+
|
72
|
+
### Enable or disable pipeline features
|
73
|
+
|
74
|
+
You can control if table structure recognition or OCR should be performed by arguments passed to `DocumentConverter`
|
75
|
+
```python
|
76
|
+
doc_converter = DocumentConverter(
|
77
|
+
artifacts_path=artifacts_path,
|
78
|
+
pipeline_options=PipelineOptions(do_table_structure=False, # Controls if table structure is recovered.
|
79
|
+
do_ocr=True), # Controls if OCR is applied (ignores programmatic content)
|
80
|
+
)
|
81
|
+
```
|
82
|
+
|
83
|
+
### Impose limits on the document size
|
84
|
+
|
85
|
+
You can limit the file size and number of pages which should be allowed to process per document.
|
86
|
+
```python
|
87
|
+
paths = [Path("./test/data/2206.01062.pdf")]
|
88
|
+
|
89
|
+
input = DocumentConversionInput.from_paths(
|
90
|
+
paths, limits=DocumentLimits(max_num_pages=100, max_file_size=20971520)
|
91
|
+
)
|
92
|
+
```
|
93
|
+
|
94
|
+
### Convert from binary PDF streams
|
95
|
+
|
96
|
+
You can convert PDFs from a binary stream instead of from the filesystem as follows:
|
97
|
+
```python
|
98
|
+
buf = BytesIO(your_binary_stream)
|
99
|
+
docs = [DocumentStream(filename="my_doc.pdf", stream=buf)]
|
100
|
+
input = DocumentConversionInput.from_streams(docs)
|
101
|
+
converted_docs = doc_converter.convert(input)
|
102
|
+
```
|
103
|
+
### Limit resource usage
|
104
|
+
|
105
|
+
You can limit the CPU threads used by `docling` by setting the environment variable `OMP_NUM_THREADS` accordingly. The default setting is using 4 CPU threads.
|
106
|
+
|
107
|
+
|
108
|
+
## Contributing
|
109
|
+
|
110
|
+
Please read [Contributing to Docling](https://github.com/DS4SD/docling/blob/main/CONTRIBUTING.md) for details.
|
111
|
+
|
112
|
+
|
113
|
+
## References
|
114
|
+
|
115
|
+
If you use `Docling` in your projects, please consider citing the following:
|
116
|
+
|
117
|
+
```bib
|
118
|
+
@software{Docling,
|
119
|
+
author = {Deep Search Team},
|
120
|
+
month = {7},
|
121
|
+
title = {{Docling}},
|
122
|
+
url = {https://github.com/DS4SD/docling},
|
123
|
+
version = {main},
|
124
|
+
year = {2024}
|
125
|
+
}
|
126
|
+
```
|
127
|
+
|
128
|
+
## License
|
129
|
+
|
130
|
+
The `Docling` codebase is under MIT license.
|
131
|
+
For individual model usage, please refer to the model licenses found in the original packages.
|
132
|
+
|
docling-0.1.2/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
<p align="center">
|
2
|
+
<a href="https://github.com/ds4sd/docling"> <img loading="lazy" alt="Docling" src="https://github.com/DS4SD/docling/raw/main/logo.png" width="150" /> </a>
|
3
|
+
</p>
|
4
|
+
|
5
|
+
# Docling
|
6
|
+
|
7
|
+
Docling bundles PDF document conversion to JSON and Markdown in an easy, self-contained package.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
* ⚡ Converts any PDF document to JSON or Markdown format, stable and lightning fast
|
11
|
+
* 📑 Understands detailed page layout, reading order and recovers table structures
|
12
|
+
* 📝 Extracts metadata from the document, such as title, authors, references and language
|
13
|
+
* 🔍 Optionally applies OCR (use with scanned PDFs)
|
14
|
+
|
15
|
+
## Setup
|
16
|
+
|
17
|
+
You need Python 3.11 and poetry. Install poetry from [here](https://python-poetry.org/docs/#installing-with-the-official-installer).
|
18
|
+
|
19
|
+
Once you have `poetry` installed, create an environment and install the package:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
poetry env use $(which python3.11)
|
23
|
+
poetry shell
|
24
|
+
poetry install
|
25
|
+
```
|
26
|
+
|
27
|
+
**Notes**:
|
28
|
+
* Works on macOS and Linux environments. Windows platforms are currently not tested.
|
29
|
+
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
For basic usage, see the [convert.py](https://github.com/DS4SD/docling/blob/main/examples/convert.py) example module. Run with:
|
34
|
+
|
35
|
+
```
|
36
|
+
python examples/convert.py
|
37
|
+
```
|
38
|
+
The output of the above command will be written to `./scratch`.
|
39
|
+
|
40
|
+
### Enable or disable pipeline features
|
41
|
+
|
42
|
+
You can control if table structure recognition or OCR should be performed by arguments passed to `DocumentConverter`
|
43
|
+
```python
|
44
|
+
doc_converter = DocumentConverter(
|
45
|
+
artifacts_path=artifacts_path,
|
46
|
+
pipeline_options=PipelineOptions(do_table_structure=False, # Controls if table structure is recovered.
|
47
|
+
do_ocr=True), # Controls if OCR is applied (ignores programmatic content)
|
48
|
+
)
|
49
|
+
```
|
50
|
+
|
51
|
+
### Impose limits on the document size
|
52
|
+
|
53
|
+
You can limit the file size and number of pages which should be allowed to process per document.
|
54
|
+
```python
|
55
|
+
paths = [Path("./test/data/2206.01062.pdf")]
|
56
|
+
|
57
|
+
input = DocumentConversionInput.from_paths(
|
58
|
+
paths, limits=DocumentLimits(max_num_pages=100, max_file_size=20971520)
|
59
|
+
)
|
60
|
+
```
|
61
|
+
|
62
|
+
### Convert from binary PDF streams
|
63
|
+
|
64
|
+
You can convert PDFs from a binary stream instead of from the filesystem as follows:
|
65
|
+
```python
|
66
|
+
buf = BytesIO(your_binary_stream)
|
67
|
+
docs = [DocumentStream(filename="my_doc.pdf", stream=buf)]
|
68
|
+
input = DocumentConversionInput.from_streams(docs)
|
69
|
+
converted_docs = doc_converter.convert(input)
|
70
|
+
```
|
71
|
+
### Limit resource usage
|
72
|
+
|
73
|
+
You can limit the CPU threads used by `docling` by setting the environment variable `OMP_NUM_THREADS` accordingly. The default setting is using 4 CPU threads.
|
74
|
+
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
Please read [Contributing to Docling](https://github.com/DS4SD/docling/blob/main/CONTRIBUTING.md) for details.
|
79
|
+
|
80
|
+
|
81
|
+
## References
|
82
|
+
|
83
|
+
If you use `Docling` in your projects, please consider citing the following:
|
84
|
+
|
85
|
+
```bib
|
86
|
+
@software{Docling,
|
87
|
+
author = {Deep Search Team},
|
88
|
+
month = {7},
|
89
|
+
title = {{Docling}},
|
90
|
+
url = {https://github.com/DS4SD/docling},
|
91
|
+
version = {main},
|
92
|
+
year = {2024}
|
93
|
+
}
|
94
|
+
```
|
95
|
+
|
96
|
+
## License
|
97
|
+
|
98
|
+
The `Docling` codebase is under MIT license.
|
99
|
+
For individual model usage, please refer to the model licenses found in the original packages.
|
File without changes
|
File without changes
|
@@ -0,0 +1,55 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from io import BytesIO
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any, Iterable, Optional, Union
|
5
|
+
|
6
|
+
from PIL import Image
|
7
|
+
|
8
|
+
|
9
|
+
class PdfPageBackend(ABC):
|
10
|
+
def __init__(self, page_obj: Any) -> object:
|
11
|
+
pass
|
12
|
+
|
13
|
+
@abstractmethod
|
14
|
+
def get_text_in_rect(self, bbox: "BoundingBox") -> str:
|
15
|
+
pass
|
16
|
+
|
17
|
+
@abstractmethod
|
18
|
+
def get_text_cells(self) -> Iterable["Cell"]:
|
19
|
+
pass
|
20
|
+
|
21
|
+
@abstractmethod
|
22
|
+
def get_page_image(
|
23
|
+
self, scale: int = 1, cropbox: Optional["BoundingBox"] = None
|
24
|
+
) -> Image.Image:
|
25
|
+
pass
|
26
|
+
|
27
|
+
@abstractmethod
|
28
|
+
def get_size(self) -> "PageSize":
|
29
|
+
pass
|
30
|
+
|
31
|
+
@abstractmethod
|
32
|
+
def unload(self):
|
33
|
+
pass
|
34
|
+
|
35
|
+
|
36
|
+
class PdfDocumentBackend(ABC):
|
37
|
+
@abstractmethod
|
38
|
+
def __init__(self, path_or_stream: Iterable[Union[BytesIO, Path]]):
|
39
|
+
pass
|
40
|
+
|
41
|
+
@abstractmethod
|
42
|
+
def load_page(self, page_no: int) -> PdfPageBackend:
|
43
|
+
pass
|
44
|
+
|
45
|
+
@abstractmethod
|
46
|
+
def page_count(self) -> int:
|
47
|
+
pass
|
48
|
+
|
49
|
+
@abstractmethod
|
50
|
+
def is_valid(self) -> bool:
|
51
|
+
pass
|
52
|
+
|
53
|
+
@abstractmethod
|
54
|
+
def unload(self):
|
55
|
+
pass
|
@@ -0,0 +1,223 @@
|
|
1
|
+
import random
|
2
|
+
from io import BytesIO
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Iterable, List, Optional, Union
|
5
|
+
|
6
|
+
import pypdfium2 as pdfium
|
7
|
+
from PIL import Image, ImageDraw
|
8
|
+
from pypdfium2 import PdfPage
|
9
|
+
|
10
|
+
from docling.backend.abstract_backend import PdfDocumentBackend, PdfPageBackend
|
11
|
+
from docling.datamodel.base_models import BoundingBox, Cell, CoordOrigin, PageSize
|
12
|
+
|
13
|
+
|
14
|
+
class PyPdfiumPageBackend(PdfPageBackend):
|
15
|
+
def __init__(self, page_obj: PdfPage):
|
16
|
+
super().__init__(page_obj)
|
17
|
+
self._ppage = page_obj
|
18
|
+
self.text_page = None
|
19
|
+
|
20
|
+
def get_text_in_rect(self, bbox: BoundingBox) -> str:
|
21
|
+
if not self.text_page:
|
22
|
+
self.text_page = self._ppage.get_textpage()
|
23
|
+
|
24
|
+
if bbox.coord_origin != CoordOrigin.BOTTOMLEFT:
|
25
|
+
bbox = bbox.to_bottom_left_origin(self.get_size().height)
|
26
|
+
|
27
|
+
text_piece = self.text_page.get_text_bounded(*bbox.as_tuple())
|
28
|
+
|
29
|
+
return text_piece
|
30
|
+
|
31
|
+
def get_text_cells(self) -> Iterable[Cell]:
|
32
|
+
if not self.text_page:
|
33
|
+
self.text_page = self._ppage.get_textpage()
|
34
|
+
|
35
|
+
cells = []
|
36
|
+
cell_counter = 0
|
37
|
+
|
38
|
+
page_size = self.get_size()
|
39
|
+
|
40
|
+
for i in range(self.text_page.count_rects()):
|
41
|
+
rect = self.text_page.get_rect(i)
|
42
|
+
text_piece = self.text_page.get_text_bounded(*rect)
|
43
|
+
x0, y0, x1, y1 = rect
|
44
|
+
cells.append(
|
45
|
+
Cell(
|
46
|
+
id=cell_counter,
|
47
|
+
text=text_piece,
|
48
|
+
bbox=BoundingBox(
|
49
|
+
l=x0, b=y0, r=x1, t=y1, coord_origin=CoordOrigin.BOTTOMLEFT
|
50
|
+
).to_top_left_origin(page_size.height),
|
51
|
+
)
|
52
|
+
)
|
53
|
+
cell_counter += 1
|
54
|
+
|
55
|
+
# PyPdfium2 produces very fragmented cells, with sub-word level boundaries, in many PDFs.
|
56
|
+
# The cell merging code below is to clean this up.
|
57
|
+
def merge_horizontal_cells(
|
58
|
+
cells: List[Cell],
|
59
|
+
horizontal_threshold_factor: float = 1.0,
|
60
|
+
vertical_threshold_factor: float = 0.5,
|
61
|
+
) -> List[Cell]:
|
62
|
+
if not cells:
|
63
|
+
return []
|
64
|
+
|
65
|
+
def group_rows(cells: List[Cell]) -> List[List[Cell]]:
|
66
|
+
rows = []
|
67
|
+
current_row = [cells[0]]
|
68
|
+
row_top = cells[0].bbox.t
|
69
|
+
row_bottom = cells[0].bbox.b
|
70
|
+
row_height = cells[0].bbox.height
|
71
|
+
|
72
|
+
for cell in cells[1:]:
|
73
|
+
vertical_threshold = row_height * vertical_threshold_factor
|
74
|
+
if (
|
75
|
+
abs(cell.bbox.t - row_top) <= vertical_threshold
|
76
|
+
and abs(cell.bbox.b - row_bottom) <= vertical_threshold
|
77
|
+
):
|
78
|
+
current_row.append(cell)
|
79
|
+
row_top = min(row_top, cell.bbox.t)
|
80
|
+
row_bottom = max(row_bottom, cell.bbox.b)
|
81
|
+
row_height = row_bottom - row_top
|
82
|
+
else:
|
83
|
+
rows.append(current_row)
|
84
|
+
current_row = [cell]
|
85
|
+
row_top = cell.bbox.t
|
86
|
+
row_bottom = cell.bbox.b
|
87
|
+
row_height = cell.bbox.height
|
88
|
+
|
89
|
+
if current_row:
|
90
|
+
rows.append(current_row)
|
91
|
+
|
92
|
+
return rows
|
93
|
+
|
94
|
+
def merge_row(row: List[Cell]) -> List[Cell]:
|
95
|
+
merged = []
|
96
|
+
current_group = [row[0]]
|
97
|
+
|
98
|
+
for cell in row[1:]:
|
99
|
+
prev_cell = current_group[-1]
|
100
|
+
avg_height = (prev_cell.bbox.height + cell.bbox.height) / 2
|
101
|
+
if (
|
102
|
+
cell.bbox.l - prev_cell.bbox.r
|
103
|
+
<= avg_height * horizontal_threshold_factor
|
104
|
+
):
|
105
|
+
current_group.append(cell)
|
106
|
+
else:
|
107
|
+
merged.append(merge_group(current_group))
|
108
|
+
current_group = [cell]
|
109
|
+
|
110
|
+
if current_group:
|
111
|
+
merged.append(merge_group(current_group))
|
112
|
+
|
113
|
+
return merged
|
114
|
+
|
115
|
+
def merge_group(group: List[Cell]) -> Cell:
|
116
|
+
if len(group) == 1:
|
117
|
+
return group[0]
|
118
|
+
|
119
|
+
merged_text = "".join(cell.text for cell in group)
|
120
|
+
merged_bbox = BoundingBox(
|
121
|
+
l=min(cell.bbox.l for cell in group),
|
122
|
+
t=min(cell.bbox.t for cell in group),
|
123
|
+
r=max(cell.bbox.r for cell in group),
|
124
|
+
b=max(cell.bbox.b for cell in group),
|
125
|
+
)
|
126
|
+
return Cell(id=group[0].id, text=merged_text, bbox=merged_bbox)
|
127
|
+
|
128
|
+
rows = group_rows(cells)
|
129
|
+
merged_cells = [cell for row in rows for cell in merge_row(row)]
|
130
|
+
|
131
|
+
for i, cell in enumerate(merged_cells, 1):
|
132
|
+
cell.id = i
|
133
|
+
|
134
|
+
return merged_cells
|
135
|
+
|
136
|
+
def draw_clusters_and_cells():
|
137
|
+
image = self.get_page_image()
|
138
|
+
draw = ImageDraw.Draw(image)
|
139
|
+
for c in cells:
|
140
|
+
x0, y0, x1, y1 = c.bbox.as_tuple()
|
141
|
+
cell_color = (
|
142
|
+
random.randint(30, 140),
|
143
|
+
random.randint(30, 140),
|
144
|
+
random.randint(30, 140),
|
145
|
+
)
|
146
|
+
draw.rectangle([(x0, y0), (x1, y1)], outline=cell_color)
|
147
|
+
image.show()
|
148
|
+
|
149
|
+
# before merge:
|
150
|
+
# draw_clusters_and_cells()
|
151
|
+
|
152
|
+
cells = merge_horizontal_cells(cells)
|
153
|
+
|
154
|
+
# after merge:
|
155
|
+
# draw_clusters_and_cells()
|
156
|
+
|
157
|
+
return cells
|
158
|
+
|
159
|
+
def get_page_image(
|
160
|
+
self, scale: int = 1, cropbox: Optional[BoundingBox] = None
|
161
|
+
) -> Image.Image:
|
162
|
+
|
163
|
+
page_size = self.get_size()
|
164
|
+
|
165
|
+
if not cropbox:
|
166
|
+
cropbox = BoundingBox(
|
167
|
+
l=0,
|
168
|
+
r=page_size.width,
|
169
|
+
t=0,
|
170
|
+
b=page_size.height,
|
171
|
+
coord_origin=CoordOrigin.TOPLEFT,
|
172
|
+
)
|
173
|
+
padbox = BoundingBox(
|
174
|
+
l=0, r=0, t=0, b=0, coord_origin=CoordOrigin.BOTTOMLEFT
|
175
|
+
)
|
176
|
+
else:
|
177
|
+
padbox = cropbox.to_bottom_left_origin(page_size.height)
|
178
|
+
padbox.r = page_size.width - padbox.r
|
179
|
+
padbox.t = page_size.height - padbox.t
|
180
|
+
|
181
|
+
image = (
|
182
|
+
self._ppage.render(
|
183
|
+
scale=scale * 1.5,
|
184
|
+
rotation=0, # no additional rotation
|
185
|
+
crop=padbox.as_tuple(),
|
186
|
+
)
|
187
|
+
.to_pil()
|
188
|
+
.resize(size=(round(cropbox.width * scale), round(cropbox.height * scale)))
|
189
|
+
) # We resize the image from 1.5x the given scale to make it sharper.
|
190
|
+
|
191
|
+
return image
|
192
|
+
|
193
|
+
def get_size(self) -> PageSize:
|
194
|
+
return PageSize(width=self._ppage.get_width(), height=self._ppage.get_height())
|
195
|
+
|
196
|
+
def unload(self):
|
197
|
+
self._ppage = None
|
198
|
+
self.text_page = None
|
199
|
+
|
200
|
+
|
201
|
+
class PyPdfiumDocumentBackend(PdfDocumentBackend):
|
202
|
+
def __init__(self, path_or_stream: Iterable[Union[BytesIO, Path]]):
|
203
|
+
super().__init__(path_or_stream)
|
204
|
+
|
205
|
+
if isinstance(path_or_stream, Path):
|
206
|
+
self._pdoc = pdfium.PdfDocument(path_or_stream)
|
207
|
+
elif isinstance(path_or_stream, BytesIO):
|
208
|
+
self._pdoc = pdfium.PdfDocument(
|
209
|
+
path_or_stream
|
210
|
+
) # TODO Fix me, won't accept bytes.
|
211
|
+
|
212
|
+
def page_count(self) -> int:
|
213
|
+
return len(self._pdoc)
|
214
|
+
|
215
|
+
def load_page(self, page_no: int) -> PdfPage:
|
216
|
+
return PyPdfiumPageBackend(self._pdoc[page_no])
|
217
|
+
|
218
|
+
def is_valid(self) -> bool:
|
219
|
+
return self.page_count() > 0
|
220
|
+
|
221
|
+
def unload(self):
|
222
|
+
self._pdoc.close()
|
223
|
+
self._pdoc = None
|
File without changes
|