BabelDOC 0.3.16__tar.gz → 0.3.18__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.3.16 → babeldoc-0.3.18}/.github/workflows/docs.yml +1 -1
- babeldoc-0.3.18/.github/workflows/lint.yml +17 -0
- babeldoc-0.3.18/.github/workflows/pr-lint.yml +46 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.github/workflows/publish-to-pypi.yml +1 -1
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.github/workflows/test.yml +1 -1
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.gitignore +1 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/PKG-INFO +3 -1
- {babeldoc-0.3.16 → babeldoc-0.3.18}/README.md +2 -0
- babeldoc-0.3.18/babeldoc/__init__.py +1 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/const.py +1 -1
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/converter.py +9 -1
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/backend/pdf_creater.py +37 -22
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/frontend/il_creater.py +19 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/il_version_1.py +18 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/il_version_1.rnc +4 -1
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/midend/paragraph_finder.py +49 -1
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/midend/styles_and_formulas.py +5 -2
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/midend/typesetting.py +3 -3
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/utils/style_helper.py +4 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/docvision/rpc_doclayout.py +2 -2
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/high_level.py +2 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/main.py +8 -1
- babeldoc-0.3.18/babeldoc/tools/italic_recognize_tool.py +79 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/translation_config.py +14 -6
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/CONTRIBUTING.md +6 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/pyproject.toml +4 -3
- {babeldoc-0.3.16 → babeldoc-0.3.18}/uv.lock +1252 -1161
- babeldoc-0.3.16/babeldoc/__init__.py +0 -1
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.cursorignore +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.github/PULL_REQUEST_TEMPLATE/pr_form.yml +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.github/dependabot.yml +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.github/labels.yml +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.github/release-drafter.yml +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.github/workflows/codeql.yml +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.github/workflows/labeler.yml +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/.pre-commit-config.yaml +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/LICENSE +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/assets/assets.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/assets/embedding_assets_metadata.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/asynchronize/__init__.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/__init__.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/backend/__init__.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/frontend/__init__.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/il_version_1.rng +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/il_version_1.xsd +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/midend/__init__.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/midend/add_debug_information.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/midend/detect_scanned_file.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/midend/il_translator.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/midend/il_translator_llm_only.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/midend/layout_parser.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/midend/remove_descent.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/midend/table_parser.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/translator/__init__.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/translator/cache.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/translator/translator.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/utils/atomic_integer.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/utils/fontmap.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/utils/layout_helper.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/utils/priority_thread_pool_executor.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/document_il/xml_converter.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/docvision/README.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/docvision/__init__.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/docvision/doclayout.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/docvision/table_detection/rapidocr.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/pdfinterp.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/progress_monitor.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/result_merger.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/split_manager.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/tools/generate_font_metadata.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/babeldoc/tools/italic_assistance.py +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/CODE_OF_CONDUCT.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/CONTRIBUTOR_REWARD.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/ImplementationDetails/README.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/README.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/deploy.sh +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/images/babeldoc-banner.png +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/images/babeldoc-big-logo.png +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/images/babeldoc-contributor_reward_example.png +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/images/babeldoc-preview.gif +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/images/babeldoc-small-logo.png +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/index.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/intro-to-pdf-object.md +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/docs/requirements.txt +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/mkdocs.yml +0 -0
- {babeldoc-0.3.16 → babeldoc-0.3.18}/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@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
|
21
21
|
with:
|
|
22
22
|
python-version: "3.12"
|
|
23
23
|
enable-cache: true
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Lint Code
|
|
2
|
+
permissions:
|
|
3
|
+
contents: read
|
|
4
|
+
pull-requests: write
|
|
5
|
+
on: [push]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
lint:
|
|
9
|
+
strategy:
|
|
10
|
+
fail-fast: false
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- name: Ruff
|
|
15
|
+
uses: astral-sh/ruff-action@v3
|
|
16
|
+
- name: AutoCorrect
|
|
17
|
+
uses: huacnlee/autocorrect-action@main
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Lint Code and Review Dog Report
|
|
2
|
+
|
|
3
|
+
on: [pull_request]
|
|
4
|
+
permissions:
|
|
5
|
+
contents: read
|
|
6
|
+
pull-requests: write
|
|
7
|
+
jobs:
|
|
8
|
+
ruff:
|
|
9
|
+
name: runner / ruff
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Install Python
|
|
15
|
+
uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: '3.11'
|
|
18
|
+
|
|
19
|
+
- name: Install ruff
|
|
20
|
+
run: pip install ruff
|
|
21
|
+
|
|
22
|
+
- name: Install reviewdog
|
|
23
|
+
uses: reviewdog/action-setup@e04ffabe3898a0af8d0fb1af00c188831c4b5893 # v1.3.2
|
|
24
|
+
with:
|
|
25
|
+
reviewdog_version: latest
|
|
26
|
+
|
|
27
|
+
- name: Run ruff with reviewdog
|
|
28
|
+
env:
|
|
29
|
+
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
30
|
+
run: |
|
|
31
|
+
ruff check . --output-format=rdjson | reviewdog -f=rdjson -reporter=github-pr-review -fail-on-error
|
|
32
|
+
|
|
33
|
+
autocorrect:
|
|
34
|
+
name: runner / autocorrect
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- name: AutoCorrect
|
|
39
|
+
uses: huacnlee/autocorrect-action@bf91ab3904c2908dd8e71312a8a83ed1eb632997 # v2.13.3
|
|
40
|
+
- name: Report ReviewDog
|
|
41
|
+
if: failure()
|
|
42
|
+
uses: huacnlee/autocorrect-action@bf91ab3904c2908dd8e71312a8a83ed1eb632997 # v2.13.3
|
|
43
|
+
env:
|
|
44
|
+
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
45
|
+
with:
|
|
46
|
+
reviewdog: 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@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
|
39
39
|
with:
|
|
40
40
|
python-version: "3.12"
|
|
41
41
|
enable-cache: true
|
|
@@ -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@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
|
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.3.
|
|
3
|
+
Version: 0.3.18
|
|
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
|
|
@@ -191,6 +191,7 @@ uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model
|
|
|
191
191
|
- `--no-watermark`: [DEPRECATED] Use --watermark-output-mode=no_watermark instead.
|
|
192
192
|
- `--translate-table-text`: Translate table text (experimental, default: False)
|
|
193
193
|
- `--skip-scanned-detection`: Skip scanned document detection (default: False). When using split translation, only the first part performs detection if not skipped.
|
|
194
|
+
- `--ocr-workaround`: Use OCR workaround (default: False). When enabled, the tool will use OCR to detect text and fill background for scanned PDF.
|
|
194
195
|
|
|
195
196
|
> [!TIP]
|
|
196
197
|
> - Both `--skip-clean` and `--dual-translate-first` may help improve compatibility with some PDF readers
|
|
@@ -199,6 +200,7 @@ uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model
|
|
|
199
200
|
> - If you encounter any compatibility issues, try using `--enhance-compatibility` first
|
|
200
201
|
> - Use `--max-pages-per-part` for large documents to split them into smaller parts for translation and automatically merge them back.
|
|
201
202
|
> - Use `--skip-scanned-detection` to speed up processing when you know your document is not a scanned PDF.
|
|
203
|
+
> - Use `--ocr-workaround` to fill background for scanned PDF. (Current assumption: background is pure white, text is pure black, this option will also auto enable `--skip-scanned-detection`)
|
|
202
204
|
|
|
203
205
|
### Translation Service Options
|
|
204
206
|
|
|
@@ -145,6 +145,7 @@ uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model
|
|
|
145
145
|
- `--no-watermark`: [DEPRECATED] Use --watermark-output-mode=no_watermark instead.
|
|
146
146
|
- `--translate-table-text`: Translate table text (experimental, default: False)
|
|
147
147
|
- `--skip-scanned-detection`: Skip scanned document detection (default: False). When using split translation, only the first part performs detection if not skipped.
|
|
148
|
+
- `--ocr-workaround`: Use OCR workaround (default: False). When enabled, the tool will use OCR to detect text and fill background for scanned PDF.
|
|
148
149
|
|
|
149
150
|
> [!TIP]
|
|
150
151
|
> - Both `--skip-clean` and `--dual-translate-first` may help improve compatibility with some PDF readers
|
|
@@ -153,6 +154,7 @@ uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model
|
|
|
153
154
|
> - If you encounter any compatibility issues, try using `--enhance-compatibility` first
|
|
154
155
|
> - Use `--max-pages-per-part` for large documents to split them into smaller parts for translation and automatically merge them back.
|
|
155
156
|
> - Use `--skip-scanned-detection` to speed up processing when you know your document is not a scanned PDF.
|
|
157
|
+
> - Use `--ocr-workaround` to fill background for scanned PDF. (Current assumption: background is pure white, text is pure black, this option will also auto enable `--skip-scanned-detection`)
|
|
156
158
|
|
|
157
159
|
### Translation Service Options
|
|
158
160
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.18"
|
|
@@ -92,7 +92,15 @@ class PDFConverterEx(PDFConverter):
|
|
|
92
92
|
textwidth = font.char_width(cid)
|
|
93
93
|
textdisp = font.char_disp(cid)
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
if not hasattr(font, "xobj_id"):
|
|
96
|
+
log.debug(
|
|
97
|
+
f"Font {font.fontname} does not have xobj_id attribute.",
|
|
98
|
+
)
|
|
99
|
+
font_id = "UNKNOW"
|
|
100
|
+
else:
|
|
101
|
+
font_id = self.il_creater.current_page_font_name_id_map.get(
|
|
102
|
+
font.xobj_id, None
|
|
103
|
+
)
|
|
96
104
|
|
|
97
105
|
item = AWLTChar(
|
|
98
106
|
matrix,
|
|
@@ -31,11 +31,11 @@ def _subset_fonts_process(pdf_path, output_path):
|
|
|
31
31
|
pdf = pymupdf.open(pdf_path)
|
|
32
32
|
pdf.subset_fonts(fallback=False)
|
|
33
33
|
pdf.save(output_path)
|
|
34
|
-
# 返回0表示成功
|
|
34
|
+
# 返回 0 表示成功
|
|
35
35
|
os._exit(0)
|
|
36
36
|
except Exception as e:
|
|
37
37
|
logger.error(f"Error in font subsetting subprocess: {e}")
|
|
38
|
-
# 返回1表示失败
|
|
38
|
+
# 返回 1 表示失败
|
|
39
39
|
os._exit(1)
|
|
40
40
|
|
|
41
41
|
|
|
@@ -69,11 +69,11 @@ def _save_pdf_clean_process(
|
|
|
69
69
|
deflate_fonts=deflate_fonts,
|
|
70
70
|
linear=linear,
|
|
71
71
|
)
|
|
72
|
-
# 返回0表示成功
|
|
72
|
+
# 返回 0 表示成功
|
|
73
73
|
os._exit(0)
|
|
74
74
|
except Exception as e:
|
|
75
75
|
logger.error(f"Error in save PDF with clean=True subprocess: {e}")
|
|
76
|
-
# 返回1表示失败
|
|
76
|
+
# 返回 1 表示失败
|
|
77
77
|
os._exit(1)
|
|
78
78
|
|
|
79
79
|
|
|
@@ -185,21 +185,25 @@ class PDFCreater:
|
|
|
185
185
|
except Exception:
|
|
186
186
|
return set()
|
|
187
187
|
|
|
188
|
-
def
|
|
188
|
+
def _render_rectangle(
|
|
189
189
|
self,
|
|
190
190
|
draw_op: BitStream,
|
|
191
191
|
rectangle: il_version_1.PdfRectangle,
|
|
192
|
+
line_width: float = 1,
|
|
192
193
|
):
|
|
193
|
-
"""Draw a
|
|
194
|
+
"""Draw a rectangle in PDF for visualization purposes.
|
|
194
195
|
|
|
195
196
|
Args:
|
|
196
197
|
draw_op: BitStream to append PDF drawing operations
|
|
197
198
|
rectangle: Rectangle object containing position information
|
|
199
|
+
line_width: Line width
|
|
198
200
|
"""
|
|
199
201
|
x1 = rectangle.box.x
|
|
200
202
|
y1 = rectangle.box.y
|
|
201
203
|
x2 = rectangle.box.x2
|
|
202
204
|
y2 = rectangle.box.y2
|
|
205
|
+
width = x2 - x1
|
|
206
|
+
height = y2 - y1
|
|
203
207
|
# Save graphics state
|
|
204
208
|
draw_op.append(b"q ")
|
|
205
209
|
|
|
@@ -207,17 +211,13 @@ class PDFCreater:
|
|
|
207
211
|
draw_op.append(
|
|
208
212
|
rectangle.graphic_state.passthrough_per_char_instruction.encode(),
|
|
209
213
|
) # Green stroke
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# Top line
|
|
218
|
-
draw_op.append(f"{x2} {y2} m {x1} {y2} l S ".encode())
|
|
219
|
-
# Left line
|
|
220
|
-
draw_op.append(f"{x1} {y2} m {x1} {y1} l S ".encode())
|
|
214
|
+
if line_width > 0:
|
|
215
|
+
draw_op.append(f" {line_width} w ".encode()) # Line width
|
|
216
|
+
draw_op.append(f"{x1} {y1} {width} {height} re ".encode())
|
|
217
|
+
if rectangle.fill_background:
|
|
218
|
+
draw_op.append(b" f ")
|
|
219
|
+
else:
|
|
220
|
+
draw_op.append(b" S ")
|
|
221
221
|
|
|
222
222
|
# Restore graphics state
|
|
223
223
|
draw_op.append(b"Q\n")
|
|
@@ -410,7 +410,7 @@ class PDFCreater:
|
|
|
410
410
|
for rect in page.pdf_rectangle:
|
|
411
411
|
if not rect.debug_info:
|
|
412
412
|
continue
|
|
413
|
-
self.
|
|
413
|
+
self._render_rectangle(page_op, rect)
|
|
414
414
|
draw_op = page_op
|
|
415
415
|
# Since this is a draw instruction container,
|
|
416
416
|
# no additional information is needed
|
|
@@ -711,7 +711,17 @@ class PDFCreater:
|
|
|
711
711
|
# 然后添加段落中的字符
|
|
712
712
|
for paragraph in page.pdf_paragraph:
|
|
713
713
|
chars.extend(self.render_paragraph_to_char(paragraph))
|
|
714
|
-
|
|
714
|
+
for rect in page.pdf_rectangle:
|
|
715
|
+
if (
|
|
716
|
+
translation_config.ocr_workaround
|
|
717
|
+
and not rect.debug_info
|
|
718
|
+
and rect.fill_background
|
|
719
|
+
):
|
|
720
|
+
if rect.xobj_id in xobj_available_fonts:
|
|
721
|
+
draw_op = xobj_draw_ops[rect.xobj_id]
|
|
722
|
+
else:
|
|
723
|
+
draw_op = page_op
|
|
724
|
+
self._render_rectangle(draw_op, rect, line_width=0.1)
|
|
715
725
|
# 渲染所有字符
|
|
716
726
|
for char in chars:
|
|
717
727
|
if char.char_unicode == "\n":
|
|
@@ -773,7 +783,9 @@ class PDFCreater:
|
|
|
773
783
|
)
|
|
774
784
|
# pdf.update_stream(xobj.xref_id, b'')
|
|
775
785
|
for rect in page.pdf_rectangle:
|
|
776
|
-
|
|
786
|
+
if translation_config.debug and rect.debug_info:
|
|
787
|
+
self._render_rectangle(page_op, rect)
|
|
788
|
+
|
|
777
789
|
draw_op = page_op
|
|
778
790
|
op_container = pdf.get_new_xref()
|
|
779
791
|
# Since this is a draw instruction container,
|
|
@@ -783,6 +795,9 @@ class PDFCreater:
|
|
|
783
795
|
pdf[page.page_number].set_contents(op_container)
|
|
784
796
|
pbar.advance()
|
|
785
797
|
translation_config.raise_if_cancelled()
|
|
798
|
+
gc_level = 1
|
|
799
|
+
if self.translation_config.ocr_workaround:
|
|
800
|
+
gc_level = 4
|
|
786
801
|
with self.translation_config.progress_monitor.stage_start(
|
|
787
802
|
SUBSET_FONT_STAGE_NAME,
|
|
788
803
|
1,
|
|
@@ -811,7 +826,7 @@ class PDFCreater:
|
|
|
811
826
|
pdf,
|
|
812
827
|
mono_out_path,
|
|
813
828
|
translation_config,
|
|
814
|
-
garbage=
|
|
829
|
+
garbage=gc_level,
|
|
815
830
|
deflate=True,
|
|
816
831
|
clean=not translation_config.skip_clean,
|
|
817
832
|
deflate_fonts=True,
|
|
@@ -864,7 +879,7 @@ class PDFCreater:
|
|
|
864
879
|
dual,
|
|
865
880
|
dual_out_path,
|
|
866
881
|
translation_config,
|
|
867
|
-
garbage=
|
|
882
|
+
garbage=gc_level,
|
|
868
883
|
deflate=True,
|
|
869
884
|
clean=not translation_config.skip_clean,
|
|
870
885
|
deflate_fonts=True,
|
|
@@ -19,6 +19,7 @@ from pdfminer.pdftypes import resolve1 as pdftypes_resolve1
|
|
|
19
19
|
from pdfminer.psparser import PSLiteral
|
|
20
20
|
|
|
21
21
|
from babeldoc.document_il import il_version_1
|
|
22
|
+
from babeldoc.document_il.utils.style_helper import BLACK
|
|
22
23
|
from babeldoc.document_il.utils.style_helper import YELLOW
|
|
23
24
|
from babeldoc.translation_config import TranslationConfig
|
|
24
25
|
|
|
@@ -478,6 +479,7 @@ class ILCreater:
|
|
|
478
479
|
self.current_page_font_name_id_map = {}
|
|
479
480
|
self.current_page_font_char_bounding_box_map = {}
|
|
480
481
|
self.mupdf_font_map: dict[int, pymupdf.Font] = {}
|
|
482
|
+
self.graphic_state_pool = {}
|
|
481
483
|
|
|
482
484
|
def on_finish(self):
|
|
483
485
|
self.progress.__exit__(None, None, None)
|
|
@@ -796,6 +798,21 @@ class ILCreater:
|
|
|
796
798
|
f"{arg} {op}" for op, arg in gs.passthrough_instruction
|
|
797
799
|
)
|
|
798
800
|
|
|
801
|
+
# 可能会影响部分 graphic state 准确度。不过 BabelDOC 仅使用 passthrough_per_char_instruction
|
|
802
|
+
# 所以应该是没啥影响
|
|
803
|
+
# 但是池化 graphic state 后可以减少内存占用
|
|
804
|
+
if (
|
|
805
|
+
graphic_state.passthrough_per_char_instruction
|
|
806
|
+
not in self.graphic_state_pool
|
|
807
|
+
):
|
|
808
|
+
self.graphic_state_pool[graphic_state.passthrough_per_char_instruction] = (
|
|
809
|
+
graphic_state
|
|
810
|
+
)
|
|
811
|
+
else:
|
|
812
|
+
graphic_state = self.graphic_state_pool[
|
|
813
|
+
graphic_state.passthrough_per_char_instruction
|
|
814
|
+
]
|
|
815
|
+
|
|
799
816
|
return graphic_state
|
|
800
817
|
|
|
801
818
|
def on_lt_char(self, char: LTChar):
|
|
@@ -887,6 +904,8 @@ class ILCreater:
|
|
|
887
904
|
xobj_id=char.xobj_id,
|
|
888
905
|
visual_bbox=visual_bbox,
|
|
889
906
|
)
|
|
907
|
+
if self.translation_config.ocr_workaround:
|
|
908
|
+
pdf_char.pdf_style.graphic_state = BLACK
|
|
890
909
|
if pdf_style.font_size == 0.0:
|
|
891
910
|
logger.warning(
|
|
892
911
|
"Font size is 0.0 for character %s. Skip it.",
|
|
@@ -364,6 +364,18 @@ class PdfRectangle:
|
|
|
364
364
|
"type": "Attribute",
|
|
365
365
|
},
|
|
366
366
|
)
|
|
367
|
+
fill_background: bool | None = field(
|
|
368
|
+
default=None,
|
|
369
|
+
metadata={
|
|
370
|
+
"type": "Attribute",
|
|
371
|
+
},
|
|
372
|
+
)
|
|
373
|
+
xobj_id: int | None = field(
|
|
374
|
+
default=None,
|
|
375
|
+
metadata={
|
|
376
|
+
"type": "Attribute",
|
|
377
|
+
},
|
|
378
|
+
)
|
|
367
379
|
|
|
368
380
|
|
|
369
381
|
@dataclass
|
|
@@ -756,6 +768,12 @@ class PdfParagraph:
|
|
|
756
768
|
"type": "Attribute",
|
|
757
769
|
},
|
|
758
770
|
)
|
|
771
|
+
layout_id: int | None = field(
|
|
772
|
+
default=None,
|
|
773
|
+
metadata={
|
|
774
|
+
"type": "Attribute",
|
|
775
|
+
},
|
|
776
|
+
)
|
|
759
777
|
|
|
760
778
|
|
|
761
779
|
@dataclass
|
|
@@ -115,6 +115,7 @@ PDFParagraph =
|
|
|
115
115
|
attribute FirstLineIndent { xsd:boolean }?,
|
|
116
116
|
attribute debug_id { xsd:string }?,
|
|
117
117
|
attribute layout_label { xsd:string }?,
|
|
118
|
+
attribute layout_id { xsd:int }?,
|
|
118
119
|
Box,
|
|
119
120
|
PDFStyle,
|
|
120
121
|
PDFParagraphComposition*
|
|
@@ -148,5 +149,7 @@ PDFRectangle =
|
|
|
148
149
|
element pdfRectangle {
|
|
149
150
|
Box,
|
|
150
151
|
GraphicState,
|
|
151
|
-
attribute debug_info { xsd:boolean }
|
|
152
|
+
attribute debug_info { xsd:boolean }?,
|
|
153
|
+
attribute fill_background { xsd:boolean }?,
|
|
154
|
+
attribute xobjId { xsd:int }?
|
|
152
155
|
}
|
|
@@ -8,9 +8,11 @@ from babeldoc.document_il import PdfCharacter
|
|
|
8
8
|
from babeldoc.document_il import PdfLine
|
|
9
9
|
from babeldoc.document_il import PdfParagraph
|
|
10
10
|
from babeldoc.document_il import PdfParagraphComposition
|
|
11
|
+
from babeldoc.document_il import PdfRectangle
|
|
11
12
|
from babeldoc.document_il.utils.layout_helper import Layout
|
|
12
13
|
from babeldoc.document_il.utils.layout_helper import add_space_dummy_chars
|
|
13
14
|
from babeldoc.document_il.utils.layout_helper import get_char_unicode_string
|
|
15
|
+
from babeldoc.document_il.utils.style_helper import WHITE
|
|
14
16
|
from babeldoc.translation_config import TranslationConfig
|
|
15
17
|
|
|
16
18
|
logger = logging.getLogger(__name__)
|
|
@@ -35,6 +37,41 @@ class ParagraphFinder:
|
|
|
35
37
|
def __init__(self, translation_config: TranslationConfig):
|
|
36
38
|
self.translation_config = translation_config
|
|
37
39
|
|
|
40
|
+
def add_text_fill_background(self, page: Page):
|
|
41
|
+
layout_map = {layout.id: layout for layout in page.page_layout}
|
|
42
|
+
for paragraph in page.pdf_paragraph:
|
|
43
|
+
layout_id = paragraph.layout_id
|
|
44
|
+
if layout_id is None:
|
|
45
|
+
continue
|
|
46
|
+
layout = layout_map[layout_id]
|
|
47
|
+
if paragraph.box is None:
|
|
48
|
+
continue
|
|
49
|
+
x1, y1, x2, y2 = (
|
|
50
|
+
paragraph.box.x,
|
|
51
|
+
paragraph.box.y,
|
|
52
|
+
paragraph.box.x2,
|
|
53
|
+
paragraph.box.y2,
|
|
54
|
+
)
|
|
55
|
+
layout_box = layout.box
|
|
56
|
+
if layout_box.x < x1:
|
|
57
|
+
x1 = layout_box.x
|
|
58
|
+
if layout_box.y < y1:
|
|
59
|
+
y1 = layout_box.y
|
|
60
|
+
if layout_box.x2 > x2:
|
|
61
|
+
x2 = layout_box.x2
|
|
62
|
+
if layout_box.y2 > y2:
|
|
63
|
+
y2 = layout_box.y2
|
|
64
|
+
assert x2 > x1 and y2 > y1
|
|
65
|
+
page.pdf_rectangle.append(
|
|
66
|
+
PdfRectangle(
|
|
67
|
+
box=Box(x1, y1, x2, y2),
|
|
68
|
+
fill_background=True,
|
|
69
|
+
graphic_state=WHITE,
|
|
70
|
+
debug_info=False,
|
|
71
|
+
xobj_id=paragraph.xobj_id,
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
38
75
|
def update_paragraph_data(self, paragraph: PdfParagraph, update_unicode=False):
|
|
39
76
|
if not paragraph.pdf_paragraph_composition:
|
|
40
77
|
return
|
|
@@ -126,6 +163,12 @@ class ParagraphFinder:
|
|
|
126
163
|
for paragraph in paragraphs:
|
|
127
164
|
self.update_paragraph_data(paragraph, update_unicode=True)
|
|
128
165
|
|
|
166
|
+
if self.translation_config.ocr_workaround:
|
|
167
|
+
self.add_text_fill_background(page)
|
|
168
|
+
# since this is ocr file,
|
|
169
|
+
# image characters are not needed
|
|
170
|
+
page.pdf_character = []
|
|
171
|
+
|
|
129
172
|
def is_isolated_formula(self, char: PdfCharacter):
|
|
130
173
|
return char.char_unicode in (
|
|
131
174
|
"(cid:122)",
|
|
@@ -176,6 +219,7 @@ class ParagraphFinder:
|
|
|
176
219
|
if current_paragraph is None:
|
|
177
220
|
current_paragraph = PdfParagraph(
|
|
178
221
|
pdf_paragraph_composition=[line],
|
|
222
|
+
layout_id=char_layout.id,
|
|
179
223
|
debug_id=generate_base58_id(),
|
|
180
224
|
layout_label=char_layout.name
|
|
181
225
|
if not current_layout
|
|
@@ -193,7 +237,7 @@ class ParagraphFinder:
|
|
|
193
237
|
is_small_char = char_area < median_char_area * 0.1
|
|
194
238
|
|
|
195
239
|
# 检查是否需要开始新段落
|
|
196
|
-
# 如果字符面积小于中位数面积的10
|
|
240
|
+
# 如果字符面积小于中位数面积的 10% 且当前段落已有字符,则跳过新段落检测
|
|
197
241
|
if not (is_small_char and current_line_chars) and (
|
|
198
242
|
current_layout is None
|
|
199
243
|
or char_layout.id != current_layout.id
|
|
@@ -214,6 +258,7 @@ class ParagraphFinder:
|
|
|
214
258
|
else:
|
|
215
259
|
current_paragraph = PdfParagraph(
|
|
216
260
|
pdf_paragraph_composition=[line],
|
|
261
|
+
layout_id=current_layout.id,
|
|
217
262
|
debug_id=generate_base58_id(),
|
|
218
263
|
layout_label=current_layout.name,
|
|
219
264
|
)
|
|
@@ -231,6 +276,7 @@ class ParagraphFinder:
|
|
|
231
276
|
if current_paragraph is None:
|
|
232
277
|
current_paragraph = PdfParagraph(
|
|
233
278
|
pdf_paragraph_composition=[line],
|
|
279
|
+
layout_id=current_layout.id,
|
|
234
280
|
debug_id=generate_base58_id(),
|
|
235
281
|
layout_label=current_layout.name,
|
|
236
282
|
)
|
|
@@ -420,6 +466,7 @@ class ParagraphFinder:
|
|
|
420
466
|
unicode="",
|
|
421
467
|
debug_id=generate_base58_id(),
|
|
422
468
|
layout_label=paragraph.layout_label,
|
|
469
|
+
layout_id=paragraph.layout_id,
|
|
423
470
|
)
|
|
424
471
|
# 更新原段落
|
|
425
472
|
paragraph.pdf_paragraph_composition = (
|
|
@@ -449,6 +496,7 @@ class ParagraphFinder:
|
|
|
449
496
|
unicode="",
|
|
450
497
|
debug_id=generate_base58_id(),
|
|
451
498
|
layout_label=paragraph.layout_label,
|
|
499
|
+
layout_id=paragraph.layout_id,
|
|
452
500
|
)
|
|
453
501
|
# 更新原段落
|
|
454
502
|
paragraph.pdf_paragraph_composition = (
|
|
@@ -395,7 +395,7 @@ class StylesAndFormulas:
|
|
|
395
395
|
right_line = comp.pdf_line
|
|
396
396
|
break
|
|
397
397
|
|
|
398
|
-
# 计算与左右文本的y轴交集
|
|
398
|
+
# 计算与左右文本的 y 轴交集
|
|
399
399
|
left_intersection = 0
|
|
400
400
|
right_intersection = 0
|
|
401
401
|
|
|
@@ -413,7 +413,7 @@ class StylesAndFormulas:
|
|
|
413
413
|
if intersection_end > intersection_start:
|
|
414
414
|
right_intersection = intersection_end - intersection_start
|
|
415
415
|
|
|
416
|
-
# 如果有两个文本段落,将交集较小的设为none
|
|
416
|
+
# 如果有两个文本段落,将交集较小的设为 none
|
|
417
417
|
if left_line and right_line:
|
|
418
418
|
if left_intersection < right_intersection:
|
|
419
419
|
left_line = None
|
|
@@ -556,6 +556,9 @@ class StylesAndFormulas:
|
|
|
556
556
|
r"|.*DJ5Exchange.*"
|
|
557
557
|
r"|.*Times-Italic.*"
|
|
558
558
|
r"|.*Times-BoldItalic.*"
|
|
559
|
+
r"|.*CronosPro.*"
|
|
560
|
+
r"|.*ACaslon.*"
|
|
561
|
+
r"|.*Frutiger.*"
|
|
559
562
|
r")$"
|
|
560
563
|
)
|
|
561
564
|
if self.translation_config.formular_font_pattern:
|
|
@@ -221,8 +221,8 @@ class TypesettingUnit:
|
|
|
221
221
|
# 连接号
|
|
222
222
|
"~", # 全角波浪号
|
|
223
223
|
"-", # 连字符减号
|
|
224
|
-
"–", # 短破折号(EN DASH)
|
|
225
|
-
"—", # 长破折号(EM DASH)
|
|
224
|
+
"–", # 短破折号 (EN DASH)
|
|
225
|
+
"—", # 长破折号 (EM DASH)
|
|
226
226
|
# 间隔号
|
|
227
227
|
"·", # 中间点
|
|
228
228
|
"・", # 片假名中间点
|
|
@@ -767,7 +767,7 @@ class Typesetting:
|
|
|
767
767
|
line_spacing = 1.7 # 初始行距为 1.7
|
|
768
768
|
min_scale = 0.1 # 最小缩放因子
|
|
769
769
|
min_line_spacing = 1.4 # 最小行距
|
|
770
|
-
expand_space_flag = 0 # 0:
|
|
770
|
+
expand_space_flag = 0 # 0: 未扩展,1: 已向下扩展,2: 已向右扩展
|
|
771
771
|
|
|
772
772
|
while scale >= min_scale:
|
|
773
773
|
# 尝试布局排版单元
|
|
@@ -25,6 +25,10 @@ def create_pdf_style(r, g, b, font_id="china-ss", font_size=6):
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
BLACK = il_version_1.GraphicState(passthrough_per_char_instruction="0 g 0 G")
|
|
29
|
+
|
|
30
|
+
WHITE = il_version_1.GraphicState(passthrough_per_char_instruction="1 g 1 G")
|
|
31
|
+
|
|
28
32
|
# Generate all color styles
|
|
29
33
|
RED = il_version_1.GraphicState(
|
|
30
34
|
passthrough_per_char_instruction="1.0000000000 0.2313725490 0.1882352941 rg "
|
|
@@ -43,10 +43,10 @@ def encode_image(image) -> bytes:
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
@retry(
|
|
46
|
-
stop=stop_after_attempt(3), # 最多重试3次
|
|
46
|
+
stop=stop_after_attempt(3), # 最多重试 3 次
|
|
47
47
|
wait=wait_exponential(
|
|
48
48
|
multiplier=1, min=1, max=10
|
|
49
|
-
), # 指数退避策略,初始1秒,最大10秒
|
|
49
|
+
), # 指数退避策略,初始 1 秒,最大 10 秒
|
|
50
50
|
retry=retry_if_exception_type((httpx.HTTPError, Exception)), # 针对哪些异常重试
|
|
51
51
|
before_sleep=lambda retry_state: logger.warning(
|
|
52
52
|
f"Request failed, retrying in {retry_state.next_action.sleep} seconds... "
|
|
@@ -586,6 +586,7 @@ def _do_translate_single(
|
|
|
586
586
|
logger.debug(f"finish parse il from {temp_pdf_path}")
|
|
587
587
|
docs = il_creater.create_il()
|
|
588
588
|
logger.debug(f"finish create il from {temp_pdf_path}")
|
|
589
|
+
del il_creater
|
|
589
590
|
if translation_config.debug:
|
|
590
591
|
xml_converter.write_json(
|
|
591
592
|
docs,
|
|
@@ -657,6 +658,7 @@ def _do_translate_single(
|
|
|
657
658
|
il_translator = ILTranslator(translate_engine, translation_config)
|
|
658
659
|
|
|
659
660
|
il_translator.translate(docs)
|
|
661
|
+
del il_translator
|
|
660
662
|
logger.debug(f"finish ILTranslator from {temp_pdf_path}")
|
|
661
663
|
if translation_config.debug:
|
|
662
664
|
xml_converter.write_json(
|
|
@@ -23,7 +23,7 @@ from babeldoc.translation_config import TranslationConfig
|
|
|
23
23
|
from babeldoc.translation_config import WatermarkOutputMode
|
|
24
24
|
|
|
25
25
|
logger = logging.getLogger(__name__)
|
|
26
|
-
__version__ = "0.3.
|
|
26
|
+
__version__ = "0.3.18"
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def create_parser():
|
|
@@ -210,6 +210,12 @@ def create_parser():
|
|
|
210
210
|
default=False,
|
|
211
211
|
help="Skip scanned document detection (speeds up processing for non-scanned documents)",
|
|
212
212
|
)
|
|
213
|
+
translation_group.add_argument(
|
|
214
|
+
"--ocr-workaround",
|
|
215
|
+
action="store_true",
|
|
216
|
+
default=False,
|
|
217
|
+
help="Add text fill background (experimental)",
|
|
218
|
+
)
|
|
213
219
|
# service option argument group
|
|
214
220
|
service_group = translation_group.add_mutually_exclusive_group()
|
|
215
221
|
service_group.add_argument(
|
|
@@ -377,6 +383,7 @@ async def main():
|
|
|
377
383
|
table_model=table_model,
|
|
378
384
|
show_char_box=args.show_char_box,
|
|
379
385
|
skip_scanned_detection=args.skip_scanned_detection,
|
|
386
|
+
ocr_workaround=args.ocr_workaround,
|
|
380
387
|
)
|
|
381
388
|
|
|
382
389
|
# Create progress handler
|