BabelDOC 0.1.6__py3-none-any.whl

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 (42) hide show
  1. babeldoc/__init__.py +1 -0
  2. babeldoc/asynchronize/__init__.py +51 -0
  3. babeldoc/const.py +7 -0
  4. babeldoc/converter.py +510 -0
  5. babeldoc/document_il/__init__.py +43 -0
  6. babeldoc/document_il/backend/__init__.py +0 -0
  7. babeldoc/document_il/backend/pdf_creater.py +402 -0
  8. babeldoc/document_il/frontend/__init__.py +0 -0
  9. babeldoc/document_il/frontend/il_creater.py +348 -0
  10. babeldoc/document_il/il_version_1.py +799 -0
  11. babeldoc/document_il/il_version_1.rnc +141 -0
  12. babeldoc/document_il/il_version_1.rng +390 -0
  13. babeldoc/document_il/il_version_1.xsd +235 -0
  14. babeldoc/document_il/midend/__init__.py +0 -0
  15. babeldoc/document_il/midend/add_debug_information.py +138 -0
  16. babeldoc/document_il/midend/il_translator.py +553 -0
  17. babeldoc/document_il/midend/layout_parser.py +180 -0
  18. babeldoc/document_il/midend/paragraph_finder.py +452 -0
  19. babeldoc/document_il/midend/remove_descent.py +168 -0
  20. babeldoc/document_il/midend/styles_and_formulas.py +701 -0
  21. babeldoc/document_il/midend/typesetting.py +761 -0
  22. babeldoc/document_il/translator/__init__.py +0 -0
  23. babeldoc/document_il/translator/cache.py +145 -0
  24. babeldoc/document_il/translator/translator.py +267 -0
  25. babeldoc/document_il/utils/fontmap.py +227 -0
  26. babeldoc/document_il/utils/layout_helper.py +467 -0
  27. babeldoc/document_il/utils/style_helper.py +86 -0
  28. babeldoc/document_il/xml_converter.py +51 -0
  29. babeldoc/docvision/README.md +0 -0
  30. babeldoc/docvision/__init__.py +0 -0
  31. babeldoc/docvision/doclayout.py +278 -0
  32. babeldoc/docvision/rpc_doclayout.py +162 -0
  33. babeldoc/high_level.py +468 -0
  34. babeldoc/main.py +466 -0
  35. babeldoc/pdfinterp.py +464 -0
  36. babeldoc/progress_monitor.py +146 -0
  37. babeldoc/translation_config.py +156 -0
  38. babeldoc-0.1.6.dist-info/METADATA +283 -0
  39. babeldoc-0.1.6.dist-info/RECORD +42 -0
  40. babeldoc-0.1.6.dist-info/WHEEL +4 -0
  41. babeldoc-0.1.6.dist-info/entry_points.txt +2 -0
  42. babeldoc-0.1.6.dist-info/licenses/LICENSE +661 -0
@@ -0,0 +1,348 @@
1
+ import base64
2
+ import logging
3
+ import re
4
+
5
+ import pdfminer.pdfinterp
6
+ import pymupdf
7
+ from pdfminer.layout import LTChar
8
+ from pdfminer.layout import LTFigure
9
+ from pdfminer.pdffont import PDFCIDFont
10
+ from pdfminer.pdffont import PDFFont
11
+ from pdfminer.psparser import PSLiteral
12
+
13
+ from babeldoc.document_il import il_version_1
14
+ from babeldoc.translation_config import TranslationConfig
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ILCreater:
20
+ stage_name = "解析PDF并创建中间表示"
21
+
22
+ def __init__(self, translation_config: TranslationConfig):
23
+ self.progress = None
24
+ self.current_page: il_version_1.Page = None
25
+ self.mupdf: pymupdf.Document = None
26
+ self.model = translation_config.doc_layout_model
27
+ self.docs = il_version_1.Document(page=[])
28
+ self.stroking_color_space_name = None
29
+ self.non_stroking_color_space_name = None
30
+ self.passthrough_per_char_instruction: list[tuple[str, str]] = []
31
+ self.translation_config = translation_config
32
+ self.passthrough_per_char_instruction_stack: list[list[tuple[str, str]]] = []
33
+ self.xobj_id = 0
34
+ self.xobj_inc = 0
35
+ self.xobj_map: dict[int, il_version_1.PdfXobject] = {}
36
+ self.xobj_stack = []
37
+
38
+ def on_finish(self):
39
+ self.progress.__exit__(None, None, None)
40
+
41
+ def is_passthrough_per_char_operation(self, operator: str):
42
+ return re.match("^(sc|scn|g|rg|k|cs|gs|ri)$", operator, re.IGNORECASE)
43
+
44
+ def on_passthrough_per_char(self, operator: str, args: list[str]):
45
+ if not self.is_passthrough_per_char_operation(operator):
46
+ logger.error("Unknown passthrough_per_char operation: %s", operator)
47
+ return
48
+ # logger.debug("xobj_id: %d, on_passthrough_per_char: %s ( %s )", self.xobj_id, operator, args)
49
+ args = [self.parse_arg(arg) for arg in args]
50
+ for _i, value in enumerate(self.passthrough_per_char_instruction.copy()):
51
+ op, arg = value
52
+ if op == operator:
53
+ self.passthrough_per_char_instruction.remove(value)
54
+ break
55
+ self.passthrough_per_char_instruction.append((operator, " ".join(args)))
56
+ pass
57
+
58
+ def remove_latest_passthrough_per_char_instruction(self):
59
+ if self.passthrough_per_char_instruction:
60
+ self.passthrough_per_char_instruction.pop()
61
+
62
+ def parse_arg(self, arg: str):
63
+ if isinstance(arg, PSLiteral):
64
+ return f"/{arg.name}"
65
+ if not isinstance(arg, str):
66
+ return str(arg)
67
+ return arg
68
+
69
+ def pop_passthrough_per_char_instruction(self):
70
+ if self.passthrough_per_char_instruction_stack:
71
+ self.passthrough_per_char_instruction = (
72
+ self.passthrough_per_char_instruction_stack.pop()
73
+ )
74
+ else:
75
+ self.passthrough_per_char_instruction = []
76
+ logging.error(
77
+ "pop_passthrough_per_char_instruction error on page: %s",
78
+ self.current_page.page_number,
79
+ )
80
+
81
+ def push_passthrough_per_char_instruction(self):
82
+ self.passthrough_per_char_instruction_stack.append(
83
+ self.passthrough_per_char_instruction.copy(),
84
+ )
85
+
86
+ # pdf32000 page 171
87
+ def on_stroking_color_space(self, color_space_name):
88
+ self.stroking_color_space_name = color_space_name
89
+
90
+ def on_non_stroking_color_space(self, color_space_name):
91
+ self.non_stroking_color_space_name = color_space_name
92
+
93
+ def on_new_stream(self):
94
+ self.stroking_color_space_name = None
95
+ self.non_stroking_color_space_name = None
96
+ self.passthrough_per_char_instruction = []
97
+
98
+ def push_xobj(self):
99
+ self.xobj_stack.append(
100
+ (self.current_page_font_name_id_map.copy(), self.xobj_id),
101
+ )
102
+ self.current_page_font_name_id_map = {}
103
+
104
+ def pop_xobj(self):
105
+ self.current_page_font_name_id_map, self.xobj_id = self.xobj_stack.pop()
106
+
107
+ def on_xobj_begin(self, bbox, xref_id):
108
+ self.push_passthrough_per_char_instruction()
109
+ self.push_xobj()
110
+ self.xobj_inc += 1
111
+ self.xobj_id = self.xobj_inc
112
+ xobject = il_version_1.PdfXobject(
113
+ box=il_version_1.Box(
114
+ x=float(bbox[0]),
115
+ y=float(bbox[1]),
116
+ x2=float(bbox[2]),
117
+ y2=float(bbox[3]),
118
+ ),
119
+ xobj_id=self.xobj_id,
120
+ xref_id=xref_id,
121
+ )
122
+ self.current_page.pdf_xobject.append(xobject)
123
+ self.xobj_map[self.xobj_id] = xobject
124
+ return self.xobj_id
125
+
126
+ def on_xobj_end(self, xobj_id, base_op):
127
+ self.pop_passthrough_per_char_instruction()
128
+ self.pop_xobj()
129
+ xobj = self.xobj_map[xobj_id]
130
+ xobj.base_operations = il_version_1.BaseOperations(value=base_op)
131
+ self.xobj_inc += 1
132
+
133
+ def on_page_start(self):
134
+ self.current_page = il_version_1.Page(
135
+ pdf_font=[],
136
+ pdf_character=[],
137
+ page_layout=[],
138
+ # currently don't support UserUnit page parameter
139
+ # pdf32000 page 79
140
+ unit="point",
141
+ )
142
+ self.current_page_font_name_id_map = {}
143
+ self.passthrough_per_char_instruction_stack = []
144
+ self.xobj_stack = []
145
+ self.non_stroking_color_space_name = None
146
+ self.stroking_color_space_name = None
147
+ self.docs.page.append(self.current_page)
148
+
149
+ def on_page_end(self):
150
+ self.progress.advance(1)
151
+
152
+ def on_page_crop_box(
153
+ self,
154
+ x0: float | int,
155
+ y0: float | int,
156
+ x1: float | int,
157
+ y1: float | int,
158
+ ):
159
+ box = il_version_1.Box(x=float(x0), y=float(y0), x2=float(x1), y2=float(y1))
160
+ self.current_page.cropbox = il_version_1.Cropbox(box=box)
161
+
162
+ def on_page_media_box(
163
+ self,
164
+ x0: float | int,
165
+ y0: float | int,
166
+ x1: float | int,
167
+ y1: float | int,
168
+ ):
169
+ box = il_version_1.Box(x=float(x0), y=float(y0), x2=float(x1), y2=float(y1))
170
+ self.current_page.mediabox = il_version_1.Mediabox(box=box)
171
+
172
+ def on_page_number(self, page_number: int):
173
+ assert isinstance(page_number, int)
174
+ assert page_number >= 0
175
+ self.current_page.page_number = page_number
176
+
177
+ def on_page_base_operation(self, operation: str):
178
+ self.current_page.base_operations = il_version_1.BaseOperations(value=operation)
179
+
180
+ def on_page_resource_font(self, font: PDFFont, xref_id: int, font_id: str):
181
+ font_name = font.fontname
182
+ if isinstance(font_name, bytes):
183
+ try:
184
+ font_name = font_name.decode("utf-8")
185
+ except UnicodeDecodeError:
186
+ font_name = "BASE64:" + base64.b64encode(font_name).decode("utf-8")
187
+ encoding_length = 1
188
+ if isinstance(font, PDFCIDFont):
189
+ try:
190
+ # pdf 32000:2008 page 273
191
+ # Table 118 - Predefined CJK CMap names
192
+ _, encoding = self.mupdf.xref_get_key(xref_id, "Encoding")
193
+ if encoding == "/Identity-H" or encoding == "/Identity-V":
194
+ encoding_length = 2
195
+ else:
196
+ _, to_unicode_id = self.mupdf.xref_get_key(xref_id, "ToUnicode")
197
+ to_unicode_bytes = self.mupdf.xref_stream(
198
+ int(to_unicode_id.split(" ")[0]),
199
+ )
200
+ code_range = re.search(
201
+ b"begincodespacerange\n?.*<(\\d+?)>.*",
202
+ to_unicode_bytes,
203
+ ).group(1)
204
+ encoding_length = len(code_range) // 2
205
+ except Exception:
206
+ if max(font.unicode_map.cid2unichr.keys()) > 255:
207
+ encoding_length = 2
208
+ else:
209
+ encoding_length = 1
210
+ try:
211
+ mupdf_font = pymupdf.Font(fontbuffer=self.mupdf.extract_font(xref_id)[3])
212
+ bold = mupdf_font.is_bold
213
+ italic = mupdf_font.is_italic
214
+ monospaced = mupdf_font.is_monospaced
215
+ serif = mupdf_font.is_serif
216
+ except Exception:
217
+ bold = None
218
+ italic = None
219
+ monospaced = None
220
+ serif = None
221
+ il_font_metadata = il_version_1.PdfFont(
222
+ name=font_name,
223
+ xref_id=xref_id,
224
+ font_id=font_id,
225
+ encoding_length=encoding_length,
226
+ bold=bold,
227
+ italic=italic,
228
+ monospace=monospaced,
229
+ serif=serif,
230
+ ascent=font.ascent,
231
+ descent=font.descent,
232
+ )
233
+ self.current_page_font_name_id_map[font_name] = font_id
234
+ if self.xobj_id in self.xobj_map:
235
+ self.xobj_map[self.xobj_id].pdf_font.append(il_font_metadata)
236
+ else:
237
+ self.current_page.pdf_font.append(il_font_metadata)
238
+
239
+ def create_graphic_state(self, gs: pdfminer.pdfinterp.PDFGraphicState):
240
+ graphic_state = il_version_1.GraphicState()
241
+ for k, v in gs.__dict__.items():
242
+ if v is None:
243
+ continue
244
+ if k in ["scolor", "ncolor"]:
245
+ if isinstance(v, tuple):
246
+ v = list(v)
247
+ else:
248
+ v = [v]
249
+ setattr(graphic_state, k, v)
250
+ continue
251
+ if k == "linewidth":
252
+ graphic_state.linewidth = float(v)
253
+ continue
254
+ continue
255
+ raise NotImplementedError
256
+
257
+ graphic_state.stroking_color_space_name = self.stroking_color_space_name
258
+ graphic_state.non_stroking_color_space_name = self.non_stroking_color_space_name
259
+
260
+ graphic_state.passthrough_per_char_instruction = " ".join(
261
+ f"{arg} {op}" for op, arg in gs.passthrough_instruction
262
+ )
263
+
264
+ return graphic_state
265
+
266
+ def on_lt_char(self, char: LTChar):
267
+ gs = self.create_graphic_state(char.graphicstate)
268
+ # Get font from current page or xobject
269
+ font = None
270
+ for pdf_font in self.xobj_map.get(self.xobj_id, self.current_page).pdf_font:
271
+ if pdf_font.font_id == char.aw_font_id:
272
+ font = pdf_font
273
+ break
274
+
275
+ # Get descent from font
276
+ descent = 0
277
+ if font and hasattr(font, "descent"):
278
+ descent = font.descent * char.size / 1000
279
+
280
+ char_id = char.cid
281
+ char_unicode = char.get_text()
282
+ if "(cid:" not in char_unicode and len(char_unicode) > 1:
283
+ return
284
+ advance = char.adv
285
+ if char.matrix[0] == 0 and char.matrix[3] == 0:
286
+ vertical = True
287
+ bbox = il_version_1.Box(
288
+ x=char.bbox[0] - descent,
289
+ y=char.bbox[1],
290
+ x2=char.bbox[2] - descent,
291
+ y2=char.bbox[3],
292
+ )
293
+ else:
294
+ vertical = False
295
+ # Add descent to y coordinates
296
+ bbox = il_version_1.Box(
297
+ x=char.bbox[0],
298
+ y=char.bbox[1] + descent,
299
+ x2=char.bbox[2],
300
+ y2=char.bbox[3] + descent,
301
+ )
302
+ pdf_style = il_version_1.PdfStyle(
303
+ font_id=char.aw_font_id,
304
+ font_size=char.size,
305
+ graphic_state=gs,
306
+ )
307
+ pdf_char = il_version_1.PdfCharacter(
308
+ box=bbox,
309
+ pdf_character_id=char_id,
310
+ advance=advance,
311
+ char_unicode=char_unicode,
312
+ vertical=vertical,
313
+ pdf_style=pdf_style,
314
+ xobj_id=char.xobj_id,
315
+ )
316
+ self.current_page.pdf_character.append(pdf_char)
317
+
318
+ def create_il(self):
319
+ pages = [
320
+ page
321
+ for page in self.docs.page
322
+ if self.translation_config.should_translate_page(page.page_number + 1)
323
+ ]
324
+ self.docs.page = pages
325
+ return self.docs
326
+
327
+ def on_total_pages(self, total_pages: int):
328
+ assert isinstance(total_pages, int)
329
+ assert total_pages > 0
330
+ self.docs.total_pages = total_pages
331
+ total = 0
332
+ for page in range(total_pages):
333
+ if self.translation_config.should_translate_page(page + 1) is False:
334
+ continue
335
+ total += 1
336
+ self.progress = self.translation_config.progress_monitor.stage_start(
337
+ self.stage_name,
338
+ total,
339
+ )
340
+
341
+ def on_pdf_figure(self, figure: LTFigure):
342
+ box = il_version_1.Box(
343
+ figure.bbox[0],
344
+ figure.bbox[1],
345
+ figure.bbox[2],
346
+ figure.bbox[3],
347
+ )
348
+ self.current_page.pdf_figure.append(il_version_1.PdfFigure(box=box))