BabelDOC 0.1.31__tar.gz → 0.2.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.
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.github/workflows/test.yml +5 -1
- {babeldoc-0.1.31 → babeldoc-0.2.0}/LICENSE +1 -1
- {babeldoc-0.1.31 → babeldoc-0.2.0}/PKG-INFO +5 -6
- {babeldoc-0.1.31 → babeldoc-0.2.0}/README.md +3 -5
- babeldoc-0.2.0/babeldoc/__init__.py +1 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/const.py +1 -1
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/__init__.py +4 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/frontend/il_creater.py +249 -4
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/il_version_1.py +142 -73
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/il_version_1.rnc +12 -2
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/il_version_1.rng +27 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/il_version_1.xsd +20 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/midend/il_translator.py +8 -8
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/midend/paragraph_finder.py +88 -73
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/midend/styles_and_formulas.py +62 -9
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/midend/typesetting.py +144 -37
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/translator/translator.py +0 -77
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/utils/layout_helper.py +3 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/high_level.py +9 -9
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/main.py +4 -26
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/CONTRIBUTING.md +13 -11
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/deploy.sh +1 -1
- {babeldoc-0.1.31 → babeldoc-0.2.0}/pyproject.toml +3 -2
- {babeldoc-0.1.31 → babeldoc-0.2.0}/uv.lock +17 -1
- babeldoc-0.1.31/babeldoc/__init__.py +0 -1
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.cursorignore +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.github/dependabot.yml +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.github/labels.yml +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.github/release-drafter.yml +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.github/workflows/codeql.yml +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.github/workflows/docs.yml +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.github/workflows/labeler.yml +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.github/workflows/publish-to-pypi.yml +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.gitignore +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/.pre-commit-config.yaml +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/assets/assets.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/assets/embedding_assets_metadata.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/asynchronize/__init__.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/converter.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/backend/__init__.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/backend/pdf_creater.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/frontend/__init__.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/midend/__init__.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/midend/add_debug_information.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/midend/detect_scanned_file.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/midend/layout_parser.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/midend/remove_descent.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/translator/__init__.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/translator/cache.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/utils/fontmap.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/utils/style_helper.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/document_il/xml_converter.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/docvision/README.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/docvision/__init__.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/docvision/doclayout.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/docvision/rpc_doclayout.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/pdfinterp.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/progress_monitor.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/tools/generate_font_metadata.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/babeldoc/translation_config.py +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/CODE_OF_CONDUCT.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/CONTRIBUTOR_REWARD.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/ImplementationDetails/README.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/README.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/images/babeldoc-banner.png +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/images/babeldoc-big-logo.png +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/images/babeldoc-contributor_reward_example.png +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/images/babeldoc-preview.gif +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/images/babeldoc-small-logo.png +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/index.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/intro-to-pdf-object.md +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/docs/requirements.txt +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/mkdocs.yml +0 -0
- {babeldoc-0.1.31 → babeldoc-0.2.0}/tests/test_translation_config.py +0 -0
|
@@ -37,9 +37,13 @@ jobs:
|
|
|
37
37
|
run: |
|
|
38
38
|
uv run babeldoc --warmup
|
|
39
39
|
- name: Run tests
|
|
40
|
+
env:
|
|
41
|
+
OPENAI_API_KEY: ${{ secrets.OPENAIAPIKEY }}
|
|
42
|
+
OPENAI_BASE_URL: ${{ secrets.OPENAIAPIURL }}
|
|
43
|
+
OPENAI_MODEL: ${{ secrets.OPENAIMODEL }}
|
|
40
44
|
run: |
|
|
41
45
|
uv run babeldoc --help
|
|
42
|
-
uv run babeldoc --
|
|
46
|
+
uv run babeldoc --openai --files examples/ci/test.pdf --openai-api-key ${{ env.OPENAI_API_KEY }} --openai-base-url ${{ env.OPENAI_BASE_URL }} --openai-model ${{ env.OPENAI_MODEL }}
|
|
43
47
|
- name: Generate offline assets package
|
|
44
48
|
run: |
|
|
45
49
|
uv run babeldoc --generate-offline-assets /tmp/offline_assets
|
|
@@ -629,7 +629,7 @@ to attach them to the start of each source file to most effectively
|
|
|
629
629
|
state the exclusion of warranty; and each file should have at least
|
|
630
630
|
the "copyright" line and a pointer to where the full notice is found.
|
|
631
631
|
|
|
632
|
-
|
|
632
|
+
BabelDOC is library for ultimated document translation solution.
|
|
633
633
|
Copyright (C) 2024 <funstory.ai limited>
|
|
634
634
|
|
|
635
635
|
This program is free software: you can redistribute it and/or modify
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: BabelDOC
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
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
|
|
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: <3.13,>=3.10
|
|
13
13
|
Requires-Dist: bitstring>=4.3.0
|
|
14
14
|
Requires-Dist: configargparse>=1.7
|
|
15
|
+
Requires-Dist: freetype-py>=2.5.1
|
|
15
16
|
Requires-Dist: httpx[socks]>=0.27.0
|
|
16
17
|
Requires-Dist: huggingface-hub>=0.27.0
|
|
17
18
|
Requires-Dist: msgpack>=1.1.0
|
|
@@ -124,10 +125,10 @@ uv run babeldoc --help
|
|
|
124
125
|
3. Use the `uv run babeldoc` command. For example:
|
|
125
126
|
|
|
126
127
|
```bash
|
|
127
|
-
uv run babeldoc --
|
|
128
|
+
uv run babeldoc --files example.pdf --openai --openai-model "gpt-4o-mini" --openai-base-url "https://api.openai.com/v1" --openai-api-key "your-api-key-here"
|
|
128
129
|
|
|
129
130
|
# multiple files
|
|
130
|
-
uv run babeldoc --
|
|
131
|
+
uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model "gpt-4o-mini" --openai-base-url "https://api.openai.com/v1" --openai-api-key "your-api-key-here"
|
|
131
132
|
```
|
|
132
133
|
|
|
133
134
|
> [!TIP]
|
|
@@ -175,12 +176,10 @@ uv run babeldoc --bing --files example.pdf --files example2.pdf
|
|
|
175
176
|
- `--no-mono`: Do not output monolingual PDF files
|
|
176
177
|
- `--min-text-length`: Minimum text length to translate (default: 5)
|
|
177
178
|
- `--openai`: Use OpenAI for translation (default: False)
|
|
178
|
-
- `--bing`: Use Bing for translation (default: False)
|
|
179
|
-
- `--google`: Use Google Translate for translation (default: False)
|
|
180
179
|
|
|
181
180
|
> [!TIP]
|
|
182
181
|
>
|
|
183
|
-
> 1. You must specify one translation service among `--openai
|
|
182
|
+
> 1. You must specify one translation service among `--openai`.
|
|
184
183
|
> 2. It is recommended to use models with strong compatibility with OpenAI, such as: `glm-4-flash`, `deepseek-chat`, etc.
|
|
185
184
|
> 3. Currently, it has not been optimized for traditional translation engines like Bing/Google, it is recommended to use LLMs.
|
|
186
185
|
|
|
@@ -89,10 +89,10 @@ uv run babeldoc --help
|
|
|
89
89
|
3. Use the `uv run babeldoc` command. For example:
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
uv run babeldoc --
|
|
92
|
+
uv run babeldoc --files example.pdf --openai --openai-model "gpt-4o-mini" --openai-base-url "https://api.openai.com/v1" --openai-api-key "your-api-key-here"
|
|
93
93
|
|
|
94
94
|
# multiple files
|
|
95
|
-
uv run babeldoc --
|
|
95
|
+
uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model "gpt-4o-mini" --openai-base-url "https://api.openai.com/v1" --openai-api-key "your-api-key-here"
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
> [!TIP]
|
|
@@ -140,12 +140,10 @@ uv run babeldoc --bing --files example.pdf --files example2.pdf
|
|
|
140
140
|
- `--no-mono`: Do not output monolingual PDF files
|
|
141
141
|
- `--min-text-length`: Minimum text length to translate (default: 5)
|
|
142
142
|
- `--openai`: Use OpenAI for translation (default: False)
|
|
143
|
-
- `--bing`: Use Bing for translation (default: False)
|
|
144
|
-
- `--google`: Use Google Translate for translation (default: False)
|
|
145
143
|
|
|
146
144
|
> [!TIP]
|
|
147
145
|
>
|
|
148
|
-
> 1. You must specify one translation service among `--openai
|
|
146
|
+
> 1. You must specify one translation service among `--openai`.
|
|
149
147
|
> 2. It is recommended to use models with strong compatibility with OpenAI, such as: `glm-4-flash`, `deepseek-chat`, etc.
|
|
150
148
|
> 3. Currently, it has not been optimized for traditional translation engines like Bing/Google, it is recommended to use LLMs.
|
|
151
149
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -9,6 +9,7 @@ from babeldoc.document_il.il_version_1 import PageLayout
|
|
|
9
9
|
from babeldoc.document_il.il_version_1 import PdfCharacter
|
|
10
10
|
from babeldoc.document_il.il_version_1 import PdfFigure
|
|
11
11
|
from babeldoc.document_il.il_version_1 import PdfFont
|
|
12
|
+
from babeldoc.document_il.il_version_1 import PdfFontCharBoundingBox
|
|
12
13
|
from babeldoc.document_il.il_version_1 import PdfFormula
|
|
13
14
|
from babeldoc.document_il.il_version_1 import PdfLine
|
|
14
15
|
from babeldoc.document_il.il_version_1 import PdfParagraph
|
|
@@ -18,6 +19,7 @@ from babeldoc.document_il.il_version_1 import PdfSameStyleCharacters
|
|
|
18
19
|
from babeldoc.document_il.il_version_1 import PdfSameStyleUnicodeCharacters
|
|
19
20
|
from babeldoc.document_il.il_version_1 import PdfStyle
|
|
20
21
|
from babeldoc.document_il.il_version_1 import PdfXobject
|
|
22
|
+
from babeldoc.document_il.il_version_1 import VisualBbox
|
|
21
23
|
|
|
22
24
|
__all__ = [
|
|
23
25
|
"BaseOperations",
|
|
@@ -31,6 +33,7 @@ __all__ = [
|
|
|
31
33
|
"PdfCharacter",
|
|
32
34
|
"PdfFigure",
|
|
33
35
|
"PdfFont",
|
|
36
|
+
"PdfFontCharBoundingBox",
|
|
34
37
|
"PdfFormula",
|
|
35
38
|
"PdfLine",
|
|
36
39
|
"PdfParagraph",
|
|
@@ -40,4 +43,5 @@ __all__ = [
|
|
|
40
43
|
"PdfSameStyleUnicodeCharacters",
|
|
41
44
|
"PdfStyle",
|
|
42
45
|
"PdfXobject",
|
|
46
|
+
"VisualBbox",
|
|
43
47
|
]
|
|
@@ -1,21 +1,164 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import logging
|
|
3
3
|
import re
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
from itertools import islice
|
|
4
7
|
|
|
8
|
+
import freetype
|
|
5
9
|
import pdfminer.pdfinterp
|
|
6
10
|
import pymupdf
|
|
7
11
|
from pdfminer.layout import LTChar
|
|
8
12
|
from pdfminer.layout import LTFigure
|
|
9
13
|
from pdfminer.pdffont import PDFCIDFont
|
|
10
14
|
from pdfminer.pdffont import PDFFont
|
|
15
|
+
from pdfminer.pdfpage import PDFPage as PDFMinerPDFPage
|
|
16
|
+
from pdfminer.pdftypes import PDFObjRef as PDFMinerPDFObjRef
|
|
17
|
+
from pdfminer.pdftypes import resolve1 as pdftypes_resolve1
|
|
11
18
|
from pdfminer.psparser import PSLiteral
|
|
12
19
|
|
|
13
20
|
from babeldoc.document_il import il_version_1
|
|
14
21
|
from babeldoc.translation_config import TranslationConfig
|
|
15
22
|
|
|
23
|
+
|
|
24
|
+
def batched(iterable, n, *, strict=False):
|
|
25
|
+
# batched('ABCDEFG', 3) → ABC DEF G
|
|
26
|
+
if n < 1:
|
|
27
|
+
raise ValueError("n must be at least one")
|
|
28
|
+
iterator = iter(iterable)
|
|
29
|
+
while batch := tuple(islice(iterator, n)):
|
|
30
|
+
if strict and len(batch) != n:
|
|
31
|
+
raise ValueError("batched(): incomplete batch")
|
|
32
|
+
yield batch
|
|
33
|
+
|
|
34
|
+
|
|
16
35
|
logger = logging.getLogger(__name__)
|
|
17
36
|
|
|
18
37
|
|
|
38
|
+
def create_hook(func, hook):
|
|
39
|
+
@wraps(func)
|
|
40
|
+
def wrapper(*args, **kwargs):
|
|
41
|
+
hook(*args, **kwargs)
|
|
42
|
+
return func(*args, **kwargs)
|
|
43
|
+
|
|
44
|
+
return wrapper
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def hook_pdfminer_pdf_page_init(*args):
|
|
48
|
+
attrs = args[3]
|
|
49
|
+
while isinstance(attrs["MediaBox"], PDFMinerPDFObjRef):
|
|
50
|
+
attrs["MediaBox"] = pdftypes_resolve1(attrs["MediaBox"])
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
PDFMinerPDFPage.__init__ = create_hook(
|
|
54
|
+
PDFMinerPDFPage.__init__, hook_pdfminer_pdf_page_init
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def indirect(obj):
|
|
59
|
+
if isinstance(obj, tuple) and obj[0] == "xref":
|
|
60
|
+
return int(obj[1].split(" ")[0])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_glyph_cbox(face, g):
|
|
64
|
+
face.load_glyph(g, freetype.FT_LOAD_NO_SCALE)
|
|
65
|
+
cbox = face.glyph.outline.get_bbox()
|
|
66
|
+
return cbox.xMin, cbox.yMin, cbox.xMax, cbox.yMax
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_char_cbox(face, idx):
|
|
70
|
+
g = face.get_char_index(idx)
|
|
71
|
+
return get_glyph_cbox(face, g)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_name_cbox(face, name):
|
|
75
|
+
g = face.get_name_index(name)
|
|
76
|
+
return get_glyph_cbox(face, g)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def parse_font_file(doc, idx, encoding):
|
|
80
|
+
bbox_list = []
|
|
81
|
+
data = doc.xref_stream(idx)
|
|
82
|
+
face = freetype.Face(BytesIO(data))
|
|
83
|
+
for charmap in face.charmaps:
|
|
84
|
+
if charmap.encoding_name == "FT_ENCODING_ADOBE_CUSTOM":
|
|
85
|
+
face.select_charmap(freetype.FT_ENCODING_ADOBE_CUSTOM)
|
|
86
|
+
break
|
|
87
|
+
bbox_list = [get_char_cbox(face, x) for x in range(0, 256)]
|
|
88
|
+
if encoding:
|
|
89
|
+
for code, name in encoding:
|
|
90
|
+
bbox_list[code] = get_name_cbox(face, name.encode("U8"))
|
|
91
|
+
return bbox_list
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def parse_encoding(obj_str):
|
|
95
|
+
delta = []
|
|
96
|
+
current = 0
|
|
97
|
+
for x in re.finditer(
|
|
98
|
+
r"(?P<p>[\[\]])|(?P<c>\d+)|(?P<n>/[a-zA-Z0-9]+)|(?P<s>.)", obj_str
|
|
99
|
+
):
|
|
100
|
+
key = x.lastgroup
|
|
101
|
+
val = x.group()
|
|
102
|
+
if key == "c":
|
|
103
|
+
current = int(val)
|
|
104
|
+
if key == "n":
|
|
105
|
+
delta.append((current, val[1:]))
|
|
106
|
+
current += 1
|
|
107
|
+
return delta
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def parse_mapping(text):
|
|
111
|
+
mapping = []
|
|
112
|
+
for x in re.finditer(r"<(?P<num>[a-fA-F0-9]+)>", text):
|
|
113
|
+
mapping.append(int(x.group("num"), 16))
|
|
114
|
+
return mapping
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def update_cmap_pair(cmap, data):
|
|
118
|
+
for start, stop, value in batched(data, 3):
|
|
119
|
+
for code in range(start, stop + 1):
|
|
120
|
+
cmap[code] = value
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def update_cmap_code(cmap, data):
|
|
124
|
+
for code, value in batched(data, 2):
|
|
125
|
+
cmap[code] = value
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def parse_cmap(cmap_str):
|
|
129
|
+
cmap = {}
|
|
130
|
+
for x in re.finditer(
|
|
131
|
+
r"\s+beginbfrange\s*(?P<r>(<[0-9a-fA-F]+>\s*)+)endbfrange\s+", cmap_str
|
|
132
|
+
):
|
|
133
|
+
update_cmap_pair(cmap, parse_mapping(x.group("r")))
|
|
134
|
+
for x in re.finditer(
|
|
135
|
+
r"\s+beginbfchar\s*(?P<c>(<[0-9a-fA-F]+>\s*)+)endbfchar", cmap_str
|
|
136
|
+
):
|
|
137
|
+
update_cmap_code(cmap, parse_mapping(x.group("c")))
|
|
138
|
+
return cmap
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def get_code(cmap, c):
|
|
142
|
+
for k, v in cmap.items():
|
|
143
|
+
if v == c:
|
|
144
|
+
return k
|
|
145
|
+
return -1
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def get_bbox(bbox, size, c, x, y):
|
|
149
|
+
x_min, y_min, x_max, y_max = bbox[c]
|
|
150
|
+
factor = 1 / 1000 * size
|
|
151
|
+
x_min = x_min * factor
|
|
152
|
+
y_min = -y_min * factor
|
|
153
|
+
x_max = x_max * factor
|
|
154
|
+
y_max = -y_max * factor
|
|
155
|
+
ll = (x + x_min, y + y_min)
|
|
156
|
+
lr = (x + x_max, y + y_min)
|
|
157
|
+
ul = (x + x_min, y + y_max)
|
|
158
|
+
ur = (x + x_max, y + y_max)
|
|
159
|
+
return pymupdf.Quad(ll, lr, ul, ur)
|
|
160
|
+
|
|
161
|
+
|
|
19
162
|
class ILCreater:
|
|
20
163
|
stage_name = "Parse PDF and Create Intermediate Representation"
|
|
21
164
|
|
|
@@ -34,6 +177,8 @@ class ILCreater:
|
|
|
34
177
|
self.xobj_inc = 0
|
|
35
178
|
self.xobj_map: dict[int, il_version_1.PdfXobject] = {}
|
|
36
179
|
self.xobj_stack = []
|
|
180
|
+
self.current_page_font_name_id_map = {}
|
|
181
|
+
self.current_page_font_char_bounding_box_map = {}
|
|
37
182
|
|
|
38
183
|
def on_finish(self):
|
|
39
184
|
self.progress.__exit__(None, None, None)
|
|
@@ -97,12 +242,21 @@ class ILCreater:
|
|
|
97
242
|
|
|
98
243
|
def push_xobj(self):
|
|
99
244
|
self.xobj_stack.append(
|
|
100
|
-
(
|
|
245
|
+
(
|
|
246
|
+
self.current_page_font_name_id_map.copy(),
|
|
247
|
+
self.current_page_font_char_bounding_box_map.copy(),
|
|
248
|
+
self.xobj_id,
|
|
249
|
+
),
|
|
101
250
|
)
|
|
102
251
|
self.current_page_font_name_id_map = {}
|
|
252
|
+
self.current_page_font_char_bounding_box_map = {}
|
|
103
253
|
|
|
104
254
|
def pop_xobj(self):
|
|
105
|
-
|
|
255
|
+
(
|
|
256
|
+
self.current_page_font_name_id_map,
|
|
257
|
+
self.current_page_font_char_bounding_box_map,
|
|
258
|
+
self.xobj_id,
|
|
259
|
+
) = self.xobj_stack.pop()
|
|
106
260
|
|
|
107
261
|
def on_xobj_begin(self, bbox, xref_id):
|
|
108
262
|
self.push_passthrough_per_char_instruction()
|
|
@@ -140,6 +294,7 @@ class ILCreater:
|
|
|
140
294
|
unit="point",
|
|
141
295
|
)
|
|
142
296
|
self.current_page_font_name_id_map = {}
|
|
297
|
+
self.current_page_font_char_bounding_box_map = {}
|
|
143
298
|
self.passthrough_per_char_instruction_stack = []
|
|
144
299
|
self.xobj_stack = []
|
|
145
300
|
self.non_stroking_color_space_name = None
|
|
@@ -229,13 +384,73 @@ class ILCreater:
|
|
|
229
384
|
serif=serif,
|
|
230
385
|
ascent=font.ascent,
|
|
231
386
|
descent=font.descent,
|
|
387
|
+
pdf_font_char_bounding_box=[],
|
|
232
388
|
)
|
|
389
|
+
try:
|
|
390
|
+
bbox_list, cmap = self.parse_font_xobj_id(xref_id)
|
|
391
|
+
font_char_bounding_box_map = {}
|
|
392
|
+
if not cmap:
|
|
393
|
+
cmap = {x: x for x in range(257)}
|
|
394
|
+
for char_id in cmap:
|
|
395
|
+
if char_id < 0 or char_id >= len(bbox_list):
|
|
396
|
+
continue
|
|
397
|
+
bbox = bbox_list[char_id]
|
|
398
|
+
x, y, x2, y2 = bbox
|
|
399
|
+
if (
|
|
400
|
+
x == 0
|
|
401
|
+
and y == 0
|
|
402
|
+
and x2 == 500
|
|
403
|
+
and y2 == 698
|
|
404
|
+
or x == 0
|
|
405
|
+
and y == 0
|
|
406
|
+
and x2 == 0
|
|
407
|
+
and y2 == 0
|
|
408
|
+
):
|
|
409
|
+
# ignore default bounding box
|
|
410
|
+
continue
|
|
411
|
+
il_font_metadata.pdf_font_char_bounding_box.append(
|
|
412
|
+
il_version_1.PdfFontCharBoundingBox(
|
|
413
|
+
x=x,
|
|
414
|
+
y=y,
|
|
415
|
+
x2=x2,
|
|
416
|
+
y2=y2,
|
|
417
|
+
char_id=char_id,
|
|
418
|
+
)
|
|
419
|
+
)
|
|
420
|
+
font_char_bounding_box_map[char_id] = bbox
|
|
421
|
+
if self.xobj_id in self.xobj_map:
|
|
422
|
+
if self.xobj_id not in self.current_page_font_char_bounding_box_map:
|
|
423
|
+
self.current_page_font_char_bounding_box_map[self.xobj_id] = {}
|
|
424
|
+
self.current_page_font_char_bounding_box_map[self.xobj_id][font_id] = (
|
|
425
|
+
font_char_bounding_box_map
|
|
426
|
+
)
|
|
427
|
+
else:
|
|
428
|
+
self.current_page_font_char_bounding_box_map[font_id] = (
|
|
429
|
+
font_char_bounding_box_map
|
|
430
|
+
)
|
|
431
|
+
except Exception:
|
|
432
|
+
pass
|
|
233
433
|
self.current_page_font_name_id_map[font_name] = font_id
|
|
234
434
|
if self.xobj_id in self.xobj_map:
|
|
235
435
|
self.xobj_map[self.xobj_id].pdf_font.append(il_font_metadata)
|
|
236
436
|
else:
|
|
237
437
|
self.current_page.pdf_font.append(il_font_metadata)
|
|
238
438
|
|
|
439
|
+
def parse_font_xobj_id(self, xobj_id: int):
|
|
440
|
+
encoding = []
|
|
441
|
+
font_encoding = self.mupdf.xref_get_key(xobj_id, "Encoding/Differences")
|
|
442
|
+
if font_encoding:
|
|
443
|
+
encoding = parse_encoding(font_encoding[1])
|
|
444
|
+
bbox_list = []
|
|
445
|
+
font_file = self.mupdf.xref_get_key(xobj_id, "FontDescriptor/FontFile")
|
|
446
|
+
if file_idx := indirect(font_file):
|
|
447
|
+
bbox_list = parse_font_file(self.mupdf, file_idx, encoding)
|
|
448
|
+
cmap = {}
|
|
449
|
+
to_unicode = self.mupdf.xref_get_key(xobj_id, "ToUnicode")
|
|
450
|
+
if to_unicode_idx := indirect(to_unicode):
|
|
451
|
+
cmap = parse_cmap(self.mupdf.xref_stream(to_unicode_idx).decode("U8"))
|
|
452
|
+
return bbox_list, cmap
|
|
453
|
+
|
|
239
454
|
def create_graphic_state(self, gs: pdfminer.pdfinterp.PDFGraphicState):
|
|
240
455
|
graphic_state = il_version_1.GraphicState()
|
|
241
456
|
for k, v in gs.__dict__.items():
|
|
@@ -278,13 +493,27 @@ class ILCreater:
|
|
|
278
493
|
descent = font.descent * char.size / 1000
|
|
279
494
|
|
|
280
495
|
char_id = char.cid
|
|
496
|
+
|
|
497
|
+
if font_bounding_box_map := self.current_page_font_char_bounding_box_map.get(
|
|
498
|
+
self.xobj_id, self.current_page_font_char_bounding_box_map
|
|
499
|
+
).get(font.font_id):
|
|
500
|
+
char_bounding_box = font_bounding_box_map.get(char_id, None)
|
|
501
|
+
else:
|
|
502
|
+
char_bounding_box = None
|
|
503
|
+
|
|
281
504
|
char_unicode = char.get_text()
|
|
282
505
|
if "(cid:" not in char_unicode and len(char_unicode) > 1:
|
|
283
506
|
return
|
|
284
507
|
advance = char.adv
|
|
508
|
+
bbox = il_version_1.Box(
|
|
509
|
+
x=char.bbox[0],
|
|
510
|
+
y=char.bbox[1],
|
|
511
|
+
x2=char.bbox[2],
|
|
512
|
+
y2=char.bbox[3],
|
|
513
|
+
)
|
|
285
514
|
if char.matrix[0] == 0 and char.matrix[3] == 0:
|
|
286
515
|
vertical = True
|
|
287
|
-
|
|
516
|
+
visual_bbox = il_version_1.Box(
|
|
288
517
|
x=char.bbox[0] - descent,
|
|
289
518
|
y=char.bbox[1],
|
|
290
519
|
x2=char.bbox[2] - descent,
|
|
@@ -293,12 +522,13 @@ class ILCreater:
|
|
|
293
522
|
else:
|
|
294
523
|
vertical = False
|
|
295
524
|
# Add descent to y coordinates
|
|
296
|
-
|
|
525
|
+
visual_bbox = il_version_1.Box(
|
|
297
526
|
x=char.bbox[0],
|
|
298
527
|
y=char.bbox[1] + descent,
|
|
299
528
|
x2=char.bbox[2],
|
|
300
529
|
y2=char.bbox[3] + descent,
|
|
301
530
|
)
|
|
531
|
+
visual_bbox = il_version_1.VisualBbox(box=visual_bbox)
|
|
302
532
|
pdf_style = il_version_1.PdfStyle(
|
|
303
533
|
font_id=char.aw_font_id,
|
|
304
534
|
font_size=char.size,
|
|
@@ -312,6 +542,7 @@ class ILCreater:
|
|
|
312
542
|
vertical=vertical,
|
|
313
543
|
pdf_style=pdf_style,
|
|
314
544
|
xobj_id=char.xobj_id,
|
|
545
|
+
visual_bbox=visual_bbox,
|
|
315
546
|
)
|
|
316
547
|
if pdf_style.font_size == 0.0:
|
|
317
548
|
logger.warning(
|
|
@@ -319,6 +550,20 @@ class ILCreater:
|
|
|
319
550
|
char_unicode,
|
|
320
551
|
)
|
|
321
552
|
return
|
|
553
|
+
|
|
554
|
+
if char_bounding_box:
|
|
555
|
+
x_min, y_min, x_max, y_max = char_bounding_box
|
|
556
|
+
factor = 1 / 1000 * pdf_style.font_size
|
|
557
|
+
x_min = x_min * factor
|
|
558
|
+
y_min = y_min * factor
|
|
559
|
+
x_max = x_max * factor
|
|
560
|
+
y_max = y_max * factor
|
|
561
|
+
ll = (char.bbox[0] + x_min, char.bbox[1] + y_min)
|
|
562
|
+
ur = (char.bbox[0] + x_max, char.bbox[1] + y_max)
|
|
563
|
+
pdf_char.visual_bbox = il_version_1.VisualBbox(
|
|
564
|
+
il_version_1.Box(ll[0], ll[1], ur[0], ur[1])
|
|
565
|
+
)
|
|
566
|
+
|
|
322
567
|
self.current_page.pdf_character.append(pdf_char)
|
|
323
568
|
|
|
324
569
|
def create_il(self):
|