BabelDOC 0.3.20__tar.gz → 0.3.22__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.20 → babeldoc-0.3.22}/PKG-INFO +3 -1
- babeldoc-0.3.22/babeldoc/__init__.py +1 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/assets/embedding_assets_metadata.py +24 -23
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/asynchronize/__init__.py +1 -1
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/const.py +1 -1
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/backend/pdf_creater.py +137 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/frontend/il_creater.py +8 -3
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/midend/paragraph_finder.py +2 -16
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/midend/styles_and_formulas.py +94 -8
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/translator/translator.py +13 -6
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/utils/fontmap.py +17 -1
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/utils/layout_helper.py +18 -0
- babeldoc-0.3.22/babeldoc/format/office/__init__.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/high_level.py +37 -1
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/main.py +1 -1
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/tools/italic_assistance.py +1 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/tools/italic_recognize_tool.py +8 -2
- {babeldoc-0.3.20 → babeldoc-0.3.22}/pyproject.toml +3 -3
- {babeldoc-0.3.20 → babeldoc-0.3.22}/uv.lock +6 -4
- babeldoc-0.3.20/babeldoc/__init__.py +0 -1
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.cursorignore +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/PULL_REQUEST_TEMPLATE/pr_form.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/dependabot.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/labels.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/release-drafter.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/workflows/codeql.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/workflows/docs.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/workflows/labeler.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/workflows/lint.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/workflows/pr-lint.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/workflows/publish-to-pypi.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.github/workflows/test.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.gitignore +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/.pre-commit-config.yaml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/LICENSE +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/README.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/assets/assets.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/converter.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/__init__.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/backend/__init__.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/frontend/__init__.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/il_version_1.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/il_version_1.rnc +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/il_version_1.rng +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/il_version_1.xsd +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/midend/__init__.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/midend/add_debug_information.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/midend/detect_scanned_file.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/midend/il_translator.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/midend/il_translator_llm_only.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/midend/layout_parser.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/midend/remove_descent.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/midend/table_parser.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/midend/typesetting.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/translator/__init__.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/translator/cache.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/utils/atomic_integer.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/utils/priority_thread_pool_executor.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/utils/style_helper.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/xml_converter.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/docvision/README.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/docvision/__init__.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/docvision/doclayout.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/docvision/rpc_doclayout.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/docvision/table_detection/rapidocr.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/pdfinterp.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/progress_monitor.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/result_merger.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/split_manager.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/tools/generate_font_metadata.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/translation_config.py +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/CODE_OF_CONDUCT.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/CONTRIBUTING.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/CONTRIBUTOR_REWARD.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/ImplementationDetails/README.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/README.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/deploy.sh +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-banner.png +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-big-logo.png +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-contributor_reward_example.png +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-preview.gif +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-small-logo.png +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/index.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/intro-to-pdf-object.md +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/docs/requirements.txt +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/mkdocs.yml +0 -0
- {babeldoc-0.3.20 → babeldoc-0.3.22}/tests/test_translation_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: BabelDOC
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.22
|
|
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
|
|
@@ -42,6 +42,8 @@ Provides-Extra: cuda
|
|
|
42
42
|
Requires-Dist: onnxruntime-gpu>=1.16.1; extra == 'cuda'
|
|
43
43
|
Provides-Extra: directml
|
|
44
44
|
Requires-Dist: onnxruntime-directml>=1.16.1; extra == 'directml'
|
|
45
|
+
Provides-Extra: memray
|
|
46
|
+
Requires-Dist: memray>=1.17.1; extra == 'memray'
|
|
45
47
|
Description-Content-Type: text/markdown
|
|
46
48
|
|
|
47
49
|
<!-- # Yet Another Document Translator -->
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.22"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
|
|
1
3
|
DOCLAYOUT_YOLO_DOCSTRUCTBENCH_IMGSZ1024ONNX_SHA3_256 = (
|
|
2
4
|
"60be061226930524958b5465c8c04af3d7c03bcb0beb66454f5da9f792e3cf2a"
|
|
3
5
|
)
|
|
@@ -495,6 +497,7 @@ EMBEDDING_FONT_METADATA = {
|
|
|
495
497
|
},
|
|
496
498
|
}
|
|
497
499
|
|
|
500
|
+
FONT_NAMES = {v["font_name"] for v in EMBEDDING_FONT_METADATA.values()}
|
|
498
501
|
|
|
499
502
|
CN_FONT_FAMILY = {
|
|
500
503
|
# 手写体
|
|
@@ -517,16 +520,12 @@ CN_FONT_FAMILY = {
|
|
|
517
520
|
}
|
|
518
521
|
|
|
519
522
|
HK_FONT_FAMILY = {
|
|
520
|
-
"script": ["LXGWWenKaiTC-Regular.ttf"
|
|
523
|
+
"script": ["LXGWWenKaiTC-Regular.ttf"],
|
|
521
524
|
"normal": [
|
|
522
525
|
"SourceHanSerifHK-Bold.ttf",
|
|
523
526
|
"SourceHanSerifHK-Regular.ttf",
|
|
524
527
|
"SourceHanSansHK-Bold.ttf",
|
|
525
528
|
"SourceHanSansHK-Regular.ttf",
|
|
526
|
-
"SourceHanSerifCN-Bold.ttf",
|
|
527
|
-
"SourceHanSerifCN-Regular.ttf",
|
|
528
|
-
"SourceHanSansCN-Bold.ttf",
|
|
529
|
-
"SourceHanSansCN-Regular.ttf",
|
|
530
529
|
],
|
|
531
530
|
"fallback": [
|
|
532
531
|
"GoNotoKurrent-Regular.ttf",
|
|
@@ -536,16 +535,12 @@ HK_FONT_FAMILY = {
|
|
|
536
535
|
}
|
|
537
536
|
|
|
538
537
|
TW_FONT_FAMILY = {
|
|
539
|
-
"script": ["LXGWWenKaiTC-Regular.ttf"
|
|
538
|
+
"script": ["LXGWWenKaiTC-Regular.ttf"],
|
|
540
539
|
"normal": [
|
|
541
540
|
"SourceHanSerifTW-Bold.ttf",
|
|
542
541
|
"SourceHanSerifTW-Regular.ttf",
|
|
543
542
|
"SourceHanSansTW-Bold.ttf",
|
|
544
543
|
"SourceHanSansTW-Regular.ttf",
|
|
545
|
-
"SourceHanSerifCN-Bold.ttf",
|
|
546
|
-
"SourceHanSerifCN-Regular.ttf",
|
|
547
|
-
"SourceHanSansCN-Bold.ttf",
|
|
548
|
-
"SourceHanSansCN-Regular.ttf",
|
|
549
544
|
],
|
|
550
545
|
"fallback": [
|
|
551
546
|
"GoNotoKurrent-Regular.ttf",
|
|
@@ -561,10 +556,6 @@ KR_FONT_FAMILY = {
|
|
|
561
556
|
"SourceHanSerifKR-Regular.ttf",
|
|
562
557
|
"SourceHanSansKR-Bold.ttf",
|
|
563
558
|
"SourceHanSansKR-Regular.ttf",
|
|
564
|
-
"SourceHanSerifCN-Bold.ttf",
|
|
565
|
-
"SourceHanSerifCN-Regular.ttf",
|
|
566
|
-
"SourceHanSansCN-Bold.ttf",
|
|
567
|
-
"SourceHanSansCN-Regular.ttf",
|
|
568
559
|
],
|
|
569
560
|
"fallback": [
|
|
570
561
|
"GoNotoKurrent-Regular.ttf",
|
|
@@ -580,10 +571,6 @@ JP_FONT_FAMILY = {
|
|
|
580
571
|
"SourceHanSerifJP-Regular.ttf",
|
|
581
572
|
"SourceHanSansJP-Bold.ttf",
|
|
582
573
|
"SourceHanSansJP-Regular.ttf",
|
|
583
|
-
"SourceHanSerifCN-Bold.ttf",
|
|
584
|
-
"SourceHanSerifCN-Regular.ttf",
|
|
585
|
-
"SourceHanSansCN-Bold.ttf",
|
|
586
|
-
"SourceHanSansCN-Regular.ttf",
|
|
587
574
|
],
|
|
588
575
|
"fallback": [
|
|
589
576
|
"GoNotoKurrent-Regular.ttf",
|
|
@@ -604,10 +591,6 @@ EN_FONT_FAMILY = {
|
|
|
604
591
|
"NotoSerif-Bold.ttf",
|
|
605
592
|
"NotoSans-Regular.ttf",
|
|
606
593
|
"NotoSans-Bold.ttf",
|
|
607
|
-
"SourceHanSerifCN-Bold.ttf",
|
|
608
|
-
"SourceHanSerifCN-Regular.ttf",
|
|
609
|
-
"SourceHanSansCN-Bold.ttf",
|
|
610
|
-
"SourceHanSansCN-Regular.ttf",
|
|
611
594
|
],
|
|
612
595
|
"fallback": [
|
|
613
596
|
"GoNotoKurrent-Regular.ttf",
|
|
@@ -620,14 +603,32 @@ EN_FONT_FAMILY = {
|
|
|
620
603
|
|
|
621
604
|
ALL_FONT_FAMILY = {
|
|
622
605
|
"CN": CN_FONT_FAMILY,
|
|
623
|
-
"HK": HK_FONT_FAMILY,
|
|
624
606
|
"TW": TW_FONT_FAMILY,
|
|
607
|
+
"HK": HK_FONT_FAMILY,
|
|
625
608
|
"KR": KR_FONT_FAMILY,
|
|
626
609
|
"JP": JP_FONT_FAMILY,
|
|
627
610
|
"EN": EN_FONT_FAMILY,
|
|
628
611
|
}
|
|
629
612
|
|
|
630
613
|
|
|
614
|
+
def __add_fallback_to_font_family():
|
|
615
|
+
for lang1, family1 in ALL_FONT_FAMILY.items():
|
|
616
|
+
added_font = set()
|
|
617
|
+
for font in itertools.chain.from_iterable(family1.values()):
|
|
618
|
+
added_font.add(font)
|
|
619
|
+
|
|
620
|
+
for lang2, family2 in ALL_FONT_FAMILY.items():
|
|
621
|
+
if lang1 != lang2:
|
|
622
|
+
for type_ in family1:
|
|
623
|
+
for font in family2[type_]:
|
|
624
|
+
if font not in added_font:
|
|
625
|
+
family1[type_].append(font)
|
|
626
|
+
added_font.add(font)
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
__add_fallback_to_font_family()
|
|
630
|
+
|
|
631
|
+
|
|
631
632
|
def get_font_family(lang_code: str):
|
|
632
633
|
lang_code = lang_code.upper()
|
|
633
634
|
if "KR" in lang_code:
|
|
@@ -23,7 +23,7 @@ class AsyncCallback:
|
|
|
23
23
|
self.loop.call_soon_threadsafe(self.queue.put_nowait, args)
|
|
24
24
|
|
|
25
25
|
# Add a small delay to release the GIL, ensuring the event loop has time to process messages
|
|
26
|
-
time.sleep(0.
|
|
26
|
+
time.sleep(0.1)
|
|
27
27
|
|
|
28
28
|
def finished_callback(self, *args, **kwargs):
|
|
29
29
|
# Whenever a finished is called, add to the queue as with step, but also set finished to True, so __anext__
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import itertools
|
|
1
3
|
import logging
|
|
2
4
|
import os
|
|
3
5
|
import re
|
|
4
6
|
import time
|
|
7
|
+
import unicodedata
|
|
5
8
|
from multiprocessing import Process
|
|
6
9
|
from pathlib import Path
|
|
7
10
|
|
|
11
|
+
import freetype
|
|
8
12
|
import pymupdf
|
|
9
13
|
from bitstring import BitStream
|
|
10
14
|
|
|
15
|
+
from babeldoc.assets.embedding_assets_metadata import FONT_NAMES
|
|
11
16
|
from babeldoc.document_il import il_version_1
|
|
12
17
|
from babeldoc.document_il.utils.fontmap import FontMapper
|
|
13
18
|
from babeldoc.translation_config import TranslateResult
|
|
@@ -20,6 +25,138 @@ SUBSET_FONT_STAGE_NAME = "Subset font"
|
|
|
20
25
|
SAVE_PDF_STAGE_NAME = "Save PDF"
|
|
21
26
|
|
|
22
27
|
|
|
28
|
+
def to_int(src):
|
|
29
|
+
return int(re.search(r"\d+", src).group(0))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def parse_mapping(text):
|
|
33
|
+
mapping = []
|
|
34
|
+
for x in re.finditer(rb"<(?P<num>[a-fA-F0-9]+)>", text):
|
|
35
|
+
mapping.append(int(x.group("num"), 16))
|
|
36
|
+
return mapping
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def apply_normalization(cmap, gid, code):
|
|
40
|
+
need = False
|
|
41
|
+
if 0x2F00 <= code <= 0x2FD5: # Kangxi Radicals
|
|
42
|
+
need = True
|
|
43
|
+
if 0xF900 <= code <= 0xFAFF: # CJK Compatibility Ideographs
|
|
44
|
+
need = True
|
|
45
|
+
if need:
|
|
46
|
+
norm = unicodedata.normalize("NFD", chr(code))
|
|
47
|
+
cmap[gid] = ord(norm)
|
|
48
|
+
else:
|
|
49
|
+
cmap[gid] = code
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def batched(iterable, n, *, strict=False):
|
|
53
|
+
# batched('ABCDEFG', 3) → ABC DEF G
|
|
54
|
+
if n < 1:
|
|
55
|
+
raise ValueError("n must be at least one")
|
|
56
|
+
iterator = iter(iterable)
|
|
57
|
+
while batch := tuple(itertools.islice(iterator, n)):
|
|
58
|
+
if strict and len(batch) != n:
|
|
59
|
+
raise ValueError("batched(): incomplete batch")
|
|
60
|
+
yield batch
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def update_tounicode_cmap_pair(cmap, data):
|
|
64
|
+
for start, stop, value in batched(data, 3):
|
|
65
|
+
for gid in range(start, stop + 1):
|
|
66
|
+
code = value + gid - start
|
|
67
|
+
apply_normalization(cmap, gid, code)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def update_tounicode_cmap_code(cmap, data):
|
|
71
|
+
for gid, code in batched(data, 2):
|
|
72
|
+
apply_normalization(cmap, gid, code)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def parse_tounicode_cmap(data):
|
|
76
|
+
cmap = {}
|
|
77
|
+
for x in re.finditer(
|
|
78
|
+
rb"\s+beginbfrange\s*(?P<r>(<[0-9a-fA-F]+>\s*)+)endbfrange\s+", data
|
|
79
|
+
):
|
|
80
|
+
update_tounicode_cmap_pair(cmap, parse_mapping(x.group("r")))
|
|
81
|
+
for x in re.finditer(
|
|
82
|
+
rb"\s+beginbfchar\s*(?P<c>(<[0-9a-fA-F]+>\s*)+)endbfchar", data
|
|
83
|
+
):
|
|
84
|
+
update_tounicode_cmap_code(cmap, parse_mapping(x.group("c")))
|
|
85
|
+
return cmap
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def parse_truetype_data(data):
|
|
89
|
+
glyph_in_use = []
|
|
90
|
+
face = freetype.Face(io.BytesIO(data))
|
|
91
|
+
for i in range(face.num_glyphs):
|
|
92
|
+
face.load_glyph(i)
|
|
93
|
+
if face.glyph.outline.contours:
|
|
94
|
+
glyph_in_use.append(i)
|
|
95
|
+
return glyph_in_use
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
TOUNICODE_HEAD = """\
|
|
99
|
+
/CIDInit /ProcSet findresource begin
|
|
100
|
+
12 dict begin
|
|
101
|
+
/CIDSystemInfo <</Registry(Adobe)/Ordering(UCS)/Supplement 0>> def
|
|
102
|
+
/CMapName /Adobe-Identity-UCS def
|
|
103
|
+
/CMapType 2 def
|
|
104
|
+
1 begincodespacerange
|
|
105
|
+
<0000> <FFFF>
|
|
106
|
+
endcodespacerange"""
|
|
107
|
+
TOUNICODE_TAIL = """\
|
|
108
|
+
endcmap
|
|
109
|
+
CMapName currentdict /CMap defineresource pop
|
|
110
|
+
end
|
|
111
|
+
end"""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def make_tounicode(cmap, used):
|
|
115
|
+
short = []
|
|
116
|
+
for x in used:
|
|
117
|
+
if x in cmap:
|
|
118
|
+
short.append((x, cmap[x]))
|
|
119
|
+
line = [TOUNICODE_HEAD]
|
|
120
|
+
for block in batched(short, 100):
|
|
121
|
+
line.append(f"{len(block)} beginbfchar")
|
|
122
|
+
for glyph, code in block:
|
|
123
|
+
if code < 0x10000:
|
|
124
|
+
line.append(f"<{glyph:04x}><{code:04x}>")
|
|
125
|
+
else:
|
|
126
|
+
line.append(f"<{glyph:04x}><{code:08x}>")
|
|
127
|
+
line.append("endbfchar")
|
|
128
|
+
line.append(TOUNICODE_TAIL)
|
|
129
|
+
return "\n".join(line)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def reproduce_one_font(doc, index):
|
|
133
|
+
m = doc.xref_get_key(index, "ToUnicode")
|
|
134
|
+
f = doc.xref_get_key(index, "DescendantFonts")
|
|
135
|
+
if m[0] == "xref" and f[0] == "array":
|
|
136
|
+
mi = to_int(m[1])
|
|
137
|
+
fi = to_int(f[1])
|
|
138
|
+
ff = doc.xref_get_key(fi, "FontDescriptor/FontFile2")
|
|
139
|
+
ms = doc.xref_stream(mi)
|
|
140
|
+
fs = doc.xref_stream(to_int(ff[1]))
|
|
141
|
+
cmap = parse_tounicode_cmap(ms)
|
|
142
|
+
used = parse_truetype_data(fs)
|
|
143
|
+
text = make_tounicode(cmap, used)
|
|
144
|
+
doc.update_stream(mi, bytes(text, "U8"))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def reproduce_cmap(doc):
|
|
148
|
+
assert doc
|
|
149
|
+
font_set = set()
|
|
150
|
+
for page in doc:
|
|
151
|
+
font_list = page.get_fonts()
|
|
152
|
+
for font in font_list:
|
|
153
|
+
if font[1] == "ttf" and font[3] in FONT_NAMES and ".ttf" in font[4]:
|
|
154
|
+
font_set.add(font)
|
|
155
|
+
for font in font_set:
|
|
156
|
+
reproduce_one_font(doc, font[0])
|
|
157
|
+
return doc
|
|
158
|
+
|
|
159
|
+
|
|
23
160
|
def _subset_fonts_process(pdf_path, output_path):
|
|
24
161
|
"""Function to run in subprocess for font subsetting.
|
|
25
162
|
|
|
@@ -49,8 +49,11 @@ def create_hook(func, hook):
|
|
|
49
49
|
|
|
50
50
|
def hook_pdfminer_pdf_page_init(*args):
|
|
51
51
|
attrs = args[3]
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
try:
|
|
53
|
+
while isinstance(attrs["MediaBox"], PDFMinerPDFObjRef):
|
|
54
|
+
attrs["MediaBox"] = pdftypes_resolve1(attrs["MediaBox"])
|
|
55
|
+
except Exception:
|
|
56
|
+
logger.exception(f"try to fix mediabox failed: {attrs}")
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
PDFMinerPDFPage.__init__ = create_hook(
|
|
@@ -343,6 +346,7 @@ def parse_font_file(doc, idx, encoding, differences):
|
|
|
343
346
|
bbox_list = []
|
|
344
347
|
data = doc.xref_stream(idx)
|
|
345
348
|
face = freetype.Face(BytesIO(data))
|
|
349
|
+
scale = 1000 / face.units_per_EM
|
|
346
350
|
for charmap in face.charmaps:
|
|
347
351
|
if charmap.encoding_name == "FT_ENCODING_ADOBE_CUSTOM":
|
|
348
352
|
face.select_charmap(freetype.FT_ENCODING_ADOBE_CUSTOM)
|
|
@@ -351,7 +355,8 @@ def parse_font_file(doc, idx, encoding, differences):
|
|
|
351
355
|
if differences:
|
|
352
356
|
for code, name in differences:
|
|
353
357
|
bbox_list[code] = get_name_cbox(face, name.encode("U8"))
|
|
354
|
-
|
|
358
|
+
norm_bbox_list = [[v * scale for v in box] for box in bbox_list]
|
|
359
|
+
return norm_bbox_list
|
|
355
360
|
|
|
356
361
|
|
|
357
362
|
def parse_encoding(obj_str):
|
|
@@ -12,6 +12,7 @@ from babeldoc.document_il import PdfRectangle
|
|
|
12
12
|
from babeldoc.document_il.utils.layout_helper import Layout
|
|
13
13
|
from babeldoc.document_il.utils.layout_helper import add_space_dummy_chars
|
|
14
14
|
from babeldoc.document_il.utils.layout_helper import get_char_unicode_string
|
|
15
|
+
from babeldoc.document_il.utils.layout_helper import is_bullet_point
|
|
15
16
|
from babeldoc.document_il.utils.style_helper import WHITE
|
|
16
17
|
from babeldoc.translation_config import TranslationConfig
|
|
17
18
|
|
|
@@ -30,9 +31,6 @@ class ParagraphFinder:
|
|
|
30
31
|
stage_name = "Parse Paragraphs"
|
|
31
32
|
|
|
32
33
|
# 定义项目符号的正则表达式模式
|
|
33
|
-
BULLET_POINT_PATTERN = re.compile(
|
|
34
|
-
r"[■•⚫⬤◆◇○●◦‣⁃▪▫∗†‡¹²³⁴⁵⁶⁷⁸⁹⁰₁₂₃₄₅₆₇₈₉₀ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖᵍʳˢᵗᵘᵛʷˣʸᶻ†‡§¶※⁑⁂⁕⁎⁜⁑❧☙⁋‖‽]"
|
|
35
|
-
)
|
|
36
34
|
|
|
37
35
|
def __init__(self, translation_config: TranslationConfig):
|
|
38
36
|
self.translation_config = translation_config
|
|
@@ -246,7 +244,7 @@ class ParagraphFinder:
|
|
|
246
244
|
and current_line_chars[-1].xobj_id != char.xobj_id
|
|
247
245
|
)
|
|
248
246
|
or (
|
|
249
|
-
|
|
247
|
+
is_bullet_point(char) # 如果是项目符号,开启新段落
|
|
250
248
|
and not current_line_chars
|
|
251
249
|
)
|
|
252
250
|
):
|
|
@@ -512,15 +510,3 @@ class ParagraphFinder:
|
|
|
512
510
|
break
|
|
513
511
|
j += 1
|
|
514
512
|
i += 1
|
|
515
|
-
|
|
516
|
-
def is_bullet_point(self, char: PdfCharacter) -> bool:
|
|
517
|
-
"""Check if the character is a bullet point.
|
|
518
|
-
|
|
519
|
-
Args:
|
|
520
|
-
char: The character to check
|
|
521
|
-
|
|
522
|
-
Returns:
|
|
523
|
-
bool: True if the character is a bullet point
|
|
524
|
-
"""
|
|
525
|
-
is_bullet = bool(self.BULLET_POINT_PATTERN.match(char.char_unicode))
|
|
526
|
-
return is_bullet
|
|
@@ -17,6 +17,7 @@ from babeldoc.document_il.utils.fontmap import FontMapper
|
|
|
17
17
|
from babeldoc.document_il.utils.layout_helper import LEFT_BRACKET
|
|
18
18
|
from babeldoc.document_il.utils.layout_helper import RIGHT_BRACKET
|
|
19
19
|
from babeldoc.document_il.utils.layout_helper import formular_height_ignore_char
|
|
20
|
+
from babeldoc.document_il.utils.layout_helper import is_bullet_point
|
|
20
21
|
from babeldoc.document_il.utils.layout_helper import is_same_style
|
|
21
22
|
from babeldoc.translation_config import TranslationConfig
|
|
22
23
|
|
|
@@ -70,6 +71,7 @@ class StylesAndFormulas:
|
|
|
70
71
|
continue
|
|
71
72
|
|
|
72
73
|
new_compositions = []
|
|
74
|
+
first_is_bullet = False
|
|
73
75
|
|
|
74
76
|
for composition in paragraph.pdf_paragraph_composition:
|
|
75
77
|
current_chars = []
|
|
@@ -81,6 +83,8 @@ class StylesAndFormulas:
|
|
|
81
83
|
new_compositions.append(composition)
|
|
82
84
|
continue
|
|
83
85
|
for char in line.pdf_character:
|
|
86
|
+
if not current_chars and is_bullet_point(char):
|
|
87
|
+
first_is_bullet = True
|
|
84
88
|
is_formula = (
|
|
85
89
|
( # 区分公式开头的字符&公式中间的字符。主要是逗号不能在公式开头,但是可以在中间。
|
|
86
90
|
(
|
|
@@ -112,6 +116,7 @@ class StylesAndFormulas:
|
|
|
112
116
|
is_corner_mark = (
|
|
113
117
|
len(current_chars) > 0
|
|
114
118
|
and not isspace
|
|
119
|
+
and not first_is_bullet
|
|
115
120
|
# 角标字体,有 0.76 的角标和 0.799 的大写,这里用 0.79 取中,同时考虑首字母放大的情况
|
|
116
121
|
and char.pdf_style.font_size
|
|
117
122
|
< current_chars[-1].pdf_style.font_size * 0.79
|
|
@@ -119,6 +124,7 @@ class StylesAndFormulas:
|
|
|
119
124
|
) or (
|
|
120
125
|
len(current_chars) > 0
|
|
121
126
|
and not isspace
|
|
127
|
+
and not first_is_bullet
|
|
122
128
|
# 角标字体,有 0.76 的角标和 0.799 的大写,这里用 0.79 取中,同时考虑首字母放大的情况
|
|
123
129
|
and char.pdf_style.font_size
|
|
124
130
|
< current_chars[-1].pdf_style.font_size * 1.1
|
|
@@ -520,7 +526,7 @@ class StylesAndFormulas:
|
|
|
520
526
|
return bool(re.match(r"^[0-9, ]+$", text))
|
|
521
527
|
|
|
522
528
|
def is_formulas_font(self, font_name: str) -> bool:
|
|
523
|
-
|
|
529
|
+
pattern_text = (
|
|
524
530
|
r"^("
|
|
525
531
|
r"|Cambria.*"
|
|
526
532
|
r"|EUAlbertina.+"
|
|
@@ -570,12 +576,90 @@ class StylesAndFormulas:
|
|
|
570
576
|
r"|.*CronosPro.*"
|
|
571
577
|
r"|.*ACaslon.*"
|
|
572
578
|
r"|.*Frutiger.*"
|
|
579
|
+
r"|.*BrandonGrotesque.*"
|
|
580
|
+
r"|.*FairfieldLH.*"
|
|
581
|
+
r"|.*CaeciliaLTStd.*"
|
|
582
|
+
r"|.*Whitney.*"
|
|
583
|
+
r"|.*Mercury.*"
|
|
584
|
+
r"|.*SabonLTStd.*"
|
|
585
|
+
r"|.*AnonymousPro.*"
|
|
586
|
+
r"|.*SabonLTPro.*"
|
|
587
|
+
r"|.*ArnoPro.*"
|
|
588
|
+
r"|.*CharisSIL.*"
|
|
589
|
+
r")$"
|
|
590
|
+
)
|
|
591
|
+
precise_formula_font_pattern = (
|
|
592
|
+
r"^("
|
|
593
|
+
r"|.*CambriaMath.*"
|
|
594
|
+
r"|.*Cambria Math.*"
|
|
595
|
+
r"|.*Asana.*"
|
|
596
|
+
r"|.*MiriamMonoCLM-BookOblique.*"
|
|
597
|
+
r"|.*Miriam Mono CLM.*"
|
|
598
|
+
r"|.*Logix.*"
|
|
599
|
+
r"|.*AeBonum.*"
|
|
600
|
+
r"|.*AeMRoman.*"
|
|
601
|
+
r"|.*AePagella.*"
|
|
602
|
+
r"|.*AeSchola.*"
|
|
603
|
+
r"|.*Concrete.*"
|
|
604
|
+
r"|.*LatinModernMathCompanion.*"
|
|
605
|
+
r"|.*Latin Modern Math Companion.*"
|
|
606
|
+
r"|.*RalphSmithsFormalScriptCompanion.*"
|
|
607
|
+
r"|.*Ralph Smiths Formal Script Companion.*"
|
|
608
|
+
r"|.*TeXGyreBonumMathCompanion.*"
|
|
609
|
+
r"|.*TeX Gyre Bonum Companion.*"
|
|
610
|
+
r"|.*TeXGyrePagellaMathCompanion.*"
|
|
611
|
+
r"|.*TeX Gyre Pagella Math Companion.*"
|
|
612
|
+
r"|.*TeXGyreTermesMathCompanion.*"
|
|
613
|
+
r"|.*TeX Gyre Termes Math Companion.*"
|
|
614
|
+
r"|.*XITSMathCompanion.*"
|
|
615
|
+
r"|.*XITS Math Companion.*"
|
|
616
|
+
r"|.*Erewhon.*"
|
|
617
|
+
r"|.*Euler-Math.*"
|
|
618
|
+
r"|.*Euler Math.*"
|
|
619
|
+
r"|.*FiraMath-Regular.*"
|
|
620
|
+
r"|.*Fira Math.*"
|
|
621
|
+
r"|.*Garamond-Math.*"
|
|
622
|
+
r"|.*GFSNeohellenicMath.*"
|
|
623
|
+
r"|.*KpMath.*"
|
|
624
|
+
r"|.*Lete Sans Math.*"
|
|
625
|
+
r"|.*LeteSansMath.*"
|
|
626
|
+
r"|.*LinLibertineO.*"
|
|
627
|
+
r"|.*Linux Libertine O.*"
|
|
628
|
+
r"|.*LibertinusMath-Regular.*"
|
|
629
|
+
r"|.*Libertinus Math.*"
|
|
630
|
+
r"|.*LatinModernMath-Regular.*"
|
|
631
|
+
r"|.*Latin Modern Math.*"
|
|
632
|
+
r"|.*Luciole.*"
|
|
633
|
+
r"|.*NewCM.*"
|
|
634
|
+
r"|.*NewComputerModern.*"
|
|
635
|
+
r"|.*OldStandard-Math.*"
|
|
636
|
+
r"|.*STIXMath-Regular.*"
|
|
637
|
+
r"|.*STIX Math.*"
|
|
638
|
+
r"|.*STIXTwoMath-Regular.*"
|
|
639
|
+
r"|.*STIX Two Math.*"
|
|
640
|
+
r"|.*TeXGyreBonumMath.*"
|
|
641
|
+
r"|.*TeX Gyre Bonum Math.*"
|
|
642
|
+
r"|.*TeXGyreDejaVuMath.*"
|
|
643
|
+
r"|.*TeX Gyre DejaVu Math.*"
|
|
644
|
+
r"|.*TeXGyrePagellaMath.*"
|
|
645
|
+
r"|.*TeX Gyre Pagella Math.*"
|
|
646
|
+
r"|.*TeXGyreScholaMath.*"
|
|
647
|
+
r"|.*TeX Gyre Schola Math.*"
|
|
648
|
+
r"|.*TeXGyreTermesMath.*"
|
|
649
|
+
r"|.*TeX Gyre Termes Math.*"
|
|
650
|
+
r"|.*XCharter-Math.*"
|
|
651
|
+
r"|.*XCharter Math.*"
|
|
652
|
+
r"|.*XITSMath-Bold.*"
|
|
653
|
+
r"|.*XITS Math.*"
|
|
654
|
+
r"|.*XITSMath.*"
|
|
655
|
+
r"|.*IBMPlexMath.*"
|
|
656
|
+
r"|.*IBM Plex Math.*"
|
|
573
657
|
r")$"
|
|
574
658
|
)
|
|
575
659
|
if self.translation_config.formular_font_pattern:
|
|
576
|
-
|
|
660
|
+
broad_formula_font_pattern = self.translation_config.formular_font_pattern
|
|
577
661
|
else:
|
|
578
|
-
|
|
662
|
+
broad_formula_font_pattern = (
|
|
579
663
|
r"(CM[^RB]"
|
|
580
664
|
r"|(MS|XY|MT|BL|RM|EU|LA|RS)[A-Z]"
|
|
581
665
|
r"|LINE"
|
|
@@ -599,14 +683,16 @@ class StylesAndFormulas:
|
|
|
599
683
|
if font_name.startswith("BASE64:"):
|
|
600
684
|
font_name_bytes = base64.b64decode(font_name[7:])
|
|
601
685
|
font = font_name_bytes.split(b"+")[-1]
|
|
602
|
-
|
|
603
|
-
|
|
686
|
+
pattern_text = pattern_text.encode()
|
|
687
|
+
broad_formula_font_pattern = broad_formula_font_pattern.encode()
|
|
604
688
|
else:
|
|
605
689
|
font = font_name.split("+")[-1]
|
|
606
690
|
|
|
607
|
-
if re.match(
|
|
691
|
+
if re.match(precise_formula_font_pattern, font):
|
|
692
|
+
return True
|
|
693
|
+
elif re.match(pattern_text, font):
|
|
608
694
|
return False
|
|
609
|
-
|
|
695
|
+
elif re.match(broad_formula_font_pattern, font):
|
|
610
696
|
return True
|
|
611
697
|
|
|
612
698
|
return False
|
|
@@ -626,7 +712,7 @@ class StylesAndFormulas:
|
|
|
626
712
|
and (
|
|
627
713
|
unicodedata.category(char[0])
|
|
628
714
|
in [
|
|
629
|
-
"Lm",
|
|
715
|
+
# "Lm",
|
|
630
716
|
"Mn",
|
|
631
717
|
"Sk",
|
|
632
718
|
"Sm",
|
|
@@ -215,9 +215,7 @@ class OpenAITranslator(BaseTranslator):
|
|
|
215
215
|
**self.options,
|
|
216
216
|
messages=self.prompt(text),
|
|
217
217
|
)
|
|
218
|
-
self.
|
|
219
|
-
self.prompt_token_count.inc(response.usage.prompt_tokens)
|
|
220
|
-
self.completion_token_count.inc(response.usage.completion_tokens)
|
|
218
|
+
self.update_token_count(response)
|
|
221
219
|
return response.choices[0].message.content.strip()
|
|
222
220
|
|
|
223
221
|
def prompt(self, text):
|
|
@@ -255,11 +253,20 @@ class OpenAITranslator(BaseTranslator):
|
|
|
255
253
|
},
|
|
256
254
|
],
|
|
257
255
|
)
|
|
258
|
-
self.
|
|
259
|
-
self.prompt_token_count.inc(response.usage.prompt_tokens)
|
|
260
|
-
self.completion_token_count.inc(response.usage.completion_tokens)
|
|
256
|
+
self.update_token_count(response)
|
|
261
257
|
return response.choices[0].message.content.strip()
|
|
262
258
|
|
|
259
|
+
def update_token_count(self, response):
|
|
260
|
+
try:
|
|
261
|
+
if response.usage and response.usage.total_tokens:
|
|
262
|
+
self.token_count.inc(response.usage.total_tokens)
|
|
263
|
+
if response.usage and response.usage.prompt_tokens:
|
|
264
|
+
self.prompt_token_count.inc(response.usage.prompt_tokens)
|
|
265
|
+
if response.usage and response.usage.completion_tokens:
|
|
266
|
+
self.completion_token_count.inc(response.usage.completion_tokens)
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.exception("Error updating token count")
|
|
269
|
+
|
|
263
270
|
def get_formular_placeholder(self, placeholder_id: int):
|
|
264
271
|
return "{{v" + str(placeholder_id) + "}}"
|
|
265
272
|
return "{{" + str(placeholder_id) + "}}"
|
|
@@ -174,8 +174,24 @@ class FontMapper:
|
|
|
174
174
|
)
|
|
175
175
|
return None
|
|
176
176
|
|
|
177
|
+
def get_used_font_ids(self, il: il_version_1.Document) -> set[str]:
|
|
178
|
+
result = set()
|
|
179
|
+
for page in il.page:
|
|
180
|
+
for char in page.pdf_character:
|
|
181
|
+
if char.pdf_style and char.pdf_style.font_id:
|
|
182
|
+
result.add(char.pdf_style.font_id)
|
|
183
|
+
for para in page.pdf_paragraph:
|
|
184
|
+
for comp in para.pdf_paragraph_composition:
|
|
185
|
+
if char := comp.pdf_character:
|
|
186
|
+
if char.pdf_style and char.pdf_style.font_id:
|
|
187
|
+
result.add(char.pdf_style.font_id)
|
|
188
|
+
return result
|
|
189
|
+
|
|
177
190
|
def add_font(self, doc_zh: pymupdf.Document, il: il_version_1.Document):
|
|
178
|
-
|
|
191
|
+
used_font_ids = self.get_used_font_ids(il)
|
|
192
|
+
font_list = [
|
|
193
|
+
(k, v) for k, v in self.fontid2fontpath.items() if k in used_font_ids
|
|
194
|
+
]
|
|
179
195
|
|
|
180
196
|
font_id = {}
|
|
181
197
|
xreflen = doc_zh.xref_length()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import math
|
|
3
|
+
import re
|
|
3
4
|
|
|
4
5
|
from babeldoc.document_il import GraphicState
|
|
5
6
|
from babeldoc.document_il import il_version_1
|
|
@@ -37,6 +38,23 @@ HEIGHT_NOT_USFUL_CHAR_IN_CHAR = (
|
|
|
37
38
|
LEFT_BRACKET = ("(cid:8)", "(", "(cid:16)", "{", "[", "(cid:104)", "(cid:2)")
|
|
38
39
|
RIGHT_BRACKET = ("(cid:9)", ")", "(cid:17)", "}", "]", "(cid:105)", "(cid:3)")
|
|
39
40
|
|
|
41
|
+
BULLET_POINT_PATTERN = re.compile(
|
|
42
|
+
r"[■•⚫⬤◆◇○●◦‣⁃▪▫∗†‡¹²³⁴⁵⁶⁷⁸⁹⁰₁₂₃₄₅₆₇₈₉₀ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖᵍʳˢᵗᵘᵛʷˣʸᶻ†‡§¶※⁑⁂⁕⁎⁜⁑❧☙⁋‖‽]"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_bullet_point(char: PdfCharacter) -> bool:
|
|
47
|
+
"""Check if the character is a bullet point.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
char: The character to check
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
bool: True if the character is a bullet point
|
|
54
|
+
"""
|
|
55
|
+
is_bullet = bool(BULLET_POINT_PATTERN.match(char.char_unicode))
|
|
56
|
+
return is_bullet
|
|
57
|
+
|
|
40
58
|
|
|
41
59
|
def formular_height_ignore_char(char: PdfCharacter):
|
|
42
60
|
return (
|
|
File without changes
|
|
@@ -4,6 +4,7 @@ import hashlib
|
|
|
4
4
|
import io
|
|
5
5
|
import logging
|
|
6
6
|
import pathlib
|
|
7
|
+
import shutil
|
|
7
8
|
import threading
|
|
8
9
|
import time
|
|
9
10
|
from asyncio import CancelledError
|
|
@@ -11,6 +12,7 @@ from pathlib import Path
|
|
|
11
12
|
from typing import Any
|
|
12
13
|
from typing import BinaryIO
|
|
13
14
|
|
|
15
|
+
import pymupdf
|
|
14
16
|
from pdfminer.pdfdocument import PDFDocument
|
|
15
17
|
from pdfminer.pdfinterp import PDFResourceManager
|
|
16
18
|
from pdfminer.pdfpage import PDFPage
|
|
@@ -26,6 +28,7 @@ from babeldoc.document_il import il_version_1
|
|
|
26
28
|
from babeldoc.document_il.backend.pdf_creater import SAVE_PDF_STAGE_NAME
|
|
27
29
|
from babeldoc.document_il.backend.pdf_creater import SUBSET_FONT_STAGE_NAME
|
|
28
30
|
from babeldoc.document_il.backend.pdf_creater import PDFCreater
|
|
31
|
+
from babeldoc.document_il.backend.pdf_creater import reproduce_cmap
|
|
29
32
|
from babeldoc.document_il.frontend.il_creater import ILCreater
|
|
30
33
|
from babeldoc.document_il.midend.add_debug_information import AddDebugInformation
|
|
31
34
|
from babeldoc.document_il.midend.detect_scanned_file import DetectScannedFile
|
|
@@ -75,6 +78,26 @@ resfont_map = {
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
|
|
81
|
+
def fix_cmap(translate_result: TranslateResult, translate_config: TranslationConfig):
|
|
82
|
+
processed = []
|
|
83
|
+
for attr in (
|
|
84
|
+
"mono_pdf_path",
|
|
85
|
+
"dual_pdf_path",
|
|
86
|
+
"no_watermark_mono_pdf_path",
|
|
87
|
+
"no_watermark_dual_pdf_path",
|
|
88
|
+
):
|
|
89
|
+
path = getattr(translate_result, attr)
|
|
90
|
+
if path in processed:
|
|
91
|
+
continue
|
|
92
|
+
processed.append(path)
|
|
93
|
+
|
|
94
|
+
temp_path = translate_config.get_working_file_path(f"{path.stem}.cmap.pdf")
|
|
95
|
+
pdf = pymupdf.open(path)
|
|
96
|
+
reproduce_cmap(pdf)
|
|
97
|
+
pdf.save(temp_path)
|
|
98
|
+
shutil.move(temp_path, path)
|
|
99
|
+
|
|
100
|
+
|
|
78
101
|
def verify_file_hash(file_path: str, expected_hash: str) -> bool:
|
|
79
102
|
"""Verify the SHA256 hash of a file."""
|
|
80
103
|
sha256_hash = hashlib.sha256()
|
|
@@ -514,11 +537,18 @@ def do_translate(
|
|
|
514
537
|
)
|
|
515
538
|
result.original_pdf_path = translation_config.input_file
|
|
516
539
|
result.peak_memory_usage = peak_memory_usage
|
|
540
|
+
|
|
541
|
+
# should fix macOS preview compatibility
|
|
542
|
+
# Although the issue of Windows Edge
|
|
543
|
+
# not being able to copy translated text can be fixed,
|
|
544
|
+
# the macOS preview is broken
|
|
545
|
+
# fix_cmap(result, translation_config)
|
|
517
546
|
pm.translate_done(result)
|
|
518
547
|
return result
|
|
519
548
|
|
|
520
549
|
except Exception as e:
|
|
521
550
|
logger.exception(f"translate error: {e}")
|
|
551
|
+
pm.disable = False
|
|
522
552
|
pm.translate_error(e)
|
|
523
553
|
raise
|
|
524
554
|
finally:
|
|
@@ -530,10 +560,16 @@ def do_translate(
|
|
|
530
560
|
def fix_media_box(doc: Document) -> None:
|
|
531
561
|
mediabox_data = {}
|
|
532
562
|
for page in doc:
|
|
563
|
+
mediabox = doc.xref_get_key(page.xref, "MediaBox")
|
|
564
|
+
|
|
565
|
+
# Some PDF pages do not have a mediabox
|
|
566
|
+
if mediabox[0] == "null":
|
|
567
|
+
mediabox = ("array", "[0 0 612 792]")
|
|
568
|
+
doc.xref_set_key(page.xref, "MediaBox", mediabox[1])
|
|
533
569
|
if page.mediabox.x0 != 0 or page.mediabox.y0 != 0:
|
|
534
570
|
x1 = page.mediabox.x1
|
|
535
571
|
y1 = page.mediabox.y1
|
|
536
|
-
mediabox_data[page.number] =
|
|
572
|
+
mediabox_data[page.number] = mediabox
|
|
537
573
|
doc.xref_set_key(page.xref, "MediaBox", f"[0 0 {x1} {y1}]")
|
|
538
574
|
return mediabox_data
|
|
539
575
|
|
|
@@ -104,6 +104,7 @@ def extract_fonts_from_paragraph(
|
|
|
104
104
|
same_style_unicode = comp["pdf_same_style_unicode_characters"]
|
|
105
105
|
if (
|
|
106
106
|
"pdf_style" in same_style_unicode
|
|
107
|
+
and same_style_unicode["pdf_style"] is not None
|
|
107
108
|
and "font_id" in same_style_unicode["pdf_style"]
|
|
108
109
|
):
|
|
109
110
|
font_id = same_style_unicode["pdf_style"]["font_id"]
|
|
@@ -72,8 +72,14 @@ for each_font in fonts:
|
|
|
72
72
|
font_name,
|
|
73
73
|
"[bold red]Formula Font[/bold red]",
|
|
74
74
|
)
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
else:
|
|
76
|
+
table.add_row(
|
|
77
|
+
str(page_index),
|
|
78
|
+
str(paragraph_index),
|
|
79
|
+
str(font_debug_id),
|
|
80
|
+
font_name,
|
|
81
|
+
"[bold blue]Non-Formula Font[/bold blue]",
|
|
82
|
+
)
|
|
77
83
|
|
|
78
84
|
# Print table
|
|
79
85
|
console.print(table)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "BabelDOC"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.22"
|
|
4
4
|
description = "Yet Another Document Translator"
|
|
5
5
|
license = "AGPL-3.0"
|
|
6
6
|
readme = "README.md"
|
|
@@ -48,6 +48,7 @@ dependencies = [
|
|
|
48
48
|
[project.optional-dependencies]
|
|
49
49
|
directml = ["onnxruntime-directml>=1.16.1"]
|
|
50
50
|
cuda = ["onnxruntime-gpu>=1.16.1"]
|
|
51
|
+
memray = ["memray>=1.17.1"]
|
|
51
52
|
|
|
52
53
|
[project.urls]
|
|
53
54
|
Homepage = "https://github.com/funstory-ai/BabelDOC"
|
|
@@ -141,7 +142,6 @@ dev = [
|
|
|
141
142
|
"pygments>=2.19.1",
|
|
142
143
|
"ruff>=0.9.2",
|
|
143
144
|
"pytest>=8.3.4",
|
|
144
|
-
"memray>=1.17.1",
|
|
145
145
|
]
|
|
146
146
|
|
|
147
147
|
[tool.pytest.ini_options]
|
|
@@ -149,7 +149,7 @@ pythonpath = [".", "src"]
|
|
|
149
149
|
testpaths = ["tests"]
|
|
150
150
|
|
|
151
151
|
[bumpver]
|
|
152
|
-
current_version = "0.3.
|
|
152
|
+
current_version = "0.3.22"
|
|
153
153
|
version_pattern = "MAJOR.MINOR.PATCH[.PYTAGNUM]"
|
|
154
154
|
|
|
155
155
|
[bumpver.file_patterns]
|
|
@@ -48,7 +48,7 @@ wheels = [
|
|
|
48
48
|
|
|
49
49
|
[[package]]
|
|
50
50
|
name = "babeldoc"
|
|
51
|
-
version = "0.3.
|
|
51
|
+
version = "0.3.22"
|
|
52
52
|
source = { editable = "." }
|
|
53
53
|
dependencies = [
|
|
54
54
|
{ name = "bitstring" },
|
|
@@ -86,13 +86,15 @@ cuda = [
|
|
|
86
86
|
directml = [
|
|
87
87
|
{ name = "onnxruntime-directml" },
|
|
88
88
|
]
|
|
89
|
+
memray = [
|
|
90
|
+
{ name = "memray" },
|
|
91
|
+
]
|
|
89
92
|
|
|
90
93
|
[package.dev-dependencies]
|
|
91
94
|
dev = [
|
|
92
95
|
{ name = "bumpver" },
|
|
93
96
|
{ name = "markdown-callouts" },
|
|
94
97
|
{ name = "markdown-include" },
|
|
95
|
-
{ name = "memray" },
|
|
96
98
|
{ name = "mkdocs-git-authors-plugin" },
|
|
97
99
|
{ name = "mkdocs-git-committers-plugin-2" },
|
|
98
100
|
{ name = "mkdocs-git-revision-date-localized-plugin" },
|
|
@@ -110,6 +112,7 @@ requires-dist = [
|
|
|
110
112
|
{ name = "freetype-py", specifier = ">=2.5.1" },
|
|
111
113
|
{ name = "httpx", extras = ["socks"], specifier = ">=0.27.0" },
|
|
112
114
|
{ name = "huggingface-hub", specifier = ">=0.27.0" },
|
|
115
|
+
{ name = "memray", marker = "extra == 'memray'", specifier = ">=1.17.1" },
|
|
113
116
|
{ name = "msgpack", specifier = ">=1.1.0" },
|
|
114
117
|
{ name = "numpy", specifier = ">=2.0.2" },
|
|
115
118
|
{ name = "onnx", specifier = ">=1.17.0" },
|
|
@@ -134,14 +137,13 @@ requires-dist = [
|
|
|
134
137
|
{ name = "tqdm", specifier = ">=4.67.1" },
|
|
135
138
|
{ name = "xsdata", extras = ["cli", "lxml", "soap"], specifier = ">=24.12" },
|
|
136
139
|
]
|
|
137
|
-
provides-extras = ["directml", "cuda"]
|
|
140
|
+
provides-extras = ["directml", "cuda", "memray"]
|
|
138
141
|
|
|
139
142
|
[package.metadata.requires-dev]
|
|
140
143
|
dev = [
|
|
141
144
|
{ name = "bumpver", specifier = ">=2024.1130" },
|
|
142
145
|
{ name = "markdown-callouts", specifier = ">=0.4.0" },
|
|
143
146
|
{ name = "markdown-include", specifier = ">=0.8.1" },
|
|
144
|
-
{ name = "memray", specifier = ">=1.17.1" },
|
|
145
147
|
{ name = "mkdocs-git-authors-plugin", specifier = ">=0.9.2" },
|
|
146
148
|
{ name = "mkdocs-git-committers-plugin-2", specifier = ">=2.5.0" },
|
|
147
149
|
{ name = "mkdocs-git-revision-date-localized-plugin", specifier = ">=1.3.0" },
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.20"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.20 → babeldoc-0.3.22}/babeldoc/document_il/utils/priority_thread_pool_executor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.20 → babeldoc-0.3.22}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.20 → babeldoc-0.3.22}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-big-logo-with-transparent-background.png
RENAMED
|
File without changes
|
{babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-big-logo-with-transparent-background.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-small-logo-with-transparent-background.png
RENAMED
|
File without changes
|
{babeldoc-0.3.20 → babeldoc-0.3.22}/docs/images/babeldoc-small-logo-with-transparent-background.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|