docx-from-pdf 0.1__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 (64) hide show
  1. docx_from_pdf/__init__.py +3 -0
  2. docx_from_pdf/backend/__init__.py +1 -0
  3. docx_from_pdf/backend/pdfium.py +440 -0
  4. docx_from_pdf/common/Block.py +144 -0
  5. docx_from_pdf/common/Collection.py +359 -0
  6. docx_from_pdf/common/Element.py +307 -0
  7. docx_from_pdf/common/__init__.py +0 -0
  8. docx_from_pdf/common/algorithm.py +403 -0
  9. docx_from_pdf/common/constants.py +90 -0
  10. docx_from_pdf/common/docx.py +593 -0
  11. docx_from_pdf/common/geometry.py +206 -0
  12. docx_from_pdf/common/share.py +319 -0
  13. docx_from_pdf/converter.py +473 -0
  14. docx_from_pdf/font/Fonts.py +243 -0
  15. docx_from_pdf/font/__init__.py +0 -0
  16. docx_from_pdf/gui/App.py +37 -0
  17. docx_from_pdf/gui/MainFrame.py +147 -0
  18. docx_from_pdf/gui/__init__.py +0 -0
  19. docx_from_pdf/image/Image.py +112 -0
  20. docx_from_pdf/image/ImageBlock.py +81 -0
  21. docx_from_pdf/image/ImageSpan.py +27 -0
  22. docx_from_pdf/image/ImagesExtractor.py +117 -0
  23. docx_from_pdf/image/__init__.py +0 -0
  24. docx_from_pdf/layout/Blocks.py +650 -0
  25. docx_from_pdf/layout/Column.py +49 -0
  26. docx_from_pdf/layout/Layout.py +177 -0
  27. docx_from_pdf/layout/Section.py +97 -0
  28. docx_from_pdf/layout/Sections.py +91 -0
  29. docx_from_pdf/layout/__init__.py +0 -0
  30. docx_from_pdf/main.py +135 -0
  31. docx_from_pdf/page/BasePage.py +27 -0
  32. docx_from_pdf/page/Page.py +211 -0
  33. docx_from_pdf/page/Pages.py +90 -0
  34. docx_from_pdf/page/RawPage.py +279 -0
  35. docx_from_pdf/page/RawPageFactory.py +21 -0
  36. docx_from_pdf/page/RawPagePdfium.py +66 -0
  37. docx_from_pdf/page/__init__.py +0 -0
  38. docx_from_pdf/shape/Path.py +405 -0
  39. docx_from_pdf/shape/Paths.py +142 -0
  40. docx_from_pdf/shape/Shape.py +365 -0
  41. docx_from_pdf/shape/Shapes.py +241 -0
  42. docx_from_pdf/shape/__init__.py +0 -0
  43. docx_from_pdf/table/Border.py +419 -0
  44. docx_from_pdf/table/Cell.py +165 -0
  45. docx_from_pdf/table/Cells.py +27 -0
  46. docx_from_pdf/table/Row.py +78 -0
  47. docx_from_pdf/table/Rows.py +25 -0
  48. docx_from_pdf/table/TableBlock.py +174 -0
  49. docx_from_pdf/table/TableStructure.py +633 -0
  50. docx_from_pdf/table/TablesConstructor.py +382 -0
  51. docx_from_pdf/table/__init__.py +0 -0
  52. docx_from_pdf/text/Char.py +65 -0
  53. docx_from_pdf/text/Line.py +183 -0
  54. docx_from_pdf/text/Lines.py +281 -0
  55. docx_from_pdf/text/Spans.py +59 -0
  56. docx_from_pdf/text/TextBlock.py +502 -0
  57. docx_from_pdf/text/TextSpan.py +426 -0
  58. docx_from_pdf/text/__init__.py +0 -0
  59. docx_from_pdf-0.1.dist-info/METADATA +103 -0
  60. docx_from_pdf-0.1.dist-info/RECORD +64 -0
  61. docx_from_pdf-0.1.dist-info/WHEEL +5 -0
  62. docx_from_pdf-0.1.dist-info/entry_points.txt +2 -0
  63. docx_from_pdf-0.1.dist-info/licenses/LICENSE +7 -0
  64. docx_from_pdf-0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,3 @@
1
+ from .converter import Converter
2
+ from .page.Page import Page
3
+ from .main import parse
@@ -0,0 +1 @@
1
+ """PDF backend implementations."""
@@ -0,0 +1,440 @@
1
+ """PDFium-backed document and page primitives."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ctypes import byref, c_double, c_float, c_int, c_uint, create_string_buffer
6
+ from io import BytesIO
7
+ from math import cos, pi, sin, sqrt
8
+ from pathlib import Path
9
+
10
+ import pypdfium2 as pdfium
11
+ import pypdfium2.raw as pdfium_c
12
+
13
+ from ..common.geometry import Matrix, Rect
14
+
15
+
16
+ class PdfiumDocument:
17
+ """Document wrapper exposing the subset of behavior used by the converter."""
18
+
19
+ def __init__(self, pdf_file: str | None = None, password: str | None = None, stream: bytes | None = None):
20
+ self._document = pdfium.PdfDocument(stream if stream is not None else Path(pdf_file), password=password or None)
21
+ self.needs_pass = False
22
+
23
+ def __len__(self):
24
+ return len(self._document)
25
+
26
+ def __iter__(self):
27
+ for page_index in range(len(self)):
28
+ yield self[page_index]
29
+
30
+ def __getitem__(self, page_index: int):
31
+ return PdfiumPage(self, page_index)
32
+
33
+ def authenticate(self, password: str):
34
+ return True
35
+
36
+ def close(self):
37
+ self._document.close()
38
+
39
+
40
+ class PdfiumPage:
41
+ """Page wrapper with extraction helpers."""
42
+
43
+ def __init__(self, document: PdfiumDocument, page_index: int):
44
+ self.parent = document
45
+ self.page_index = page_index
46
+ self._page = document._document[page_index]
47
+ self._pdf_cropbox = self._page.get_cropbox()
48
+ crop_left, crop_bottom, crop_right, crop_top = self._pdf_cropbox
49
+ self.width = crop_right - crop_left
50
+ self.height = crop_top - crop_bottom
51
+ self.rect = Rect(0.0, 0.0, self.width, self.height)
52
+ self.cropbox = self.rect
53
+ self.rotation = self._page.get_rotation()
54
+ self.rotation_matrix = Matrix()
55
+
56
+ def close(self):
57
+ self._page.close()
58
+
59
+ def extract_text_blocks(self, sort=None):
60
+ text_page = self._page.get_textpage()
61
+ chars = self._extract_chars(text_page)
62
+ if sort:
63
+ chars.sort(key=lambda char: (char["bbox"][1], char["bbox"][0]))
64
+ lines = self._group_chars_into_lines(chars)
65
+ return [{"type": 0, "bbox": self._bbox_for_lines(lines), "lines": lines}] if lines else []
66
+
67
+ def extract_images(self, clip_image_res_ratio: float = 3.0):
68
+ images = []
69
+ for image_object in self._page.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_IMAGE], max_depth=3):
70
+ bbox = self._pdf_bounds_to_page_rect(image_object.get_bounds())
71
+ if bbox.get_area() <= 4:
72
+ continue
73
+ image_bytes, width, height = self.render_clip_to_png(
74
+ bbox,
75
+ zoom=clip_image_res_ratio,
76
+ rm_text=True,
77
+ rm_image=False,
78
+ )
79
+ images.append(
80
+ {
81
+ "type": 1,
82
+ "bbox": tuple(bbox),
83
+ "width": width,
84
+ "height": height,
85
+ "image": image_bytes,
86
+ }
87
+ )
88
+ return images
89
+
90
+ def extract_paths(self):
91
+ paths = []
92
+ for path_object in self._page.get_objects(filter=[pdfium_c.FPDF_PAGEOBJ_PATH], max_depth=3):
93
+ items = self._extract_path_items(path_object)
94
+ if not items:
95
+ continue
96
+ fill_mode = c_int()
97
+ stroke = c_int()
98
+ pdfium_c.FPDFPath_GetDrawMode(path_object, byref(fill_mode), byref(stroke))
99
+ path_type = ""
100
+ if fill_mode.value:
101
+ path_type += "f"
102
+ if stroke.value:
103
+ path_type += "s"
104
+ if not path_type:
105
+ continue
106
+ stroke_width = c_float()
107
+ pdfium_c.FPDFPageObj_GetStrokeWidth(path_object, byref(stroke_width))
108
+ paths.append(
109
+ {
110
+ "type": path_type,
111
+ "items": items,
112
+ "width": max(0.1, float(stroke_width.value)),
113
+ "color": self._get_object_color(path_object, stroke=True),
114
+ "fill": self._get_object_color(path_object, stroke=False),
115
+ "closePath": any(item[0] == "close" for item in items),
116
+ }
117
+ )
118
+ return paths
119
+
120
+ def render_clip_to_png(self, bbox=None, zoom: float = 3.0, rm_text: bool = False, rm_image: bool = False):
121
+ clip = Rect(bbox) if bbox is not None else self.rect
122
+ crop = (
123
+ max(0.0, clip.x0),
124
+ max(0.0, self.height - clip.y1),
125
+ max(0.0, self.width - clip.x1),
126
+ max(0.0, clip.y0),
127
+ )
128
+ deactivated_objects = self._deactivate_page_objects(rm_text=rm_text, rm_image=rm_image)
129
+ try:
130
+ bitmap = self._page.render(scale=zoom, crop=crop, rev_byteorder=True)
131
+ pil_image = bitmap.to_pil()
132
+ output = BytesIO()
133
+ pil_image.save(output, format="PNG")
134
+ return output.getvalue(), pil_image.width, pil_image.height
135
+ finally:
136
+ for page_object in deactivated_objects:
137
+ pdfium_c.FPDFPageObj_SetIsActive(page_object, 1)
138
+
139
+ def _extract_chars(self, text_page):
140
+ chars = []
141
+ for char_index in range(text_page.count_chars()):
142
+ character = self._xml_compatible_text(text_page.get_text_range(char_index, 1))
143
+ if not character:
144
+ continue
145
+ bbox = self._pdf_bounds_to_page_rect(text_page.get_charbox(char_index, loose=True))
146
+ if bbox.is_empty:
147
+ continue
148
+ origin_x = c_double()
149
+ origin_y = c_double()
150
+ pdfium_c.FPDFText_GetCharOrigin(text_page, char_index, byref(origin_x), byref(origin_y))
151
+ font_name = self._font_name(text_page, char_index)
152
+ font_size = self._effective_font_size(text_page, char_index)
153
+ chars.append(
154
+ {
155
+ "c": character,
156
+ "bbox": tuple(bbox),
157
+ "origin": (float(origin_x.value), self.height - float(origin_y.value)),
158
+ "dir": self._text_direction(text_page, char_index),
159
+ "font": font_name,
160
+ "size": font_size,
161
+ "color": self._text_color(text_page, char_index),
162
+ "flags": self._font_flags(text_page, char_index, font_name),
163
+ }
164
+ )
165
+ return chars
166
+
167
+ def _group_chars_into_lines(self, chars):
168
+ lines = []
169
+ for char in chars:
170
+ for line in reversed(lines[-8:]):
171
+ if line["_dir"] != char["dir"]:
172
+ continue
173
+ if abs(line["_center"] - self._line_center(char)) <= self._line_grouping_threshold(char):
174
+ line["_chars"].append(char)
175
+ line["_center"] = self._weighted_line_center(line["_chars"])
176
+ break
177
+ else:
178
+ lines.append({"_center": self._line_center(char), "_dir": char["dir"], "_chars": [char]})
179
+
180
+ raw_lines = []
181
+ for line in sorted(lines, key=self._line_sort_key):
182
+ line_chars = self._sort_line_chars(line["_chars"], line["_dir"])
183
+ for segment_chars in self._split_line_chars_by_gap(line_chars, line["_dir"]):
184
+ self._add_missing_spaces(segment_chars, line["_dir"])
185
+ spans = self._group_chars_into_spans(segment_chars)
186
+ raw_lines.append({"wmode": 0, "dir": line["_dir"], "bbox": self._bbox_for_spans(spans), "spans": spans})
187
+ return raw_lines
188
+
189
+ @staticmethod
190
+ def _split_line_chars_by_gap(chars, direction):
191
+ if not chars:
192
+ return []
193
+ groups = [[chars[0]]]
194
+ for char in chars[1:]:
195
+ previous_char = groups[-1][-1]
196
+ if direction == (0.0, -1.0):
197
+ gap = previous_char["bbox"][1] - char["bbox"][3]
198
+ else:
199
+ gap = char["bbox"][0] - previous_char["bbox"][2]
200
+ threshold = max(8.0, min(PdfiumPage._char_extent(previous_char), PdfiumPage._char_extent(char)) * 1.2)
201
+ if gap > threshold:
202
+ groups.append([char])
203
+ else:
204
+ groups[-1].append(char)
205
+ return groups
206
+
207
+ @staticmethod
208
+ def _add_missing_spaces(chars, direction):
209
+ for previous_char, char in zip(chars, chars[1:]):
210
+ if previous_char["c"].isspace() or char["c"].isspace():
211
+ continue
212
+ if direction == (0.0, -1.0):
213
+ gap = previous_char["bbox"][1] - char["bbox"][3]
214
+ else:
215
+ gap = char["bbox"][0] - previous_char["bbox"][2]
216
+ threshold = max(1.5, min(PdfiumPage._char_advance_extent(previous_char), PdfiumPage._char_advance_extent(char)) * 0.35)
217
+ if gap > threshold:
218
+ previous_char["c"] += " "
219
+
220
+ @staticmethod
221
+ def _group_chars_into_spans(chars):
222
+ spans = []
223
+ current = None
224
+ for char in chars:
225
+ key = (char["font"], round(char["size"], 2), char["color"], char["flags"])
226
+ if current is None or current["_key"] != key:
227
+ current = {
228
+ "_key": key,
229
+ "font": char["font"],
230
+ "size": char["size"],
231
+ "color": char["color"],
232
+ "flags": char["flags"],
233
+ "chars": [],
234
+ }
235
+ spans.append(current)
236
+ current["chars"].append({"c": char["c"], "bbox": char["bbox"], "origin": char["origin"]})
237
+ for span in spans:
238
+ span["bbox"] = PdfiumPage._bbox_for_chars(span["chars"])
239
+ del span["_key"]
240
+ return spans
241
+
242
+ def _extract_path_items(self, path_object):
243
+ matrix = path_object.get_matrix()
244
+ items = []
245
+ cursor = None
246
+ bezier_points = []
247
+ segment_count = pdfium_c.FPDFPath_CountSegments(path_object)
248
+ for segment_index in range(segment_count):
249
+ segment = pdfium_c.FPDFPath_GetPathSegment(path_object, segment_index)
250
+ segment_type = pdfium_c.FPDFPathSegment_GetType(segment)
251
+ x = c_float()
252
+ y = c_float()
253
+ pdfium_c.FPDFPathSegment_GetPoint(segment, byref(x), byref(y))
254
+ point = self._matrix_point_to_page_point(matrix, x.value, y.value)
255
+ if segment_type == pdfium_c.FPDF_SEGMENT_MOVETO:
256
+ cursor = point
257
+ bezier_points = []
258
+ elif segment_type == pdfium_c.FPDF_SEGMENT_LINETO and cursor is not None:
259
+ items.append(("l", cursor, point))
260
+ cursor = point
261
+ bezier_points = []
262
+ elif segment_type == pdfium_c.FPDF_SEGMENT_BEZIERTO and cursor is not None:
263
+ bezier_points.append(point)
264
+ if len(bezier_points) == 3:
265
+ items.append(("c", cursor, bezier_points[0], bezier_points[1], bezier_points[2]))
266
+ cursor = bezier_points[2]
267
+ bezier_points = []
268
+ if pdfium_c.FPDFPathSegment_GetClose(segment):
269
+ items.append(("close",))
270
+ return items
271
+
272
+ def _deactivate_page_objects(self, rm_text: bool, rm_image: bool):
273
+ if not rm_text and not rm_image:
274
+ return []
275
+ filters = []
276
+ if rm_text:
277
+ filters.append(pdfium_c.FPDF_PAGEOBJ_TEXT)
278
+ if rm_image:
279
+ filters.append(pdfium_c.FPDF_PAGEOBJ_IMAGE)
280
+ deactivated_objects = []
281
+ for page_object in self._page.get_objects(filter=filters, max_depth=3):
282
+ active = c_int()
283
+ pdfium_c.FPDFPageObj_GetIsActive(page_object, byref(active))
284
+ if active.value:
285
+ pdfium_c.FPDFPageObj_SetIsActive(page_object, 0)
286
+ deactivated_objects.append(page_object)
287
+ return deactivated_objects
288
+
289
+ def _matrix_point_to_page_point(self, matrix, x: float, y: float):
290
+ page_x = x * matrix.a + y * matrix.c + matrix.e
291
+ page_y = x * matrix.b + y * matrix.d + matrix.f
292
+ crop_left, _, _, crop_top = self._pdf_cropbox
293
+ return (float(page_x - crop_left), float(crop_top - page_y))
294
+
295
+ def _pdf_bounds_to_page_rect(self, bounds):
296
+ left, bottom, right, top = bounds
297
+ crop_left, _, _, crop_top = self._pdf_cropbox
298
+ return Rect(left - crop_left, crop_top - top, right - crop_left, crop_top - bottom)
299
+
300
+ @staticmethod
301
+ def _effective_font_size(text_page, char_index: int):
302
+ font_size = float(pdfium_c.FPDFText_GetFontSize(text_page, char_index) or 12.0)
303
+ matrix = pdfium_c.FS_MATRIX()
304
+ if not pdfium_c.FPDFText_GetMatrix(text_page, char_index, byref(matrix)):
305
+ return font_size
306
+ vertical_scale = sqrt(matrix.c * matrix.c + matrix.d * matrix.d)
307
+ return font_size * vertical_scale if vertical_scale else font_size
308
+
309
+ @staticmethod
310
+ def _font_name(text_page, char_index: int):
311
+ buffer = create_string_buffer(512)
312
+ flags = c_int()
313
+ length = pdfium_c.FPDFText_GetFontInfo(text_page, char_index, buffer, len(buffer), byref(flags))
314
+ return buffer.value[:length].decode("utf-8", errors="ignore").split("+")[-1] or "Arial"
315
+
316
+ @staticmethod
317
+ def _font_flags(text_page, char_index: int, font_name: str):
318
+ flags = 0
319
+ if "Italic" in font_name or "Oblique" in font_name:
320
+ flags |= 2**1
321
+ if "Bold" in font_name or pdfium_c.FPDFText_GetFontWeight(text_page, char_index) >= 600:
322
+ flags |= 2**4
323
+ return flags
324
+
325
+ @staticmethod
326
+ def _text_direction(text_page, char_index: int):
327
+ angle = float(pdfium_c.FPDFText_GetCharAngle(text_page, char_index)) % (2 * pi)
328
+ if angle <= 0.1 or abs(angle - 2 * pi) <= 0.1:
329
+ return (1.0, 0.0)
330
+ if abs(angle - 1.5 * pi) <= 0.1:
331
+ return (0.0, -1.0)
332
+ return (round(cos(angle), 3), round(sin(angle), 3))
333
+
334
+ @staticmethod
335
+ def _text_color(text_page, char_index: int):
336
+ red = c_uint()
337
+ green = c_uint()
338
+ blue = c_uint()
339
+ alpha = c_uint()
340
+ if not pdfium_c.FPDFText_GetFillColor(text_page, char_index, byref(red), byref(green), byref(blue), byref(alpha)):
341
+ return 0
342
+ return (red.value << 16) + (green.value << 8) + blue.value
343
+
344
+ @staticmethod
345
+ def _get_object_color(path_object, stroke: bool):
346
+ red = c_uint()
347
+ green = c_uint()
348
+ blue = c_uint()
349
+ alpha = c_uint()
350
+ color_function = pdfium_c.FPDFPageObj_GetStrokeColor if stroke else pdfium_c.FPDFPageObj_GetFillColor
351
+ if not color_function(path_object, byref(red), byref(green), byref(blue), byref(alpha)):
352
+ return None
353
+ return (red.value / 255.0, green.value / 255.0, blue.value / 255.0)
354
+
355
+ @staticmethod
356
+ def _xml_compatible_text(text: str):
357
+ return "".join(char for char in text if PdfiumPage._is_xml_compatible_character(char))
358
+
359
+ @staticmethod
360
+ def _is_xml_compatible_character(char: str):
361
+ codepoint = ord(char)
362
+ return (
363
+ codepoint in (0x9, 0xA, 0xD)
364
+ or 0x20 <= codepoint <= 0xD7FF
365
+ or 0xE000 <= codepoint <= 0xFFFD
366
+ or 0x10000 <= codepoint <= 0x10FFFF
367
+ )
368
+
369
+ @staticmethod
370
+ def _vertical_center(bbox):
371
+ return (bbox[1] + bbox[3]) / 2.0
372
+
373
+ @staticmethod
374
+ def _horizontal_center(bbox):
375
+ return (bbox[0] + bbox[2]) / 2.0
376
+
377
+ @staticmethod
378
+ def _line_center(char):
379
+ if char["dir"] == (0.0, -1.0):
380
+ return PdfiumPage._horizontal_center(char["bbox"])
381
+ return PdfiumPage._vertical_center(char["bbox"])
382
+
383
+ @staticmethod
384
+ def _char_extent(char):
385
+ if char["dir"] == (0.0, -1.0):
386
+ return max(1.0, char["bbox"][2] - char["bbox"][0])
387
+ return max(1.0, char["bbox"][3] - char["bbox"][1])
388
+
389
+ @staticmethod
390
+ def _char_advance_extent(char):
391
+ if char["dir"] == (0.0, -1.0):
392
+ return max(1.0, char["bbox"][3] - char["bbox"][1])
393
+ return max(1.0, char["bbox"][2] - char["bbox"][0])
394
+
395
+ @staticmethod
396
+ def _line_grouping_threshold(char):
397
+ return max(1.5, PdfiumPage._char_extent(char) * 0.35)
398
+
399
+ @staticmethod
400
+ def _weighted_line_center(chars):
401
+ if chars[0]["dir"] == (0.0, -1.0):
402
+ return sum(PdfiumPage._horizontal_center(char["bbox"]) * PdfiumPage._char_extent(char) for char in chars) / sum(
403
+ PdfiumPage._char_extent(char) for char in chars
404
+ )
405
+ return sum(PdfiumPage._vertical_center(char["bbox"]) * PdfiumPage._char_extent(char) for char in chars) / sum(
406
+ PdfiumPage._char_extent(char) for char in chars
407
+ )
408
+
409
+ @staticmethod
410
+ def _line_sort_key(line):
411
+ if line["_dir"] == (0.0, -1.0):
412
+ return (min(char["bbox"][0] for char in line["_chars"]), min(char["bbox"][1] for char in line["_chars"]))
413
+ return (line["_center"], min(char["bbox"][0] for char in line["_chars"]))
414
+
415
+ @staticmethod
416
+ def _sort_line_chars(chars, direction):
417
+ if direction == (0.0, -1.0):
418
+ return sorted(chars, key=lambda item: (-item["bbox"][1], item["bbox"][0]))
419
+ return sorted(chars, key=lambda item: item["bbox"][0])
420
+
421
+ @staticmethod
422
+ def _bbox_for_chars(chars):
423
+ bbox = Rect()
424
+ for char in chars:
425
+ bbox |= Rect(char["bbox"])
426
+ return tuple(bbox)
427
+
428
+ @staticmethod
429
+ def _bbox_for_spans(spans):
430
+ bbox = Rect()
431
+ for span in spans:
432
+ bbox |= Rect(span["bbox"])
433
+ return tuple(bbox)
434
+
435
+ @staticmethod
436
+ def _bbox_for_lines(lines):
437
+ bbox = Rect()
438
+ for line in lines:
439
+ bbox |= Rect(line["bbox"])
440
+ return tuple(bbox)
@@ -0,0 +1,144 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ '''Base class for text/image/table blocks.
4
+ '''
5
+
6
+ from .share import BlockType, TextAlignment
7
+ from .Element import Element
8
+
9
+
10
+ class Block(Element):
11
+ '''Base class for text/image/table blocks.
12
+
13
+ Attributes:
14
+ raw (dict): initialize object from raw properties.
15
+ parent (optional): parent object that this block belongs to.
16
+ '''
17
+ def __init__(self, raw:dict=None, parent=None):
18
+ self._type = BlockType.UNDEFINED
19
+
20
+ # horizontal spacing
21
+ if raw is None: raw = {}
22
+ self.alignment = self._get_alignment(raw.get('alignment', 0))
23
+ self.left_space = raw.get('left_space', 0.0)
24
+ self.right_space = raw.get('right_space', 0.0)
25
+ self.first_line_space = raw.get('first_line_space', 0.0)
26
+
27
+ # RELATIVE position of tab stops
28
+ self.tab_stops = raw.get('tab_stops', [])
29
+
30
+ # vertical spacing
31
+ self.before_space = raw.get('before_space', 0.0)
32
+ self.after_space = raw.get('after_space', 0.0)
33
+ self.line_space = raw.get('line_space', 0.0)
34
+ self.line_space_type = raw.get('line_space_type', 1) # 0-exactly, 1-relatively
35
+
36
+ super().__init__(raw, parent)
37
+
38
+
39
+ @property
40
+ def is_text_block(self):
41
+ '''Whether test block.'''
42
+ return self._type==BlockType.TEXT
43
+
44
+ @property
45
+ def is_inline_image_block(self):
46
+ '''Whether inline image block.'''
47
+ return self._type==BlockType.IMAGE
48
+
49
+ @property
50
+ def is_float_image_block(self):
51
+ '''Whether float image block.'''
52
+ return self._type==BlockType.FLOAT_IMAGE
53
+
54
+ @property
55
+ def is_image_block(self):
56
+ '''Whether inline or float image block.'''
57
+ return self.is_inline_image_block or self.is_float_image_block
58
+
59
+ @property
60
+ def is_text_image_block(self):
61
+ '''Whether text block or inline image block.'''
62
+ return self.is_text_block or self.is_inline_image_block
63
+
64
+ @property
65
+ def is_lattice_table_block(self):
66
+ '''Whether lattice table (explicit table borders) block.'''
67
+ return self._type==BlockType.LATTICE_TABLE
68
+
69
+ @property
70
+ def is_stream_table_block(self):
71
+ '''Whether stream table (implied by table content) block.'''
72
+ return self._type==BlockType.STREAM_TABLE
73
+
74
+ @property
75
+ def is_table_block(self):
76
+ '''Whether table (lattice or stream) block.'''
77
+ return self.is_lattice_table_block or self.is_stream_table_block
78
+
79
+ def set_text_block(self):
80
+ '''Set block type.'''
81
+ self._type = BlockType.TEXT
82
+
83
+ def set_inline_image_block(self):
84
+ '''Set block type.'''
85
+ self._type = BlockType.IMAGE
86
+
87
+ def set_float_image_block(self):
88
+ '''Set block type.'''
89
+ self._type = BlockType.FLOAT_IMAGE
90
+
91
+ def set_lattice_table_block(self):
92
+ '''Set block type.'''
93
+ self._type = BlockType.LATTICE_TABLE
94
+
95
+ def set_stream_table_block(self):
96
+ '''Set block type.'''
97
+ self._type = BlockType.STREAM_TABLE
98
+
99
+ def _get_alignment(self, mode:int):
100
+ for t in TextAlignment:
101
+ if t.value==mode:
102
+ return t
103
+ return TextAlignment.LEFT
104
+
105
+ def parse_horizontal_spacing(self, bbox, *args):
106
+ """Set left alignment, and calculate left space.
107
+
108
+ Override by :obj:`docx_from_pdf.text.TextBlock`.
109
+
110
+ Args:
111
+ bbox (Rect): boundary box of this block.
112
+ """
113
+ # NOTE: in PDFium CS, horizontal text direction is same with positive x-axis,
114
+ # while vertical text is on the contrary, so use f = -1 here
115
+ idx, f = (0, 1.0) if self.is_horizontal_text else (3, -1.0)
116
+ self.alignment = TextAlignment.LEFT
117
+ self.left_space = (self.bbox[idx] - bbox[idx]) * f
118
+
119
+
120
+ def store(self):
121
+ '''Store attributes in json format.'''
122
+ res = super().store()
123
+ res.update({
124
+ 'type' : self._type.value,
125
+ 'alignment' : self.alignment.value,
126
+ 'left_space' : self.left_space,
127
+ 'right_space' : self.right_space,
128
+ 'first_line_space' : self.first_line_space,
129
+ 'before_space' : self.before_space,
130
+ 'after_space' : self.after_space,
131
+ 'line_space' : self.line_space,
132
+ 'line_space_type' : self.line_space_type,
133
+ 'tab_stops' : self.tab_stops
134
+ })
135
+ return res
136
+
137
+
138
+ def make_docx(self, *args, **kwargs):
139
+ """Create associated docx element.
140
+
141
+ Raises:
142
+ NotImplementedError
143
+ """
144
+ raise NotImplementedError