BabelDOC 0.2.23__tar.gz → 0.2.25__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.
Files changed (97) hide show
  1. {babeldoc-0.2.23 → babeldoc-0.2.25}/PKG-INFO +1 -1
  2. babeldoc-0.2.25/babeldoc/__init__.py +1 -0
  3. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/const.py +1 -1
  4. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/converter.py +6 -1
  5. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/backend/pdf_creater.py +238 -213
  6. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/frontend/il_creater.py +15 -5
  7. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/utils/fontmap.py +5 -0
  8. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/main.py +1 -1
  9. {babeldoc-0.2.23 → babeldoc-0.2.25}/pyproject.toml +2 -2
  10. {babeldoc-0.2.23 → babeldoc-0.2.25}/uv.lock +149 -126
  11. babeldoc-0.2.23/babeldoc/__init__.py +0 -1
  12. {babeldoc-0.2.23 → babeldoc-0.2.25}/.cursorignore +0 -0
  13. {babeldoc-0.2.23 → babeldoc-0.2.25}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
  14. {babeldoc-0.2.23 → babeldoc-0.2.25}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
  15. {babeldoc-0.2.23 → babeldoc-0.2.25}/.github/dependabot.yml +0 -0
  16. {babeldoc-0.2.23 → babeldoc-0.2.25}/.github/labels.yml +0 -0
  17. {babeldoc-0.2.23 → babeldoc-0.2.25}/.github/release-drafter.yml +0 -0
  18. {babeldoc-0.2.23 → babeldoc-0.2.25}/.github/workflows/codeql.yml +0 -0
  19. {babeldoc-0.2.23 → babeldoc-0.2.25}/.github/workflows/docs.yml +0 -0
  20. {babeldoc-0.2.23 → babeldoc-0.2.25}/.github/workflows/labeler.yml +0 -0
  21. {babeldoc-0.2.23 → babeldoc-0.2.25}/.github/workflows/publish-to-pypi.yml +0 -0
  22. {babeldoc-0.2.23 → babeldoc-0.2.25}/.github/workflows/test.yml +0 -0
  23. {babeldoc-0.2.23 → babeldoc-0.2.25}/.gitignore +0 -0
  24. {babeldoc-0.2.23 → babeldoc-0.2.25}/.pre-commit-config.yaml +0 -0
  25. {babeldoc-0.2.23 → babeldoc-0.2.25}/LICENSE +0 -0
  26. {babeldoc-0.2.23 → babeldoc-0.2.25}/README.md +0 -0
  27. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/assets/assets.py +0 -0
  28. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/assets/embedding_assets_metadata.py +0 -0
  29. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/asynchronize/__init__.py +0 -0
  30. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/__init__.py +0 -0
  31. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
  32. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/backend/__init__.py +0 -0
  33. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/frontend/__init__.py +0 -0
  34. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/il_version_1.py +0 -0
  35. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/il_version_1.rnc +0 -0
  36. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/il_version_1.rng +0 -0
  37. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/il_version_1.xsd +0 -0
  38. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/midend/__init__.py +0 -0
  39. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/midend/add_debug_information.py +0 -0
  40. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/midend/detect_scanned_file.py +0 -0
  41. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/midend/il_translator.py +0 -0
  42. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/midend/il_translator_llm_only.py +0 -0
  43. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/midend/layout_parser.py +0 -0
  44. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/midend/paragraph_finder.py +0 -0
  45. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/midend/remove_descent.py +0 -0
  46. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/midend/styles_and_formulas.py +0 -0
  47. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/midend/table_parser.py +0 -0
  48. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/midend/typesetting.py +0 -0
  49. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/translator/__init__.py +0 -0
  50. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/translator/cache.py +0 -0
  51. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/translator/translator.py +0 -0
  52. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/utils/atomic_integer.py +0 -0
  53. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/utils/layout_helper.py +0 -0
  54. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/utils/priority_thread_pool_executor.py +0 -0
  55. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/utils/style_helper.py +0 -0
  56. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/document_il/xml_converter.py +0 -0
  57. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/docvision/README.md +0 -0
  58. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/docvision/__init__.py +0 -0
  59. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/docvision/doclayout.py +0 -0
  60. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/docvision/rpc_doclayout.py +0 -0
  61. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/docvision/table_detection/rapidocr.py +0 -0
  62. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/high_level.py +0 -0
  63. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/pdfinterp.py +0 -0
  64. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/progress_monitor.py +0 -0
  65. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/result_merger.py +0 -0
  66. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/split_manager.py +0 -0
  67. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/tools/generate_font_metadata.py +0 -0
  68. {babeldoc-0.2.23 → babeldoc-0.2.25}/babeldoc/translation_config.py +0 -0
  69. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/CODE_OF_CONDUCT.md +0 -0
  70. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/CONTRIBUTING.md +0 -0
  71. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/CONTRIBUTOR_REWARD.md +0 -0
  72. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
  73. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
  74. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
  75. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
  76. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
  77. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/ImplementationDetails/README.md +0 -0
  78. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
  79. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
  80. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/README.md +0 -0
  81. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/deploy.sh +0 -0
  82. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/images/babeldoc-banner.png +0 -0
  83. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
  84. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
  85. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
  86. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
  87. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/images/babeldoc-big-logo.png +0 -0
  88. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/images/babeldoc-contributor_reward_example.png +0 -0
  89. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/images/babeldoc-preview.gif +0 -0
  90. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
  91. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
  92. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/images/babeldoc-small-logo.png +0 -0
  93. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/index.md +0 -0
  94. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/intro-to-pdf-object.md +0 -0
  95. {babeldoc-0.2.23 → babeldoc-0.2.25}/docs/requirements.txt +0 -0
  96. {babeldoc-0.2.23 → babeldoc-0.2.25}/mkdocs.yml +0 -0
  97. {babeldoc-0.2.23 → babeldoc-0.2.25}/tests/test_translation_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BabelDOC
3
- Version: 0.2.23
3
+ Version: 0.2.25
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
@@ -0,0 +1 @@
1
+ __version__ = "0.2.25"
@@ -3,7 +3,7 @@ import shutil
3
3
  import subprocess
4
4
  from pathlib import Path
5
5
 
6
- __version__ = "0.2.23"
6
+ __version__ = "0.2.25"
7
7
 
8
8
  CACHE_FOLDER = Path.home() / ".cache" / "babeldoc"
9
9
 
@@ -282,7 +282,12 @@ class TranslateConverter(PDFConverterEx):
282
282
  # A. 原文档解析
283
283
  for child in ltpage:
284
284
  if isinstance(child, LTChar):
285
- self.il_creater.on_lt_char(child)
285
+ try:
286
+ self.il_creater.on_lt_char(child)
287
+ except Exception:
288
+ log.exception(
289
+ 'Error processing LTChar',
290
+ )
286
291
  continue
287
292
  cur_v = False
288
293
  layout = self.layout[ltpage.pageid]
@@ -158,27 +158,30 @@ class PDFCreater:
158
158
  return self.get_xobj_available_fonts(page_xref_id, pdf)
159
159
 
160
160
  def get_xobj_available_fonts(self, page_xref_id, pdf):
161
- resources_type, r_id = pdf.xref_get_key(page_xref_id, "Resources")
162
- if resources_type == "xref":
163
- resource_xref_id = re.search("(\\d+) 0 R", r_id).group(1)
164
- r_id = pdf.xref_object(int(resource_xref_id))
165
- resources_type = "dict"
166
- if resources_type == "dict":
167
- xref_id = re.search("/Font (\\d+) 0 R", r_id)
168
- if xref_id is not None:
169
- xref_id = xref_id.group(1)
170
- font_dict = pdf.xref_object(int(xref_id))
161
+ try:
162
+ resources_type, r_id = pdf.xref_get_key(page_xref_id, "Resources")
163
+ if resources_type == "xref":
164
+ resource_xref_id = re.search("(\\d+) 0 R", r_id).group(1)
165
+ r_id = pdf.xref_object(int(resource_xref_id))
166
+ resources_type = "dict"
167
+ if resources_type == "dict":
168
+ xref_id = re.search("/Font (\\d+) 0 R", r_id)
169
+ if xref_id is not None:
170
+ xref_id = xref_id.group(1)
171
+ font_dict = pdf.xref_object(int(xref_id))
172
+ else:
173
+ search = re.search("/Font *<<(.+?)>>", r_id.replace("\n", " "))
174
+ if search is None:
175
+ # Have resources but no fonts
176
+ return set()
177
+ font_dict = search.group(1)
171
178
  else:
172
- search = re.search("/Font *<<(.+?)>>", r_id.replace("\n", " "))
173
- if search is None:
174
- # Have resources but no fonts
175
- return set()
176
- font_dict = search.group(1)
177
- else:
178
- r_id = int(r_id.split(" ")[0])
179
- _, font_dict = pdf.xref_get_key(r_id, "Font")
180
- fonts = re.findall("/([^ ]+?) ", font_dict)
181
- return set(fonts)
179
+ r_id = int(r_id.split(" ")[0])
180
+ _, font_dict = pdf.xref_get_key(r_id, "Font")
181
+ fonts = re.findall("/([^ ]+?) ", font_dict)
182
+ return set(fonts)
183
+ except Exception:
184
+ return set()
182
185
 
183
186
  def _debug_render_rectangle(
184
187
  self,
@@ -633,213 +636,235 @@ class PDFCreater:
633
636
 
634
637
  return False
635
638
 
636
- def write(self, translation_config: TranslationConfig) -> TranslateResult:
637
- basename = Path(translation_config.input_file).stem
638
- debug_suffix = ".debug" if translation_config.debug else ""
639
- if translation_config.watermark_output_mode != WatermarkOutputMode.Watermarked:
640
- debug_suffix += ".no_watermark"
641
- mono_out_path = translation_config.get_output_file_path(
642
- f"{basename}{debug_suffix}.{translation_config.lang_out}.mono.pdf",
643
- )
644
- pdf = pymupdf.open(self.original_pdf_path)
645
- self.font_mapper.add_font(pdf, self.docs)
646
- with self.translation_config.progress_monitor.stage_start(
647
- self.stage_name,
648
- len(self.docs.page),
649
- ) as pbar:
650
- for page in self.docs.page:
651
- translation_config.raise_if_cancelled()
652
- xobj_available_fonts = {}
653
- xobj_draw_ops = {}
654
- xobj_encoding_length_map = {}
655
- available_font_list = self.get_available_font_list(pdf, page)
656
-
657
- for xobj in page.pdf_xobject:
658
- xobj_available_fonts[xobj.xobj_id] = available_font_list.copy()
659
- try:
660
- xobj_available_fonts[xobj.xobj_id].update(
661
- self.get_xobj_available_fonts(xobj.xref_id, pdf),
662
- )
663
- except Exception:
664
- pass
665
- xobj_encoding_length_map[xobj.xobj_id] = {
666
- f.font_id: f.encoding_length for f in xobj.pdf_font
639
+ def write(
640
+ self, translation_config: TranslationConfig, check_font_exists: bool = False
641
+ ) -> TranslateResult:
642
+ try:
643
+ basename = Path(translation_config.input_file).stem
644
+ debug_suffix = ".debug" if translation_config.debug else ""
645
+ if (
646
+ translation_config.watermark_output_mode
647
+ != WatermarkOutputMode.Watermarked
648
+ ):
649
+ debug_suffix += ".no_watermark"
650
+ mono_out_path = translation_config.get_output_file_path(
651
+ f"{basename}{debug_suffix}.{translation_config.lang_out}.mono.pdf",
652
+ )
653
+ pdf = pymupdf.open(self.original_pdf_path)
654
+ self.font_mapper.add_font(pdf, self.docs)
655
+ with self.translation_config.progress_monitor.stage_start(
656
+ self.stage_name,
657
+ len(self.docs.page),
658
+ ) as pbar:
659
+ for page in self.docs.page:
660
+ translation_config.raise_if_cancelled()
661
+ xobj_available_fonts = {}
662
+ xobj_draw_ops = {}
663
+ xobj_encoding_length_map = {}
664
+ available_font_list = self.get_available_font_list(pdf, page)
665
+
666
+ for xobj in page.pdf_xobject:
667
+ xobj_available_fonts[xobj.xobj_id] = available_font_list.copy()
668
+ try:
669
+ xobj_available_fonts[xobj.xobj_id].update(
670
+ self.get_xobj_available_fonts(xobj.xref_id, pdf),
671
+ )
672
+ except Exception:
673
+ pass
674
+ xobj_encoding_length_map[xobj.xobj_id] = {
675
+ f.font_id: f.encoding_length for f in xobj.pdf_font
676
+ }
677
+ xobj_op = BitStream()
678
+ xobj_op.append(xobj.base_operations.value.encode())
679
+ xobj_draw_ops[xobj.xobj_id] = xobj_op
680
+ page_encoding_length_map = {
681
+ f.font_id: f.encoding_length for f in page.pdf_font
667
682
  }
668
- xobj_op = BitStream()
669
- xobj_op.append(xobj.base_operations.value.encode())
670
- xobj_draw_ops[xobj.xobj_id] = xobj_op
671
- page_encoding_length_map = {
672
- f.font_id: f.encoding_length for f in page.pdf_font
673
- }
674
- page_op = BitStream()
675
- # q {ops_base}Q 1 0 0 1 {x0} {y0} cm {ops_new}
676
- # page_op.append(b"q ")
677
- page_op.append(page.base_operations.value.encode())
678
- page_op.append(b" \n")
679
- # page_op.append(b" Q ")
680
- # page_op.append(
681
- # f"q Q 1 0 0 1 {page.cropbox.box.x} {page.cropbox.box.y} cm \n".encode(),
682
- # )
683
- # 收集所有字符
684
- chars = []
685
- # 首先添加页面级别的字符
686
- if page.pdf_character:
687
- chars.extend(page.pdf_character)
688
- # 然后添加段落中的字符
689
- for paragraph in page.pdf_paragraph:
690
- chars.extend(self.render_paragraph_to_char(paragraph))
691
-
692
- # 渲染所有字符
693
- for char in chars:
694
- if char.char_unicode == "\n":
695
- continue
696
- if char.pdf_character_id is None:
697
- # dummy char
698
- continue
699
- char_size = char.pdf_style.font_size
700
- font_id = char.pdf_style.font_id
701
- if char.xobj_id in xobj_available_fonts:
702
- if font_id not in xobj_available_fonts[char.xobj_id]:
683
+ page_op = BitStream()
684
+ # q {ops_base}Q 1 0 0 1 {x0} {y0} cm {ops_new}
685
+ # page_op.append(b"q ")
686
+ page_op.append(page.base_operations.value.encode())
687
+ page_op.append(b" \n")
688
+ # page_op.append(b" Q ")
689
+ # page_op.append(
690
+ # f"q Q 1 0 0 1 {page.cropbox.box.x} {page.cropbox.box.y} cm \n".encode(),
691
+ # )
692
+ # 收集所有字符
693
+ chars = []
694
+ # 首先添加页面级别的字符
695
+ if page.pdf_character:
696
+ chars.extend(page.pdf_character)
697
+ # 然后添加段落中的字符
698
+ for paragraph in page.pdf_paragraph:
699
+ chars.extend(self.render_paragraph_to_char(paragraph))
700
+
701
+ # 渲染所有字符
702
+ for char in chars:
703
+ if char.char_unicode == "\n":
703
704
  continue
704
- draw_op = xobj_draw_ops[char.xobj_id]
705
- encoding_length_map = xobj_encoding_length_map[char.xobj_id]
706
- else:
707
- if font_id not in available_font_list:
705
+ if char.pdf_character_id is None:
706
+ # dummy char
708
707
  continue
709
- draw_op = page_op
710
- encoding_length_map = page_encoding_length_map
711
-
712
- draw_op.append(b"q ")
713
- self.render_graphic_state(draw_op, char.pdf_style.graphic_state)
714
- if char.vertical:
715
- draw_op.append(
716
- f"BT /{font_id} {char_size:f} Tf 0 1 -1 0 {char.box.x2:f} {char.box.y:f} Tm ".encode(),
717
- )
718
- else:
708
+ char_size = char.pdf_style.font_size
709
+ font_id = char.pdf_style.font_id
710
+ if char.xobj_id in xobj_available_fonts:
711
+ if (
712
+ check_font_exists
713
+ and font_id not in xobj_available_fonts[char.xobj_id]
714
+ ):
715
+ continue
716
+ draw_op = xobj_draw_ops[char.xobj_id]
717
+ encoding_length_map = xobj_encoding_length_map[char.xobj_id]
718
+ else:
719
+ if check_font_exists and font_id not in available_font_list:
720
+ continue
721
+ draw_op = page_op
722
+ encoding_length_map = page_encoding_length_map
723
+
724
+ draw_op.append(b"q ")
725
+ self.render_graphic_state(draw_op, char.pdf_style.graphic_state)
726
+ if char.vertical:
727
+ draw_op.append(
728
+ f"BT /{font_id} {char_size:f} Tf 0 1 -1 0 {char.box.x2:f} {char.box.y:f} Tm ".encode(),
729
+ )
730
+ else:
731
+ draw_op.append(
732
+ f"BT /{font_id} {char_size:f} Tf 1 0 0 1 {char.box.x:f} {char.box.y:f} Tm ".encode(),
733
+ )
734
+
735
+ encoding_length = encoding_length_map.get(font_id, None)
736
+ if encoding_length is None:
737
+ logger.debug(
738
+ f"Font {font_id} not found in encoding length map for page {page.page_number}"
739
+ )
740
+ continue
741
+ # pdf32000-2008 page14:
742
+ # As hexadecimal data enclosed in angle brackets < >
743
+ # see 7.3.4.3, "Hexadecimal Strings."
719
744
  draw_op.append(
720
- f"BT /{font_id} {char_size:f} Tf 1 0 0 1 {char.box.x:f} {char.box.y:f} Tm ".encode(),
745
+ f"<{char.pdf_character_id:0{encoding_length * 2}x}>".upper().encode(),
721
746
  )
722
747
 
723
- encoding_length = encoding_length_map[font_id]
724
- # pdf32000-2008 page14:
725
- # As hexadecimal data enclosed in angle brackets < >
726
- # see 7.3.4.3, "Hexadecimal Strings."
727
- draw_op.append(
728
- f"<{char.pdf_character_id:0{encoding_length * 2}x}>".upper().encode(),
748
+ draw_op.append(b" Tj ET Q \n")
749
+ for xobj in page.pdf_xobject:
750
+ draw_op = xobj_draw_ops[xobj.xobj_id]
751
+ try:
752
+ pdf.update_stream(xobj.xref_id, draw_op.tobytes())
753
+ except Exception:
754
+ logger.warning(
755
+ f"update xref {xobj.xref_id} stream fail, continue"
756
+ )
757
+ # pdf.update_stream(xobj.xref_id, b'')
758
+ for rect in page.pdf_rectangle:
759
+ self._debug_render_rectangle(page_op, rect)
760
+ draw_op = page_op
761
+ op_container = pdf.get_new_xref()
762
+ # Since this is a draw instruction container,
763
+ # no additional information is needed
764
+ pdf.update_object(op_container, "<<>>")
765
+ pdf.update_stream(op_container, draw_op.tobytes())
766
+ pdf[page.page_number].set_contents(op_container)
767
+ pbar.advance()
768
+ translation_config.raise_if_cancelled()
769
+ with self.translation_config.progress_monitor.stage_start(
770
+ SUBSET_FONT_STAGE_NAME,
771
+ 1,
772
+ ) as pbar:
773
+ if not translation_config.skip_clean:
774
+ pdf = self.subset_fonts_in_subprocess(
775
+ pdf, translation_config, tag="mono"
729
776
  )
730
777
 
731
- draw_op.append(b" Tj ET Q \n")
732
- for xobj in page.pdf_xobject:
733
- draw_op = xobj_draw_ops[xobj.xobj_id]
734
- try:
735
- pdf.update_stream(xobj.xref_id, draw_op.tobytes())
736
- except Exception:
737
- logger.warning(
738
- f"update xref {xobj.xref_id} stream fail, continue"
739
- )
740
- # pdf.update_stream(xobj.xref_id, b'')
741
- for rect in page.pdf_rectangle:
742
- self._debug_render_rectangle(page_op, rect)
743
- draw_op = page_op
744
- op_container = pdf.get_new_xref()
745
- # Since this is a draw instruction container,
746
- # no additional information is needed
747
- pdf.update_object(op_container, "<<>>")
748
- pdf.update_stream(op_container, draw_op.tobytes())
749
- pdf[page.page_number].set_contents(op_container)
750
778
  pbar.advance()
751
- translation_config.raise_if_cancelled()
752
- with self.translation_config.progress_monitor.stage_start(
753
- SUBSET_FONT_STAGE_NAME,
754
- 1,
755
- ) as pbar:
756
- if not translation_config.skip_clean:
757
- pdf = self.subset_fonts_in_subprocess(
758
- pdf, translation_config, tag="mono"
759
- )
760
-
761
- pbar.advance()
762
- with self.translation_config.progress_monitor.stage_start(
763
- SAVE_PDF_STAGE_NAME,
764
- 2,
765
- ) as pbar:
766
- if not translation_config.no_mono:
767
- if translation_config.debug:
779
+ with self.translation_config.progress_monitor.stage_start(
780
+ SAVE_PDF_STAGE_NAME,
781
+ 2,
782
+ ) as pbar:
783
+ if not translation_config.no_mono:
784
+ if translation_config.debug:
785
+ translation_config.raise_if_cancelled()
786
+ pdf.save(
787
+ f"{mono_out_path}.decompressed.pdf",
788
+ expand=True,
789
+ pretty=True,
790
+ )
768
791
  translation_config.raise_if_cancelled()
769
- pdf.save(
770
- f"{mono_out_path}.decompressed.pdf",
771
- expand=True,
772
- pretty=True,
773
- )
774
- translation_config.raise_if_cancelled()
775
- self.save_pdf_with_timeout(
776
- pdf,
777
- mono_out_path,
778
- translation_config,
779
- garbage=1,
780
- deflate=True,
781
- clean=not translation_config.skip_clean,
782
- deflate_fonts=True,
783
- linear=False,
784
- tag="mono",
785
- )
786
- pbar.advance()
787
- dual_out_path = None
788
- if not translation_config.no_dual:
789
- dual_out_path = translation_config.get_output_file_path(
790
- f"{basename}{debug_suffix}.{translation_config.lang_out}.dual.pdf",
791
- )
792
- translation_config.raise_if_cancelled()
793
- original_pdf = pymupdf.open(self.original_pdf_path)
794
- translated_pdf = pdf
795
-
796
- # Choose between alternating pages and side-by-side format
797
- # Default to side-by-side if not specified
798
- use_alternating_pages = translation_config.use_alternating_pages_dual
799
-
800
- if use_alternating_pages:
801
- # Create a dual PDF with alternating pages (original and translation)
802
- dual = self.create_alternating_pages_dual_pdf(
803
- self.original_pdf_path,
804
- translated_pdf,
792
+ self.save_pdf_with_timeout(
793
+ pdf,
794
+ mono_out_path,
805
795
  translation_config,
796
+ garbage=1,
797
+ deflate=True,
798
+ clean=not translation_config.skip_clean,
799
+ deflate_fonts=True,
800
+ linear=False,
801
+ tag="mono",
806
802
  )
807
- else:
808
- # Create a dual PDF with side-by-side pages (original and translation)
809
- dual = self.create_side_by_side_dual_pdf(
810
- original_pdf,
811
- translated_pdf,
812
- dual_out_path,
813
- translation_config,
803
+ pbar.advance()
804
+ dual_out_path = None
805
+ if not translation_config.no_dual:
806
+ dual_out_path = translation_config.get_output_file_path(
807
+ f"{basename}{debug_suffix}.{translation_config.lang_out}.dual.pdf",
814
808
  )
815
-
816
- if translation_config.debug:
817
809
  translation_config.raise_if_cancelled()
818
- try:
819
- dual = self.write_debug_info(dual, translation_config)
820
- except Exception:
821
- logger.warning(
822
- "Failed to write debug info to dual PDF",
823
- exc_info=True,
810
+ original_pdf = pymupdf.open(self.original_pdf_path)
811
+ translated_pdf = pdf
812
+
813
+ # Choose between alternating pages and side-by-side format
814
+ # Default to side-by-side if not specified
815
+ use_alternating_pages = (
816
+ translation_config.use_alternating_pages_dual
817
+ )
818
+
819
+ if use_alternating_pages:
820
+ # Create a dual PDF with alternating pages (original and translation)
821
+ dual = self.create_alternating_pages_dual_pdf(
822
+ self.original_pdf_path,
823
+ translated_pdf,
824
+ translation_config,
825
+ )
826
+ else:
827
+ # Create a dual PDF with side-by-side pages (original and translation)
828
+ dual = self.create_side_by_side_dual_pdf(
829
+ original_pdf,
830
+ translated_pdf,
831
+ dual_out_path,
832
+ translation_config,
824
833
  )
825
834
 
826
- self.save_pdf_with_timeout(
827
- dual,
828
- dual_out_path,
829
- translation_config,
830
- garbage=1,
831
- deflate=True,
832
- clean=not translation_config.skip_clean,
833
- deflate_fonts=True,
834
- linear=False,
835
- tag="dual",
836
- )
837
- if translation_config.debug:
838
- translation_config.raise_if_cancelled()
839
- dual.save(
840
- f"{dual_out_path}.decompressed.pdf",
841
- expand=True,
842
- pretty=True,
835
+ if translation_config.debug:
836
+ translation_config.raise_if_cancelled()
837
+ try:
838
+ dual = self.write_debug_info(dual, translation_config)
839
+ except Exception:
840
+ logger.warning(
841
+ "Failed to write debug info to dual PDF",
842
+ exc_info=True,
843
+ )
844
+
845
+ self.save_pdf_with_timeout(
846
+ dual,
847
+ dual_out_path,
848
+ translation_config,
849
+ garbage=1,
850
+ deflate=True,
851
+ clean=not translation_config.skip_clean,
852
+ deflate_fonts=True,
853
+ linear=False,
854
+ tag="dual",
843
855
  )
844
- pbar.advance()
845
- return TranslateResult(mono_out_path, dual_out_path)
856
+ if translation_config.debug:
857
+ translation_config.raise_if_cancelled()
858
+ dual.save(
859
+ f"{dual_out_path}.decompressed.pdf",
860
+ expand=True,
861
+ pretty=True,
862
+ )
863
+ pbar.advance()
864
+ return TranslateResult(mono_out_path, dual_out_path)
865
+ except Exception:
866
+ logger.exception(
867
+ "Failed to create PDF: %s",
868
+ translation_config.input_file,
869
+ )
870
+ return self.write(translation_config, True)
@@ -497,11 +497,21 @@ class ILCreater:
497
497
 
498
498
  char_id = char.cid
499
499
 
500
- if font_bounding_box_map := self.current_page_font_char_bounding_box_map.get(
501
- self.xobj_id, self.current_page_font_char_bounding_box_map
502
- ).get(font.font_id):
503
- char_bounding_box = font_bounding_box_map.get(char_id, None)
504
- else:
500
+ try:
501
+ if (
502
+ font_bounding_box_map
503
+ := self.current_page_font_char_bounding_box_map.get(
504
+ self.xobj_id, self.current_page_font_char_bounding_box_map
505
+ ).get(font.font_id)
506
+ ):
507
+ char_bounding_box = font_bounding_box_map.get(char_id, None)
508
+ else:
509
+ char_bounding_box = None
510
+ except Exception:
511
+ logger.debug(
512
+ "Failed to get font bounding box for char %s",
513
+ char.get_text(),
514
+ )
505
515
  char_bounding_box = None
506
516
 
507
517
  char_unicode = char.get_text()
@@ -187,6 +187,11 @@ class FontMapper:
187
187
  pbar.advance(1)
188
188
  for xref in range(1, xreflen):
189
189
  pbar.advance(1)
190
+ # xref_type = doc_zh.xref_get_key(xref, "Type")
191
+ # if xref_type[1] == "/Page":
192
+ # resources_xref = doc_zh.xref_get_key(xref, "Resources")
193
+ # if resources_xref[0] == 'null':
194
+ # doc_zh.xref_set_key(xref, "Resources", f"<</Font<<>>>>")
190
195
  for label in ["Resources/", ""]: # 可能是基于 xobj 的 res
191
196
  try: # xref 读写可能出错
192
197
  font_res = doc_zh.xref_get_key(xref, f"{label}Font")
@@ -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.2.23"
26
+ __version__ = "0.2.25"
27
27
 
28
28
 
29
29
  def create_parser():
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "BabelDOC"
3
- version = "0.2.23"
3
+ version = "0.2.25"
4
4
  description = "Yet Another Document Translator"
5
5
  license = "AGPL-3.0"
6
6
  readme = "README.md"
@@ -148,7 +148,7 @@ pythonpath = [".", "src"]
148
148
  testpaths = ["tests"]
149
149
 
150
150
  [bumpver]
151
- current_version = "0.2.23"
151
+ current_version = "0.2.25"
152
152
  version_pattern = "MAJOR.MINOR.PATCH[.PYTAGNUM]"
153
153
 
154
154
  [bumpver.file_patterns]