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,467 @@
1
+ import logging
2
+ import math
3
+
4
+ from babeldoc.document_il import GraphicState
5
+ from babeldoc.document_il.il_version_1 import Box
6
+ from babeldoc.document_il.il_version_1 import PdfCharacter
7
+ from babeldoc.document_il.il_version_1 import PdfParagraph
8
+ from babeldoc.document_il.il_version_1 import PdfParagraphComposition
9
+ from pymupdf import Font
10
+
11
+ logger = logging.getLogger(__name__)
12
+ HEIGHT_NOT_USFUL_CHAR_IN_CHAR = (
13
+ "∑︁",
14
+ # 暂时假设cid:17和cid 16是特殊情况
15
+ # 来源于 arXiv:2310.18608v2 第九页公式大括号
16
+ "(cid:17)",
17
+ "(cid:16)",
18
+ # arXiv:2411.19509v2 第四页 []
19
+ "(cid:104)",
20
+ "(cid:105)",
21
+ # arXiv:2411.19509v2 第四页 公式的|竖线
22
+ "(cid:13)",
23
+ "∑︁",
24
+ # arXiv:2412.05265 27页 累加号
25
+ "(cid:88)",
26
+ # arXiv:2412.05265 16页 累乘号
27
+ "(cid:89)",
28
+ # arXiv:2412.05265 27页 积分
29
+ "(cid:90)",
30
+ # arXiv:2412.05265 32页 公式左右的中括号
31
+ "(cid:2)",
32
+ "(cid:3)",
33
+ )
34
+
35
+
36
+ LEFT_BRACKET = ("(cid:8)", "(", "(cid:16)", "{", "[", "(cid:104)", "(cid:2)")
37
+ RIGHT_BRACKET = ("(cid:9)", ")", "(cid:17)", "}", "]", "(cid:105)", "(cid:3)")
38
+
39
+
40
+ def formular_height_ignore_char(char: PdfCharacter):
41
+ return (
42
+ char.pdf_character_id is None
43
+ or char.char_unicode in HEIGHT_NOT_USFUL_CHAR_IN_CHAR
44
+ )
45
+
46
+
47
+ class Layout:
48
+ def __init__(self, layout_id, name):
49
+ self.id = layout_id
50
+ self.name = name
51
+
52
+ @staticmethod
53
+ def is_newline(prev_char: PdfCharacter, curr_char: PdfCharacter) -> bool:
54
+ # 如果没有前一个字符,不是换行
55
+ if prev_char is None:
56
+ return False
57
+
58
+ # 获取两个字符的中心 y 坐标
59
+ # prev_y = (prev_char.box.y + prev_char.box.y2) / 2
60
+ # curr_y = (curr_char.box.y + curr_char.box.y2) / 2
61
+
62
+ # 如果当前字符的 y 坐标明显低于前一个字符,说明换行了
63
+ # 这里使用字符高度的一半作为阈值
64
+ char_height = max(
65
+ curr_char.box.y2 - curr_char.box.y,
66
+ prev_char.box.y2 - prev_char.box.y,
67
+ )
68
+ char_width = max(
69
+ curr_char.box.x2 - curr_char.box.x,
70
+ prev_char.box.x2 - prev_char.box.x,
71
+ )
72
+ should_new_line = (
73
+ curr_char.box.y2 < prev_char.box.y
74
+ or curr_char.box.x2 < prev_char.box.x - char_width * 10
75
+ )
76
+ if should_new_line and (
77
+ formular_height_ignore_char(curr_char)
78
+ or formular_height_ignore_char(prev_char)
79
+ ):
80
+ return False
81
+ return should_new_line
82
+
83
+
84
+ def get_paragraph_length_except(
85
+ paragraph: PdfParagraph,
86
+ except_chars: str,
87
+ font: Font,
88
+ ) -> int:
89
+ length = 0
90
+ for composition in paragraph.pdf_paragraph_composition:
91
+ if composition.pdf_character:
92
+ length += (
93
+ composition.pdf_character[0].box.x2 - composition.pdf_character[0].box.x
94
+ )
95
+ elif composition.pdf_same_style_characters:
96
+ for pdf_char in composition.pdf_same_style_characters.pdf_character:
97
+ if pdf_char.char_unicode in except_chars:
98
+ continue
99
+ length += pdf_char.box.x2 - pdf_char.box.x
100
+ elif composition.pdf_same_style_unicode_characters:
101
+ for char_unicode in composition.pdf_same_style_unicode_characters.unicode:
102
+ if char_unicode in except_chars:
103
+ continue
104
+ length += font.char_lengths(
105
+ char_unicode,
106
+ composition.pdf_same_style_unicode_characters.pdf_style.font_size,
107
+ )[0]
108
+ elif composition.pdf_line:
109
+ for pdf_char in composition.pdf_line.pdf_character:
110
+ if pdf_char.char_unicode in except_chars:
111
+ continue
112
+ length += pdf_char.box.x2 - pdf_char.box.x
113
+ elif composition.pdf_formula:
114
+ length += composition.pdf_formula.box.x2 - composition.pdf_formula.box.x
115
+ else:
116
+ logger.error(
117
+ f"Unknown composition type. "
118
+ f"Composition: {composition}. "
119
+ f"Paragraph: {paragraph}. ",
120
+ )
121
+ continue
122
+ return length
123
+
124
+
125
+ def get_paragraph_unicode(paragraph: PdfParagraph) -> str:
126
+ chars = []
127
+ for composition in paragraph.pdf_paragraph_composition:
128
+ if composition.pdf_line:
129
+ chars.extend(composition.pdf_line.pdf_character)
130
+ elif composition.pdf_same_style_characters:
131
+ chars.extend(composition.pdf_same_style_characters.pdf_character)
132
+ elif composition.pdf_same_style_unicode_characters:
133
+ chars.extend(composition.pdf_same_style_unicode_characters.unicode)
134
+ elif composition.pdf_formula:
135
+ chars.extend(composition.pdf_formula.pdf_character)
136
+ elif composition.pdf_character:
137
+ chars.append(composition.pdf_character)
138
+ else:
139
+ logger.error(
140
+ f"Unknown composition type. "
141
+ f"Composition: {composition}. "
142
+ f"Paragraph: {paragraph}. ",
143
+ )
144
+ continue
145
+ return get_char_unicode_string(chars)
146
+
147
+
148
+ def get_char_unicode_string(chars: list[PdfCharacter | str]) -> str:
149
+ """
150
+ 将字符列表转换为 Unicode 字符串,根据字符间距自动插入空格。
151
+ 有些 PDF 不会显式编码空格,这时需要根据间距自动插入空格。
152
+
153
+ Args:
154
+ chars: 字符列表,可以是 PdfCharacter 对象或字符串
155
+
156
+ Returns:
157
+ str: 处理后的 Unicode 字符串
158
+ """
159
+ # 计算字符间距的中位数
160
+ distances = []
161
+ for i in range(len(chars) - 1):
162
+ if not (
163
+ isinstance(chars[i], PdfCharacter)
164
+ and isinstance(chars[i + 1], PdfCharacter)
165
+ ):
166
+ continue
167
+ distance = chars[i + 1].box.x - chars[i].box.x2
168
+ if distance > 1: # 只考虑正向距离
169
+ distances.append(distance)
170
+
171
+ # 去重后的距离
172
+ distinct_distances = sorted(set(distances))
173
+
174
+ if not distinct_distances:
175
+ median_distance = 1
176
+ elif len(distinct_distances) == 1:
177
+ median_distance = distinct_distances[0]
178
+ else:
179
+ median_distance = distinct_distances[1]
180
+
181
+ # 构建 unicode 字符串,根据间距插入空格
182
+ unicode_chars = []
183
+ for i in range(len(chars)):
184
+ # 如果不是字符对象,直接添加,一般来说这个时候 chars[i] 是字符串
185
+ if not isinstance(chars[i], PdfCharacter):
186
+ unicode_chars.append(chars[i])
187
+ continue
188
+ unicode_chars.append(chars[i].char_unicode)
189
+
190
+ # 如果是空格,跳过
191
+ if chars[i].char_unicode == " ":
192
+ continue
193
+
194
+ # 如果两个字符都是 PdfCharacter,检查间距
195
+ if i < len(chars) - 1 and isinstance(chars[i + 1], PdfCharacter):
196
+ distance = chars[i + 1].box.x - chars[i].box.x2
197
+ if distance >= median_distance or Layout.is_newline( # 间距大于中位数
198
+ chars[i],
199
+ chars[i + 1],
200
+ ): # 换行
201
+ unicode_chars.append(" ") # 添加空格
202
+
203
+ return "".join(unicode_chars)
204
+
205
+
206
+ def get_paragraph_max_height(paragraph: PdfParagraph) -> float:
207
+ """
208
+ 获取段落中最高的排版单元高度。
209
+
210
+ Args:
211
+ paragraph: PDF段落对象
212
+
213
+ Returns:
214
+ float: 最大高度值
215
+ """
216
+ max_height = 0.0
217
+ for composition in paragraph.pdf_paragraph_composition:
218
+ if composition is None:
219
+ continue
220
+ if composition.pdf_character:
221
+ char_height = (
222
+ composition.pdf_character[0].box.y2 - composition.pdf_character[0].box.y
223
+ )
224
+ max_height = max(max_height, char_height)
225
+ elif composition.pdf_same_style_characters:
226
+ for pdf_char in composition.pdf_same_style_characters.pdf_character:
227
+ char_height = pdf_char.box.y2 - pdf_char.box.y
228
+ max_height = max(max_height, char_height)
229
+ elif composition.pdf_same_style_unicode_characters:
230
+ # 对于纯Unicode字符,我们使用其样式中的字体大小作为高度估计
231
+ font_size = (
232
+ composition.pdf_same_style_unicode_characters.pdf_style.font_size
233
+ )
234
+ max_height = max(max_height, font_size)
235
+ elif composition.pdf_line:
236
+ for pdf_char in composition.pdf_line.pdf_character:
237
+ char_height = pdf_char.box.y2 - pdf_char.box.y
238
+ max_height = max(max_height, char_height)
239
+ elif composition.pdf_formula:
240
+ formula_height = (
241
+ composition.pdf_formula.box.y2 - composition.pdf_formula.box.y
242
+ )
243
+ max_height = max(max_height, formula_height)
244
+ else:
245
+ logger.error(
246
+ f"Unknown composition type. "
247
+ f"Composition: {composition}. "
248
+ f"Paragraph: {paragraph}. ",
249
+ )
250
+ continue
251
+ return max_height
252
+
253
+
254
+ def is_same_style(style1, style2) -> bool:
255
+ """判断两个样式是否相同"""
256
+ if style1 is None or style2 is None:
257
+ return style1 is style2
258
+
259
+ return (
260
+ style1.font_id == style2.font_id
261
+ and math.fabs(style1.font_size - style2.font_size) < 0.02
262
+ and is_same_graphic_state(style1.graphic_state, style2.graphic_state)
263
+ )
264
+
265
+
266
+ def is_same_style_except_size(style1, style2) -> bool:
267
+ """判断两个样式是否相同"""
268
+ if style1 is None or style2 is None:
269
+ return style1 is style2
270
+
271
+ return (
272
+ style1.font_id == style2.font_id
273
+ and 0.7 < math.fabs(style1.font_size / style2.font_size) < 1.3
274
+ and is_same_graphic_state(style1.graphic_state, style2.graphic_state)
275
+ )
276
+
277
+
278
+ def is_same_style_except_font(style1, style2) -> bool:
279
+ """判断两个样式是否相同"""
280
+ if style1 is None or style2 is None:
281
+ return style1 is style2
282
+
283
+ return math.fabs(
284
+ style1.font_size - style2.font_size,
285
+ ) < 0.02 and is_same_graphic_state(style1.graphic_state, style2.graphic_state)
286
+
287
+
288
+ def is_same_graphic_state(state1: GraphicState, state2: GraphicState) -> bool:
289
+ """判断两个 GraphicState 是否相同"""
290
+ if state1 is None or state2 is None:
291
+ return state1 is state2
292
+
293
+ return (
294
+ state1.linewidth == state2.linewidth
295
+ and state1.dash == state2.dash
296
+ and state1.flatness == state2.flatness
297
+ and state1.intent == state2.intent
298
+ and state1.linecap == state2.linecap
299
+ and state1.linejoin == state2.linejoin
300
+ and state1.miterlimit == state2.miterlimit
301
+ and state1.ncolor == state2.ncolor
302
+ and state1.scolor == state2.scolor
303
+ and state1.stroking_color_space_name == state2.stroking_color_space_name
304
+ and state1.non_stroking_color_space_name == state2.non_stroking_color_space_name
305
+ and state1.passthrough_per_char_instruction
306
+ == state2.passthrough_per_char_instruction
307
+ )
308
+
309
+
310
+ def add_space_dummy_chars(paragraph: PdfParagraph) -> None:
311
+ """
312
+ 在PDF段落中添加表示空格的dummy字符。
313
+ 这个函数会直接修改传入的paragraph对象,在需要空格的地方添加dummy字符。
314
+ 同时也会处理不同组成部分之间的空格。
315
+
316
+ Args:
317
+ paragraph: 需要处理的PDF段落对象
318
+ """
319
+ # 首先处理每个组成部分内部的空格
320
+ for composition in paragraph.pdf_paragraph_composition:
321
+ if composition.pdf_line:
322
+ chars = composition.pdf_line.pdf_character
323
+ _add_space_dummy_chars_to_list(chars)
324
+ elif composition.pdf_same_style_characters:
325
+ chars = composition.pdf_same_style_characters.pdf_character
326
+ _add_space_dummy_chars_to_list(chars)
327
+ elif composition.pdf_same_style_unicode_characters:
328
+ # 对于unicode字符,不需要处理。
329
+ # 这种类型只会出现在翻译好的结果中
330
+ continue
331
+ elif composition.pdf_formula:
332
+ chars = composition.pdf_formula.pdf_character
333
+ _add_space_dummy_chars_to_list(chars)
334
+
335
+ # 然后处理组成部分之间的空格
336
+ for i in range(len(paragraph.pdf_paragraph_composition) - 1):
337
+ curr_comp = paragraph.pdf_paragraph_composition[i]
338
+ next_comp = paragraph.pdf_paragraph_composition[i + 1]
339
+
340
+ # 获取当前组成部分的最后一个字符
341
+ curr_last_char = _get_last_char_from_composition(curr_comp)
342
+ if not curr_last_char:
343
+ continue
344
+
345
+ # 获取下一个组成部分的第一个字符
346
+ next_first_char = _get_first_char_from_composition(next_comp)
347
+ if not next_first_char:
348
+ continue
349
+
350
+ # 检查两个组成部分之间是否需要添加空格
351
+ distance = next_first_char.box.x - curr_last_char.box.x2
352
+ if distance > 1: # 只考虑正向距离
353
+ # 创建一个dummy字符作为空格
354
+ space_box = Box(
355
+ x=curr_last_char.box.x2,
356
+ y=curr_last_char.box.y,
357
+ x2=curr_last_char.box.x2 + distance,
358
+ y2=curr_last_char.box.y2,
359
+ )
360
+
361
+ space_char = PdfCharacter(
362
+ pdf_style=curr_last_char.pdf_style,
363
+ box=space_box,
364
+ char_unicode=" ",
365
+ scale=curr_last_char.scale,
366
+ advance=space_box.x2 - space_box.x,
367
+ )
368
+
369
+ # 将空格添加到当前组成部分的末尾
370
+ if curr_comp.pdf_line:
371
+ curr_comp.pdf_line.pdf_character.append(space_char)
372
+ elif curr_comp.pdf_same_style_characters:
373
+ curr_comp.pdf_same_style_characters.pdf_character.append(space_char)
374
+ elif curr_comp.pdf_formula:
375
+ curr_comp.pdf_formula.pdf_character.append(space_char)
376
+
377
+
378
+ def _get_first_char_from_composition(
379
+ comp: PdfParagraphComposition,
380
+ ) -> PdfCharacter | None:
381
+ """获取组成部分的第一个字符"""
382
+ if comp.pdf_line and comp.pdf_line.pdf_character:
383
+ return comp.pdf_line.pdf_character[0]
384
+ elif (
385
+ comp.pdf_same_style_characters and comp.pdf_same_style_characters.pdf_character
386
+ ):
387
+ return comp.pdf_same_style_characters.pdf_character[0]
388
+ elif comp.pdf_formula and comp.pdf_formula.pdf_character:
389
+ return comp.pdf_formula.pdf_character[0]
390
+ elif comp.pdf_character:
391
+ return comp.pdf_character
392
+ return None
393
+
394
+
395
+ def _get_last_char_from_composition(
396
+ comp: PdfParagraphComposition,
397
+ ) -> PdfCharacter | None:
398
+ """获取组成部分的最后一个字符"""
399
+ if comp.pdf_line and comp.pdf_line.pdf_character:
400
+ return comp.pdf_line.pdf_character[-1]
401
+ elif (
402
+ comp.pdf_same_style_characters and comp.pdf_same_style_characters.pdf_character
403
+ ):
404
+ return comp.pdf_same_style_characters.pdf_character[-1]
405
+ elif comp.pdf_formula and comp.pdf_formula.pdf_character:
406
+ return comp.pdf_formula.pdf_character[-1]
407
+ elif comp.pdf_character:
408
+ return comp.pdf_character
409
+ return None
410
+
411
+
412
+ def _add_space_dummy_chars_to_list(chars: list[PdfCharacter]) -> None:
413
+ """
414
+ 在字符列表中的适当位置添加表示空格的dummy字符。
415
+
416
+ Args:
417
+ chars: PdfCharacter对象列表
418
+ """
419
+ if not chars:
420
+ return
421
+
422
+ # 计算字符间距的中位数
423
+ distances = []
424
+ for i in range(len(chars) - 1):
425
+ distance = chars[i + 1].box.x - chars[i].box.x2
426
+ if distance > 1: # 只考虑正向距离
427
+ distances.append(distance)
428
+
429
+ # 去重后的距离
430
+ distinct_distances = sorted(set(distances))
431
+
432
+ if not distinct_distances:
433
+ median_distance = 1
434
+ elif len(distinct_distances) == 1:
435
+ median_distance = distinct_distances[0]
436
+ else:
437
+ median_distance = distinct_distances[1]
438
+
439
+ # 在需要的地方插入空格字符
440
+ i = 0
441
+ while i < len(chars) - 1:
442
+ curr_char = chars[i]
443
+ next_char = chars[i + 1]
444
+
445
+ distance = next_char.box.x - curr_char.box.x2
446
+ if distance >= median_distance or Layout.is_newline(curr_char, next_char):
447
+ # 创建一个dummy字符作为空格
448
+ space_box = Box(
449
+ x=curr_char.box.x2,
450
+ y=curr_char.box.y,
451
+ x2=curr_char.box.x2 + min(distance, median_distance),
452
+ y2=curr_char.box.y2,
453
+ )
454
+
455
+ space_char = PdfCharacter(
456
+ pdf_style=curr_char.pdf_style,
457
+ box=space_box,
458
+ char_unicode=" ",
459
+ scale=curr_char.scale,
460
+ advance=space_box.x2 - space_box.x,
461
+ )
462
+
463
+ # 在当前位置后插入空格字符
464
+ chars.insert(i + 1, space_char)
465
+ i += 2 # 跳过刚插入的空格
466
+ else:
467
+ i += 1
@@ -0,0 +1,86 @@
1
+ from babeldoc.document_il import il_version_1
2
+
3
+
4
+ def create_pdf_style(r, g, b, font_id="china-ss", font_size=6):
5
+ """
6
+ Create a PdfStyle object from RGB values.
7
+
8
+ Args:
9
+ r: Red component in range 0-255
10
+ g: Green component in range 0-255
11
+ b: Blue component in range 0-255
12
+ font_id: Font identifier
13
+ font_size: Font size
14
+
15
+ Returns:
16
+ PdfStyle object with the specified color
17
+ """
18
+ r, g, b = [x / 255.0 for x in (r, g, b)]
19
+ return il_version_1.PdfStyle(
20
+ font_id=font_id,
21
+ font_size=font_size,
22
+ graphic_state=il_version_1.GraphicState(
23
+ passthrough_per_char_instruction=f"{r:.10f} {g:.10f} {b:.10f} rg",
24
+ ),
25
+ )
26
+
27
+
28
+ # Generate all color styles
29
+ RED = il_version_1.GraphicState(
30
+ passthrough_per_char_instruction="1.0000000000 0.2313725490 0.1882352941 rg "
31
+ "1.0000000000 0.2313725490 0.1882352941 RG",
32
+ )
33
+
34
+ ORANGE = il_version_1.GraphicState(
35
+ passthrough_per_char_instruction="1.0000000000 0.5843137255 0.0000000000 rg "
36
+ "1.0000000000 0.5843137255 0.0000000000 RG",
37
+ )
38
+ YELLOW = il_version_1.GraphicState(
39
+ passthrough_per_char_instruction="1.0000000000 0.8000000000 0.0000000000 rg "
40
+ "1.0000000000 0.8000000000 0.0000000000 RG",
41
+ )
42
+
43
+ GREEN = il_version_1.GraphicState(
44
+ passthrough_per_char_instruction="0.2039215686 0.7803921569 0.3490196078 rg "
45
+ "0.2039215686 0.7803921569 0.3490196078 RG",
46
+ )
47
+
48
+ MINT = il_version_1.GraphicState(
49
+ passthrough_per_char_instruction="0.0000000000 0.7803921569 0.7450980392 rg "
50
+ "0.0000000000 0.7803921569 0.7450980392 RG",
51
+ )
52
+
53
+ TEAL = il_version_1.GraphicState(
54
+ passthrough_per_char_instruction="0.1882352941 0.6901960784 0.7803921569 rg "
55
+ "0.1882352941 0.6901960784 0.7803921569 RG",
56
+ )
57
+
58
+ CYAN = il_version_1.GraphicState(
59
+ passthrough_per_char_instruction="0.1960784314 0.6784313725 0.9019607843 rg "
60
+ "0.1960784314 0.6784313725 0.9019607843 RG",
61
+ )
62
+
63
+ BLUE = il_version_1.GraphicState(
64
+ passthrough_per_char_instruction="0.0000000000 0.4784313725 1.0000000000 rg "
65
+ "0.0000000000 0.4784313725 1.0000000000 RG",
66
+ )
67
+
68
+ INDIGO = il_version_1.GraphicState(
69
+ passthrough_per_char_instruction="0.3450980392 0.3372549020 0.8392156863 rg "
70
+ "0.3450980392 0.3372549020 0.8392156863 RG",
71
+ )
72
+
73
+ PURPLE = il_version_1.GraphicState(
74
+ passthrough_per_char_instruction="0.6862745098 0.3215686275 0.8705882353 rg "
75
+ "0.6862745098 0.3215686275 0.8705882353 RG",
76
+ )
77
+
78
+ PINK = il_version_1.GraphicState(
79
+ passthrough_per_char_instruction="1.0000000000 0.1764705882 0.3333333333 rg "
80
+ "1.0000000000 0.1764705882 0.3333333333 RG",
81
+ )
82
+
83
+ BROWN = il_version_1.GraphicState(
84
+ passthrough_per_char_instruction="0.6352941176 0.5176470588 0.3686274510 rg "
85
+ "0.6352941176 0.5176470588 0.3686274510 RG",
86
+ )
@@ -0,0 +1,51 @@
1
+ import copy
2
+ from pathlib import Path
3
+
4
+ import orjson
5
+ from xsdata.formats.dataclass.context import XmlContext
6
+ from xsdata.formats.dataclass.parsers import XmlParser
7
+ from xsdata.formats.dataclass.serializers import XmlSerializer
8
+ from xsdata.formats.dataclass.serializers.config import SerializerConfig
9
+
10
+ from babeldoc.document_il import il_version_1
11
+
12
+
13
+ class XMLConverter:
14
+ def __init__(self):
15
+ self.parser = XmlParser()
16
+ config = SerializerConfig(indent=" ")
17
+ context = XmlContext()
18
+ self.serializer = XmlSerializer(context=context, config=config)
19
+
20
+ def write_xml(self, document: il_version_1.Document, path: str):
21
+ with Path(path).open("w", encoding="utf-8") as f:
22
+ f.write(self.to_xml(document))
23
+
24
+ def read_xml(self, path: str) -> il_version_1.Document:
25
+ with Path(path).open(encoding="utf-8") as f:
26
+ return self.from_xml(f.read())
27
+
28
+ def to_xml(self, document: il_version_1.Document) -> str:
29
+ return self.serializer.render(document)
30
+
31
+ def from_xml(self, xml: str) -> il_version_1.Document:
32
+ return self.parser.from_string(
33
+ xml,
34
+ il_version_1.Document,
35
+ )
36
+
37
+ def deepcopy(self, document: il_version_1.Document) -> il_version_1.Document:
38
+ return copy.deepcopy(document)
39
+ # return self.from_xml(self.to_xml(document))
40
+
41
+ def to_json(self, document: il_version_1.Document) -> str:
42
+ return orjson.dumps(
43
+ document,
44
+ option=orjson.OPT_APPEND_NEWLINE
45
+ | orjson.OPT_INDENT_2
46
+ | orjson.OPT_SORT_KEYS,
47
+ ).decode()
48
+
49
+ def write_json(self, document: il_version_1.Document, path: str):
50
+ with Path(path).open("w", encoding="utf-8") as f:
51
+ f.write(self.to_json(document))
File without changes
File without changes