BabelDOC 0.2.14__tar.gz → 0.2.16__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.
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.github/workflows/docs.yml +1 -1
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.github/workflows/publish-to-pypi.yml +4 -4
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.github/workflows/test.yml +1 -1
- {babeldoc-0.2.14 → babeldoc-0.2.16}/PKG-INFO +7 -1
- {babeldoc-0.2.14 → babeldoc-0.2.16}/README.md +5 -0
- babeldoc-0.2.16/babeldoc/__init__.py +1 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/assets/assets.py +35 -1
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/assets/embedding_assets_metadata.py +10 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/const.py +1 -1
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/midend/paragraph_finder.py +2 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/midend/styles_and_formulas.py +3 -1
- babeldoc-0.2.16/babeldoc/document_il/midend/table_parser.py +164 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/midend/typesetting.py +16 -6
- babeldoc-0.2.16/babeldoc/docvision/table_detection/rapidocr.py +316 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/high_level.py +12 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/main.py +14 -1
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/translation_config.py +3 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/pyproject.toml +3 -2
- {babeldoc-0.2.14 → babeldoc-0.2.16}/uv.lock +182 -91
- babeldoc-0.2.14/babeldoc/__init__.py +0 -1
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.cursorignore +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.github/dependabot.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.github/labels.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.github/release-drafter.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.github/workflows/codeql.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.github/workflows/labeler.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.gitignore +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/.pre-commit-config.yaml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/LICENSE +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/asynchronize/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/converter.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/backend/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/backend/pdf_creater.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/frontend/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/frontend/il_creater.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/il_version_1.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/il_version_1.rnc +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/il_version_1.rng +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/il_version_1.xsd +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/midend/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/midend/add_debug_information.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/midend/detect_scanned_file.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/midend/il_translator.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/midend/il_translator_llm_only.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/midend/layout_parser.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/midend/remove_descent.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/translator/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/translator/cache.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/translator/translator.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/utils/fontmap.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/utils/layout_helper.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/utils/style_helper.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/document_il/xml_converter.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/docvision/README.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/docvision/__init__.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/docvision/doclayout.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/docvision/rpc_doclayout.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/pdfinterp.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/progress_monitor.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/result_merger.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/split_manager.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/babeldoc/tools/generate_font_metadata.py +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/CODE_OF_CONDUCT.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/CONTRIBUTING.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/CONTRIBUTOR_REWARD.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/ImplementationDetails/README.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/README.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/deploy.sh +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/images/babeldoc-banner.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/images/babeldoc-big-logo.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/images/babeldoc-contributor_reward_example.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/images/babeldoc-preview.gif +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/images/babeldoc-small-logo.png +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/index.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/intro-to-pdf-object.md +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/docs/requirements.txt +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/mkdocs.yml +0 -0
- {babeldoc-0.2.14 → babeldoc-0.2.16}/tests/test_translation_config.py +0 -0
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
git config user.name github-actions[bot]
|
|
18
18
|
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
|
|
19
19
|
- name: Setup uv with Python 3.12
|
|
20
|
-
uses: astral-sh/setup-uv@
|
|
20
|
+
uses: astral-sh/setup-uv@22695119d769bdb6f7032ad67b9bca0ef8c4a174 # v5.4.0
|
|
21
21
|
with:
|
|
22
22
|
python-version: "3.12"
|
|
23
23
|
enable-cache: true
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
36
36
|
|
|
37
37
|
- name: Setup uv with Python 3.12
|
|
38
|
-
uses: astral-sh/setup-uv@
|
|
38
|
+
uses: astral-sh/setup-uv@22695119d769bdb6f7032ad67b9bca0ef8c4a174 # v5.4.0
|
|
39
39
|
with:
|
|
40
40
|
python-version: "3.12"
|
|
41
41
|
enable-cache: true
|
|
@@ -68,7 +68,7 @@ jobs:
|
|
|
68
68
|
run: "uv build"
|
|
69
69
|
|
|
70
70
|
- name: Store the distribution packages
|
|
71
|
-
uses: actions/upload-artifact@v4.6.
|
|
71
|
+
uses: actions/upload-artifact@v4.6.2
|
|
72
72
|
with:
|
|
73
73
|
name: python-package-distributions
|
|
74
74
|
path: dist/
|
|
@@ -89,7 +89,7 @@ jobs:
|
|
|
89
89
|
|
|
90
90
|
steps:
|
|
91
91
|
- name: Download all the dists
|
|
92
|
-
uses: actions/download-artifact@
|
|
92
|
+
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
|
93
93
|
with:
|
|
94
94
|
name: python-package-distributions
|
|
95
95
|
path: dist/
|
|
@@ -113,7 +113,7 @@ jobs:
|
|
|
113
113
|
|
|
114
114
|
steps:
|
|
115
115
|
- name: Download all the dists
|
|
116
|
-
uses: actions/download-artifact@
|
|
116
|
+
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
|
117
117
|
with:
|
|
118
118
|
name: python-package-distributions
|
|
119
119
|
path: dist/
|
|
@@ -28,7 +28,7 @@ jobs:
|
|
|
28
28
|
path: ~/.cache/babeldoc
|
|
29
29
|
key: babeldoc-assets-${{ hashFiles('babeldoc/assets/embedding_assets_metadata.py') }}
|
|
30
30
|
- name: Setup uv with Python ${{ matrix.python-version }}
|
|
31
|
-
uses: astral-sh/setup-uv@
|
|
31
|
+
uses: astral-sh/setup-uv@22695119d769bdb6f7032ad67b9bca0ef8c4a174 # v5.4.0
|
|
32
32
|
with:
|
|
33
33
|
python-version: ${{ matrix.python-version }}
|
|
34
34
|
enable-cache: true
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: BabelDOC
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.16
|
|
4
4
|
Summary: Yet Another Document Translator
|
|
5
5
|
Project-URL: Homepage, https://github.com/funstory-ai/BabelDOC
|
|
6
6
|
Project-URL: Issues, https://github.com/funstory-ai/BabelDOC/issues
|
|
@@ -30,6 +30,7 @@ Requires-Dist: psutil>=7.0.0
|
|
|
30
30
|
Requires-Dist: pydantic>=2.10.6
|
|
31
31
|
Requires-Dist: pymupdf>=1.25.1
|
|
32
32
|
Requires-Dist: python-levenshtein>=0.27.1
|
|
33
|
+
Requires-Dist: rapidocr-onnxruntime>=1.4.4
|
|
33
34
|
Requires-Dist: rich>=13.9.4
|
|
34
35
|
Requires-Dist: scikit-image>=0.25.2
|
|
35
36
|
Requires-Dist: tenacity>=9.0.0
|
|
@@ -87,6 +88,10 @@ PDF scientific paper translation and bilingual comparison library.
|
|
|
87
88
|
<img src="https://s.immersivetranslate.com/assets/r2-uploads/images/babeldoc-preview.png" width="80%"/>
|
|
88
89
|
</div>
|
|
89
90
|
|
|
91
|
+
## We are hiring
|
|
92
|
+
|
|
93
|
+
See details: [EN](https://github.com/funstory-ai/jobs) | [ZH](https://github.com/funstory-ai/jobs/blob/main/README_ZH.md)
|
|
94
|
+
|
|
90
95
|
## Getting Started
|
|
91
96
|
|
|
92
97
|
### Install from PyPI
|
|
@@ -181,6 +186,7 @@ uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model
|
|
|
181
186
|
- `--watermark-output-mode`: Control watermark output mode: 'watermarked' (default) adds watermark to translated PDF, 'no_watermark' doesn't add watermark, 'both' outputs both versions.
|
|
182
187
|
- `--max-pages-per-part`: Maximum number of pages per part for split translation. If not set, no splitting will be performed.
|
|
183
188
|
- `--no-watermark`: [DEPRECATED] Use --watermark-output-mode=no_watermark instead.
|
|
189
|
+
- `--translate-table-text`: Translate table text (experimental, default: False)
|
|
184
190
|
|
|
185
191
|
> [!TIP]
|
|
186
192
|
> - Both `--skip-clean` and `--dual-translate-first` may help improve compatibility with some PDF readers
|
|
@@ -42,6 +42,10 @@ PDF scientific paper translation and bilingual comparison library.
|
|
|
42
42
|
<img src="https://s.immersivetranslate.com/assets/r2-uploads/images/babeldoc-preview.png" width="80%"/>
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
|
+
## We are hiring
|
|
46
|
+
|
|
47
|
+
See details: [EN](https://github.com/funstory-ai/jobs) | [ZH](https://github.com/funstory-ai/jobs/blob/main/README_ZH.md)
|
|
48
|
+
|
|
45
49
|
## Getting Started
|
|
46
50
|
|
|
47
51
|
### Install from PyPI
|
|
@@ -136,6 +140,7 @@ uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model
|
|
|
136
140
|
- `--watermark-output-mode`: Control watermark output mode: 'watermarked' (default) adds watermark to translated PDF, 'no_watermark' doesn't add watermark, 'both' outputs both versions.
|
|
137
141
|
- `--max-pages-per-part`: Maximum number of pages per part for split translation. If not set, no splitting will be performed.
|
|
138
142
|
- `--no-watermark`: [DEPRECATED] Use --watermark-output-mode=no_watermark instead.
|
|
143
|
+
- `--translate-table-text`: Translate table text (experimental, default: False)
|
|
139
144
|
|
|
140
145
|
> [!TIP]
|
|
141
146
|
> - Both `--skip-clean` and `--dual-translate-first` may help improve compatibility with some PDF readers
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.16"
|
|
@@ -14,6 +14,10 @@ from babeldoc.assets.embedding_assets_metadata import (
|
|
|
14
14
|
from babeldoc.assets.embedding_assets_metadata import EMBEDDING_FONT_METADATA
|
|
15
15
|
from babeldoc.assets.embedding_assets_metadata import FONT_METADATA_URL
|
|
16
16
|
from babeldoc.assets.embedding_assets_metadata import FONT_URL_BY_UPSTREAM
|
|
17
|
+
from babeldoc.assets.embedding_assets_metadata import (
|
|
18
|
+
TABLE_DETECTION_RAPIDOCR_MODEL_SHA3_256,
|
|
19
|
+
)
|
|
20
|
+
from babeldoc.assets.embedding_assets_metadata import TABLE_DETECTION_RAPIDOCR_MODEL_URL
|
|
17
21
|
from babeldoc.assets.embedding_assets_metadata import TIKTOKEN_CACHES
|
|
18
22
|
from babeldoc.const import get_cache_file_path
|
|
19
23
|
from tenacity import retry
|
|
@@ -204,10 +208,36 @@ async def get_doclayout_onnx_model_path_async(client: httpx.AsyncClient | None =
|
|
|
204
208
|
return onnx_path
|
|
205
209
|
|
|
206
210
|
|
|
211
|
+
async def get_table_detection_rapidocr_model_path_async(
|
|
212
|
+
client: httpx.AsyncClient | None = None,
|
|
213
|
+
):
|
|
214
|
+
onnx_path = get_cache_file_path("ch_PP-OCRv4_det_infer.onnx", "models")
|
|
215
|
+
if verify_file(onnx_path, TABLE_DETECTION_RAPIDOCR_MODEL_SHA3_256):
|
|
216
|
+
return onnx_path
|
|
217
|
+
|
|
218
|
+
logger.info("table detection rapidocr model not found or corrupted, downloading...")
|
|
219
|
+
fastest_upstream, _ = await get_fastest_upstream_for_model(client)
|
|
220
|
+
if fastest_upstream is None:
|
|
221
|
+
logger.error("Failed to get fastest upstream")
|
|
222
|
+
exit(1)
|
|
223
|
+
|
|
224
|
+
url = TABLE_DETECTION_RAPIDOCR_MODEL_URL[fastest_upstream]
|
|
225
|
+
|
|
226
|
+
await download_file(client, url, onnx_path, TABLE_DETECTION_RAPIDOCR_MODEL_SHA3_256)
|
|
227
|
+
logger.info(
|
|
228
|
+
f"Download table detection rapidocr model from {fastest_upstream} success"
|
|
229
|
+
)
|
|
230
|
+
return onnx_path
|
|
231
|
+
|
|
232
|
+
|
|
207
233
|
def get_doclayout_onnx_model_path():
|
|
208
234
|
return run_coro(get_doclayout_onnx_model_path_async())
|
|
209
235
|
|
|
210
236
|
|
|
237
|
+
def get_table_detection_rapidocr_model_path():
|
|
238
|
+
return run_coro(get_table_detection_rapidocr_model_path_async())
|
|
239
|
+
|
|
240
|
+
|
|
211
241
|
def get_font_url_by_name_and_upstream(font_file_name: str, upstream: str):
|
|
212
242
|
if upstream not in FONT_URL_BY_UPSTREAM:
|
|
213
243
|
logger.critical(f"Invalid upstream: {upstream}")
|
|
@@ -329,7 +359,11 @@ def generate_all_assets_file_list():
|
|
|
329
359
|
{
|
|
330
360
|
"name": "doclayout_yolo_docstructbench_imgsz1024.onnx",
|
|
331
361
|
"sha3_256": DOCLAYOUT_YOLO_DOCSTRUCTBENCH_IMGSZ1024ONNX_SHA3_256,
|
|
332
|
-
}
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"name": "ch_PP-OCRv4_det_infer.onnx",
|
|
365
|
+
"sha3_256": TABLE_DETECTION_RAPIDOCR_MODEL_SHA3_256,
|
|
366
|
+
},
|
|
333
367
|
)
|
|
334
368
|
return result
|
|
335
369
|
|
|
@@ -2,6 +2,10 @@ DOCLAYOUT_YOLO_DOCSTRUCTBENCH_IMGSZ1024ONNX_SHA3_256 = (
|
|
|
2
2
|
"60be061226930524958b5465c8c04af3d7c03bcb0beb66454f5da9f792e3cf2a"
|
|
3
3
|
)
|
|
4
4
|
|
|
5
|
+
TABLE_DETECTION_RAPIDOCR_MODEL_SHA3_256 = (
|
|
6
|
+
"062f4619afe91b33147c033acadecbb53f2a7b99ac703d157b96d5b10948da5e"
|
|
7
|
+
)
|
|
8
|
+
|
|
5
9
|
TIKTOKEN_CACHES = {
|
|
6
10
|
"fb374d419588a4632f3f557e76b4b70aebbca790": "cb04bcda5782cfbbe77f2f991d92c0ea785d9496ef1137c91dfc3c8c324528d6"
|
|
7
11
|
}
|
|
@@ -26,6 +30,12 @@ DOC_LAYOUT_ONNX_MODEL_URL = {
|
|
|
26
30
|
"modelscope": "https://www.modelscope.cn/models/AI-ModelScope/DocLayout-YOLO-DocStructBench-onnx/resolve/master/doclayout_yolo_docstructbench_imgsz1024.onnx",
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
TABLE_DETECTION_RAPIDOCR_MODEL_URL = {
|
|
34
|
+
"huggingface": "https://huggingface.co/spaces/RapidAI/RapidOCR/resolve/main/models/text_det/ch_PP-OCRv4_det_infer.onnx",
|
|
35
|
+
"hf-mirror": "https://hf-mirror.com/spaces/RapidAI/RapidOCR/resolve/main/models/text_det/ch_PP-OCRv4_det_infer.onnx",
|
|
36
|
+
"modelscope": "https://www.modelscope.cn/models/RapidAI/RapidOCR/resolve/master/onnx/PP-OCRv4/det/ch_PP-OCRv4_det_infer.onnx",
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
# from https://github.com/funstory-ai/BabelDOC-Assets/blob/main/font_metadata.json
|
|
30
40
|
EMBEDDING_FONT_METADATA = {
|
|
31
41
|
"GoNotoKurrent-Bold.ttf": {
|
|
@@ -287,6 +287,7 @@ class ParagraphFinder:
|
|
|
287
287
|
"abandon",
|
|
288
288
|
"figure_caption",
|
|
289
289
|
"table_caption",
|
|
290
|
+
"table_text",
|
|
290
291
|
]
|
|
291
292
|
|
|
292
293
|
def get_layout(
|
|
@@ -313,6 +314,7 @@ class ParagraphFinder:
|
|
|
313
314
|
"table_footnote",
|
|
314
315
|
"table_caption",
|
|
315
316
|
"figure_caption",
|
|
317
|
+
"table_text",
|
|
316
318
|
"table",
|
|
317
319
|
"figure",
|
|
318
320
|
"abandon",
|
|
@@ -428,6 +428,8 @@ class StylesAndFormulas:
|
|
|
428
428
|
formula.x_offset = 0 # 如果左边没有文字,x_offset 应该为 0
|
|
429
429
|
if abs(formula.x_offset) < 0.1:
|
|
430
430
|
formula.x_offset = 0
|
|
431
|
+
if formula.x_offset > 10:
|
|
432
|
+
formula.x_offset = 0
|
|
431
433
|
# if formula.x_offset > 0:
|
|
432
434
|
# formula.x_offset = 0
|
|
433
435
|
|
|
@@ -443,7 +445,7 @@ class StylesAndFormulas:
|
|
|
443
445
|
if abs(formula.y_offset) < 0.1:
|
|
444
446
|
formula.y_offset = 0
|
|
445
447
|
|
|
446
|
-
if max(abs(formula.y_offset), abs(formula.x_offset)) >
|
|
448
|
+
if max(abs(formula.y_offset), abs(formula.x_offset)) > 10:
|
|
447
449
|
pass
|
|
448
450
|
# logging.debug(
|
|
449
451
|
# f"公式 {formula.box} 的偏移量过大:{formula.x_offset}, {formula.y_offset}"
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import cv2
|
|
5
|
+
import numpy as np
|
|
6
|
+
from pymupdf import Document
|
|
7
|
+
|
|
8
|
+
from babeldoc.document_il import il_version_1
|
|
9
|
+
from babeldoc.document_il.utils.style_helper import GREEN
|
|
10
|
+
from babeldoc.translation_config import TranslationConfig
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TableParser:
|
|
16
|
+
stage_name = "Parse Table"
|
|
17
|
+
|
|
18
|
+
def __init__(self, translation_config: TranslationConfig):
|
|
19
|
+
self.translation_config = translation_config
|
|
20
|
+
self.model = translation_config.table_model
|
|
21
|
+
|
|
22
|
+
def _save_debug_image(self, image: np.ndarray, layouts, page_number: int):
|
|
23
|
+
"""Save debug image with drawn boxes if debug mode is enabled."""
|
|
24
|
+
if not self.translation_config.debug:
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
if not isinstance(layouts, list):
|
|
28
|
+
layouts = [layouts]
|
|
29
|
+
debug_dir = Path(
|
|
30
|
+
self.translation_config.get_working_file_path("table-ocr-box-image")
|
|
31
|
+
)
|
|
32
|
+
debug_dir.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
# Draw boxes on the image
|
|
35
|
+
debug_image = image.copy()
|
|
36
|
+
for layout in layouts:
|
|
37
|
+
for box in layout.boxes:
|
|
38
|
+
x0, y0, x1, y1 = box.xyxy
|
|
39
|
+
cv2.rectangle(
|
|
40
|
+
debug_image,
|
|
41
|
+
(int(x0), int(y0)),
|
|
42
|
+
(int(x1), int(y1)),
|
|
43
|
+
(0, 255, 0),
|
|
44
|
+
2,
|
|
45
|
+
)
|
|
46
|
+
# Add text label
|
|
47
|
+
cv2.putText(
|
|
48
|
+
debug_image,
|
|
49
|
+
layout.names[box.cls],
|
|
50
|
+
(int(x0), int(y0) - 5),
|
|
51
|
+
cv2.FONT_HERSHEY_SIMPLEX,
|
|
52
|
+
0.5,
|
|
53
|
+
(0, 255, 0),
|
|
54
|
+
1,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Save the image
|
|
58
|
+
output_path = debug_dir / f"{page_number}.jpg"
|
|
59
|
+
cv2.imwrite(str(output_path), debug_image)
|
|
60
|
+
|
|
61
|
+
def _save_debug_box_to_page(self, page: il_version_1.Page):
|
|
62
|
+
"""Save debug boxes and text labels to the PDF page."""
|
|
63
|
+
if not self.translation_config.debug:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
color = GREEN
|
|
67
|
+
|
|
68
|
+
for layout in page.page_layout:
|
|
69
|
+
# Create a rectangle box
|
|
70
|
+
rect = il_version_1.PdfRectangle(
|
|
71
|
+
box=il_version_1.Box(
|
|
72
|
+
x=layout.box.x,
|
|
73
|
+
y=layout.box.y,
|
|
74
|
+
x2=layout.box.x2,
|
|
75
|
+
y2=layout.box.y2,
|
|
76
|
+
),
|
|
77
|
+
graphic_state=color,
|
|
78
|
+
debug_info=True,
|
|
79
|
+
)
|
|
80
|
+
page.pdf_rectangle.append(rect)
|
|
81
|
+
|
|
82
|
+
# Create text label at top-left corner
|
|
83
|
+
# Note: PDF coordinates are from bottom-left,
|
|
84
|
+
# so we use y2 for top position
|
|
85
|
+
style = il_version_1.PdfStyle(
|
|
86
|
+
font_id="china-ss",
|
|
87
|
+
font_size=4,
|
|
88
|
+
graphic_state=color,
|
|
89
|
+
)
|
|
90
|
+
page.pdf_paragraph.append(
|
|
91
|
+
il_version_1.PdfParagraph(
|
|
92
|
+
first_line_indent=False,
|
|
93
|
+
box=il_version_1.Box(
|
|
94
|
+
x=layout.box.x,
|
|
95
|
+
y=layout.box.y2,
|
|
96
|
+
x2=layout.box.x2,
|
|
97
|
+
y2=layout.box.y2 + 5,
|
|
98
|
+
),
|
|
99
|
+
vertical=False,
|
|
100
|
+
pdf_style=style,
|
|
101
|
+
unicode=layout.class_name,
|
|
102
|
+
pdf_paragraph_composition=[
|
|
103
|
+
il_version_1.PdfParagraphComposition(
|
|
104
|
+
pdf_same_style_unicode_characters=il_version_1.PdfSameStyleUnicodeCharacters(
|
|
105
|
+
unicode=layout.class_name,
|
|
106
|
+
pdf_style=style,
|
|
107
|
+
debug_info=True,
|
|
108
|
+
),
|
|
109
|
+
),
|
|
110
|
+
],
|
|
111
|
+
xobj_id=-1,
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def process(self, docs: il_version_1.Document, mupdf_doc: Document):
|
|
116
|
+
"""Generate layouts for all pages that need to be translated."""
|
|
117
|
+
# Get pages that need to be translated
|
|
118
|
+
have_table_pages = {}
|
|
119
|
+
for page in docs.page:
|
|
120
|
+
for layout in page.page_layout:
|
|
121
|
+
if layout.class_name == "table":
|
|
122
|
+
have_table_pages[page.page_number] = page
|
|
123
|
+
with self.translation_config.progress_monitor.stage_start(
|
|
124
|
+
self.stage_name,
|
|
125
|
+
len(have_table_pages),
|
|
126
|
+
) as progress:
|
|
127
|
+
# Process predictions for each page
|
|
128
|
+
for page, layouts in self.model.handle_document(
|
|
129
|
+
have_table_pages.values(),
|
|
130
|
+
mupdf_doc,
|
|
131
|
+
self.translation_config,
|
|
132
|
+
self._save_debug_image,
|
|
133
|
+
):
|
|
134
|
+
page_layouts = []
|
|
135
|
+
for layout in layouts.boxes:
|
|
136
|
+
# Convert coordinate system from picture to il
|
|
137
|
+
# system to the il coordinate system
|
|
138
|
+
x0, y0, x1, y1 = layout.xyxy
|
|
139
|
+
pix = mupdf_doc[page.page_number].get_pixmap()
|
|
140
|
+
h, w = pix.height, pix.width
|
|
141
|
+
x0, y0, x1, y1 = (
|
|
142
|
+
np.clip(int(x0 - 1), 0, w - 1),
|
|
143
|
+
np.clip(int(h - y1 - 1), 0, h - 1),
|
|
144
|
+
np.clip(int(x1 + 1), 0, w - 1),
|
|
145
|
+
np.clip(int(h - y0 + 1), 0, h - 1),
|
|
146
|
+
)
|
|
147
|
+
page_layout = il_version_1.PageLayout(
|
|
148
|
+
id=len(page_layouts) + 1,
|
|
149
|
+
box=il_version_1.Box(
|
|
150
|
+
x0.item(),
|
|
151
|
+
y0.item(),
|
|
152
|
+
x1.item(),
|
|
153
|
+
y1.item(),
|
|
154
|
+
),
|
|
155
|
+
conf=layout.conf.item(),
|
|
156
|
+
class_name=layouts.names[layout.cls],
|
|
157
|
+
)
|
|
158
|
+
page_layouts.append(page_layout)
|
|
159
|
+
|
|
160
|
+
page.page_layout.extend(page_layouts)
|
|
161
|
+
self._save_debug_box_to_page(page)
|
|
162
|
+
progress.advance(1)
|
|
163
|
+
|
|
164
|
+
return docs
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import copy
|
|
1
2
|
import logging
|
|
2
3
|
import re
|
|
3
4
|
import statistics
|
|
@@ -603,17 +604,26 @@ class Typesetting:
|
|
|
603
604
|
self.font_mapper.base_font.char_lengths("你", font_size * scale)[0] * 0.5
|
|
604
605
|
)
|
|
605
606
|
|
|
606
|
-
#
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
/ len(typesetting_units)
|
|
610
|
-
if typesetting_units
|
|
611
|
-
else 0
|
|
607
|
+
# 计算行高(使用众数)
|
|
608
|
+
unit_heights = (
|
|
609
|
+
[unit.height for unit in typesetting_units] if typesetting_units else []
|
|
612
610
|
)
|
|
611
|
+
if not unit_heights:
|
|
612
|
+
avg_height = 0
|
|
613
|
+
elif len(unit_heights) == 1:
|
|
614
|
+
avg_height = unit_heights[0] * scale
|
|
615
|
+
else:
|
|
616
|
+
try:
|
|
617
|
+
avg_height = statistics.mode(unit_heights) * scale
|
|
618
|
+
except statistics.StatisticsError:
|
|
619
|
+
# 如果没有众数(所有值都出现相同次数),则使用平均值
|
|
620
|
+
avg_height = sum(unit_heights) / len(unit_heights) * scale
|
|
613
621
|
|
|
614
622
|
# 初始化位置为右上角,并减去一个平均行高
|
|
615
623
|
current_x = box.x
|
|
616
624
|
current_y = box.y2 - avg_height
|
|
625
|
+
box = copy.deepcopy(box)
|
|
626
|
+
box.y -= avg_height * (line_spacing - 1)
|
|
617
627
|
line_height = 0
|
|
618
628
|
current_line_heights = [] # 存储当前行所有元素的高度
|
|
619
629
|
|