pdftext 0.1.2__tar.gz → 0.2.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pdftext
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Extract structured text from pdfs quickly
5
5
  Home-page: https://github.com/VikParuchuri/pdftext
6
6
  License: Apache-2.0
@@ -82,6 +82,8 @@ The output will be a json list, with each item in the list corresponding to a si
82
82
  - `name` - font name, may be None
83
83
  - `flags` - font flags, in the format of the `PDF spec 1.7 Section 5.7.1 Font Descriptor Flags`
84
84
 
85
+ If the pdf is rotated, the bboxes will be relative to the rotated page (they're rotated after being extracted).
86
+
85
87
  # Programmatic usage
86
88
 
87
89
  Extract plain text:
@@ -59,6 +59,8 @@ The output will be a json list, with each item in the list corresponding to a si
59
59
  - `name` - font name, may be None
60
60
  - `flags` - font flags, in the format of the `PDF spec 1.7 Section 5.7.1 Font Descriptor Flags`
61
61
 
62
+ If the pdf is rotated, the bboxes will be relative to the rotated page (they're rotated after being extracted).
63
+
62
64
  # Programmatic usage
63
65
 
64
66
  Extract plain text:
Binary file
@@ -36,24 +36,14 @@ def dictionary_output(pdf_path, sort=False, model=None):
36
36
  for key in bad_keys:
37
37
  del block[key]
38
38
  for line in block["lines"]:
39
- line_box = None
40
39
  bad_keys = [key for key in line.keys() if key not in ["chars", "bbox"]]
41
40
  for key in bad_keys:
42
41
  del line[key]
43
42
  for char in line["chars"]:
44
- char["bbox"] = unnormalize_bbox(char["bbox"], page["bbox"])
43
+ char["bbox"] = unnormalize_bbox(char["bbox"], page["width"], page["height"])
45
44
  char["char"] = postprocess_text(char["char"])
46
- if line_box is None:
47
- line_box = char["bbox"]
48
- else:
49
- line_box = [
50
- min(line_box[0], char["bbox"][0]),
51
- min(line_box[1], char["bbox"][1]),
52
- max(line_box[2], char["bbox"][2]),
53
- max(line_box[3], char["bbox"][3]),
54
- ]
55
- line["bbox"] = line_box
56
- block["bbox"] = unnormalize_bbox(block["bbox"], page["bbox"])
45
+ line["bbox"] = unnormalize_bbox(line["bbox"], page["width"], page["height"])
46
+ block["bbox"] = unnormalize_bbox(block["bbox"], page["width"], page["height"])
57
47
  if sort:
58
48
  page["blocks"] = sort_blocks(page["blocks"])
59
49
  return pages
@@ -90,7 +90,14 @@ def update_block(blocks, block):
90
90
  def infer_single_page(text_chars):
91
91
  prev_char = None
92
92
 
93
- blocks = {"blocks": []}
93
+ blocks = {
94
+ "blocks": [],
95
+ "page": text_chars["page"],
96
+ "rotation": text_chars["rotation"],
97
+ "bbox": text_chars["bbox"],
98
+ "width": text_chars["width"],
99
+ "height": text_chars["height"],
100
+ }
94
101
  block = {"lines": []}
95
102
  line = {"spans": []}
96
103
  span = {"chars": []}
@@ -125,9 +132,6 @@ def infer_single_page(text_chars):
125
132
  if len(block["lines"]) > 0:
126
133
  update_block(blocks, block)
127
134
 
128
- blocks["page"] = text_chars["page"]
129
- blocks["rotation"] = text_chars["rotation"]
130
- blocks["bbox"] = text_chars["bbox"]
131
135
  return blocks
132
136
 
133
137
 
@@ -31,17 +31,32 @@ def get_pdfium_chars(pdf_path, fontname_sample_freq=settings.FONTNAME_SAMPLE_FRE
31
31
  page = pdf.get_page(page_idx)
32
32
  text_page = page.get_textpage()
33
33
  mediabox = page.get_mediabox()
34
- bl_origin = mediabox[0] == 0 and mediabox[1] == 0
35
-
34
+ page_rotation = page.get_rotation()
36
35
  bbox = page.get_bbox()
37
- page_width = math.ceil(bbox[2] - bbox[0])
36
+ page_width = math.ceil(abs(bbox[2] - bbox[0]))
37
+ page_height = math.ceil(abs(bbox[1] - bbox[3]))
38
+ bbox = pdfium_page_bbox_to_device_bbox(page, bbox, page_width, page_height, page_rotation)
39
+
40
+ # Recalculate page width and height with new bboxes
41
+ page_width = math.ceil(abs(bbox[2] - bbox[0]))
38
42
  page_height = math.ceil(abs(bbox[1] - bbox[3]))
39
43
 
44
+ # Flip width and height if rotated
45
+ if page_rotation == 90 or page_rotation == 270:
46
+ page_width, page_height = page_height, page_width
47
+
48
+ bl_origin = all([
49
+ mediabox[0] == 0,
50
+ mediabox[1] == 0
51
+ ])
52
+
40
53
  text_chars = {
41
54
  "chars": [],
42
55
  "page": page_idx,
43
- "rotation": page.get_rotation(),
44
- "bbox": pdfium_page_bbox_to_device_bbox(page, bbox, page_width, page_height)
56
+ "rotation": page_rotation,
57
+ "bbox": bbox,
58
+ "width": page_width,
59
+ "height": page_height,
45
60
  }
46
61
 
47
62
  fontname = None
@@ -59,8 +74,8 @@ def get_pdfium_chars(pdf_path, fontname_sample_freq=settings.FONTNAME_SAMPLE_FRE
59
74
 
60
75
  rotation = pdfium_c.FPDFText_GetCharAngle(text_page, i)
61
76
  rotation = rotation * 180 / math.pi # convert from radians to degrees
62
- coords = text_page.get_charbox(i, loose=True)
63
- device_coords = page_bbox_to_device_bbox(page, coords, page_width, page_height, bl_origin, normalize=True)
77
+ coords = text_page.get_charbox(i, loose=False)
78
+ device_coords = page_bbox_to_device_bbox(page, coords, page_width, page_height, bl_origin, page_rotation, normalize=True)
64
79
 
65
80
  char_info = {
66
81
  "font": {
@@ -0,0 +1,148 @@
1
+ import pypdfium2.raw as pdfium_c
2
+ import ctypes
3
+ import math
4
+
5
+ from pdftext.settings import settings
6
+
7
+ LINE_BREAKS = ["\n", "\u000D", "\u000A", "\u000C"]
8
+ TABS = ["\t", "\u0009"]
9
+ SPACES = [" ", "\ufffe", "\uFEFF", "\xa0"]
10
+ HYPHEN = "-"
11
+ WHITESPACE_CHARS = ["\n", "\r", "\f", "\t", " "]
12
+ LIGATURES = {
13
+ "ff": "ff",
14
+ "ffi": "ffi",
15
+ "ffl": "ffl",
16
+ "fi": "fi",
17
+ "fl": "fl",
18
+ "st": "st",
19
+ "ſt": "st",
20
+ }
21
+
22
+
23
+ def char_count(textpage, *rect):
24
+ args = (textpage, *rect)
25
+ n_chars = pdfium_c.FPDFText_GetBoundedText(*args, None, 0)
26
+ if n_chars <= 0:
27
+ return 0
28
+ return n_chars
29
+
30
+
31
+ def normalize_bbox(bbox, page_bound):
32
+ x1, y1, x2, y2 = bbox
33
+ x1 = x1 / page_bound[2]
34
+ y1 = y1 / page_bound[3]
35
+ x2 = x2 / page_bound[2]
36
+ y2 = y2 / page_bound[3]
37
+ return x1, y1, x2, y2
38
+
39
+
40
+ def unnormalize_bbox(bbox, page_width, page_height):
41
+ x1, y1, x2, y2 = bbox
42
+ x1 = round(x1 * page_width, 1)
43
+ y1 = round(y1 * page_height, 1)
44
+ x2 = round(x2 * page_width, 1)
45
+ y2 = round(y2 * page_height, 1)
46
+ return x1, y1, x2, y2
47
+
48
+
49
+ def get_fontname(textpage, char_index):
50
+ n_bytes = settings.FONT_BUFFER_SIZE
51
+ buffer = ctypes.create_string_buffer(n_bytes)
52
+ # Re-interpret the type from char to unsigned short as required by the function
53
+ buffer_ptr = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_ushort))
54
+ flag_buffer = ctypes.c_int()
55
+ flag_ptr = ctypes.pointer(flag_buffer)
56
+ font_info = pdfium_c.FPDFText_GetFontInfo(textpage, char_index, buffer_ptr, n_bytes, flag_ptr)
57
+ if font_info == 0:
58
+ return None, None
59
+ try:
60
+ decoded = buffer.value.decode("utf-8")
61
+ except Exception as e:
62
+ return None, None
63
+ return decoded, flag_buffer.value
64
+
65
+
66
+ def page_to_device(page, x, y, page_width, page_height, page_rotation: int):
67
+ if page_rotation == 90:
68
+ page_rotation = 1
69
+ elif page_rotation == 180:
70
+ page_rotation = 2
71
+ elif page_rotation == 270:
72
+ page_rotation = 3
73
+ else:
74
+ page_rotation = 0
75
+ device_x = ctypes.c_int()
76
+ device_y = ctypes.c_int()
77
+ device_x_ptr = ctypes.pointer(device_x)
78
+ device_y_ptr = ctypes.pointer(device_y)
79
+ width = math.ceil(page_width)
80
+ height = math.ceil(page_height)
81
+ pdfium_c.FPDF_PageToDevice(page, 0, 0, width, height, page_rotation, x, y, device_x_ptr, device_y_ptr)
82
+ x = device_x.value
83
+ y = device_y.value
84
+ return x, y
85
+
86
+
87
+ def pdfium_page_bbox_to_device_bbox(page, bbox, page_width, page_height, page_rotation):
88
+ left_bottom = page_to_device(page, *bbox[:2], page_width, page_height, page_rotation)
89
+ top_right = page_to_device(page, *bbox[2:], page_width, page_height, page_rotation)
90
+
91
+ dev_bbox = [left_bottom[0], top_right[1], top_right[0], left_bottom[1]]
92
+ return dev_bbox
93
+
94
+
95
+ def fast_page_bbox_to_device_bbox(page, bbox, page_width, page_height):
96
+ left, bottom, right, top = bbox
97
+
98
+ dev_bbox = [left, page_height-top, right, page_height-bottom]
99
+ return dev_bbox
100
+
101
+
102
+ def page_bbox_to_device_bbox(page, bbox, page_width: int, page_height: int, bl_origin: bool, page_rotation: int, normalize=False):
103
+ orig_page_height, orig_page_width = page_height, page_width
104
+ if page_rotation in [90, 270]:
105
+ orig_page_height, orig_page_width = page_width, page_height
106
+
107
+ if bl_origin:
108
+ bbox = fast_page_bbox_to_device_bbox(page, bbox, page_width, page_height)
109
+ if page_rotation > 0:
110
+ bbox = rotate_page_bbox(bbox, page_rotation, page_width, page_height)
111
+ else:
112
+ bbox = pdfium_page_bbox_to_device_bbox(page, bbox, orig_page_width, orig_page_height, page_rotation)
113
+ if page_rotation > 0:
114
+ bbox = rotate_pdfium_bbox(bbox, page_rotation, page_width, page_height)
115
+
116
+ if normalize:
117
+ bbox = [bbox[0] / page_width, bbox[1] / page_height, bbox[2] / page_width, bbox[3] / page_height]
118
+ return bbox
119
+
120
+
121
+ def rotate_pdfium_bbox(bbox, angle_deg, width, height):
122
+ x1, y1, x2, y2 = bbox
123
+ if angle_deg == 90:
124
+ bbox = [y1, x1, y2, x2]
125
+ bbox = [bbox[2], height - bbox[1], bbox[0], height - bbox[3]]
126
+ elif angle_deg == 180:
127
+ bbox = [x2, y2, x1, y1]
128
+ bbox = [width - bbox[0], height - bbox[1], width - bbox[2], height - bbox[3]]
129
+ elif angle_deg == 270:
130
+ bbox = rotate_pdfium_bbox(bbox, 90, width, height)
131
+ bbox = rotate_pdfium_bbox(bbox, 180, width, height)
132
+
133
+ return bbox
134
+
135
+
136
+ def rotate_page_bbox(bbox, angle_deg, width, height):
137
+ x1, y1, x2, y2 = bbox
138
+ if angle_deg == 90:
139
+ bbox = [y1, x1, y2, x2]
140
+ bbox = [height - bbox[2], bbox[1], height - bbox[0], bbox[3]]
141
+ elif angle_deg == 180:
142
+ bbox = [x2, y2, x1, y1]
143
+ bbox = [width - bbox[0], height - bbox[1], width - bbox[2], height - bbox[3]]
144
+ elif angle_deg == 270:
145
+ bbox = rotate_page_bbox(bbox, 90, width, height)
146
+ bbox = rotate_page_bbox(bbox, 180, width, height)
147
+
148
+ return bbox
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pdftext"
3
- version = "0.1.2"
3
+ version = "0.2.0"
4
4
  description = "Extract structured text from pdfs quickly"
5
5
  authors = ["Vik Paruchuri <vik.paruchuri@gmail.com>"]
6
6
  license = "Apache-2.0"
Binary file
@@ -1,102 +0,0 @@
1
- import pypdfium2.raw as pdfium_c
2
- import ctypes
3
- import math
4
-
5
- from pdftext.settings import settings
6
-
7
- LINE_BREAKS = ["\n", "\u000D", "\u000A", "\u000C"]
8
- TABS = ["\t", "\u0009"]
9
- SPACES = [" ", "\ufffe", "\uFEFF", "\xa0"]
10
- HYPHEN = "-"
11
- WHITESPACE_CHARS = ["\n", "\r", "\f", "\t", " "]
12
- LIGATURES = {
13
- "ff": "ff",
14
- "ffi": "ffi",
15
- "ffl": "ffl",
16
- "fi": "fi",
17
- "fl": "fl",
18
- "st": "st",
19
- "ſt": "st",
20
- }
21
-
22
-
23
- def char_count(textpage, *rect):
24
- args = (textpage, *rect)
25
- n_chars = pdfium_c.FPDFText_GetBoundedText(*args, None, 0)
26
- if n_chars <= 0:
27
- return 0
28
- return n_chars
29
-
30
-
31
- def normalize_bbox(bbox, page_bound):
32
- x1, y1, x2, y2 = bbox
33
- x1 = x1 / page_bound[2]
34
- y1 = y1 / page_bound[3]
35
- x2 = x2 / page_bound[2]
36
- y2 = y2 / page_bound[3]
37
- return x1, y1, x2, y2
38
-
39
-
40
- def unnormalize_bbox(bbox, page_bound):
41
- x1, y1, x2, y2 = bbox
42
- x1 = round(x1 * page_bound[2], 1)
43
- y1 = round(y1 * page_bound[3], 1)
44
- x2 = round(x2 * page_bound[2], 1)
45
- y2 = round(y2 * page_bound[3], 1)
46
- return x1, y1, x2, y2
47
-
48
-
49
- def get_fontname(textpage, char_index):
50
- n_bytes = settings.FONT_BUFFER_SIZE
51
- buffer = ctypes.create_string_buffer(n_bytes)
52
- # Re-interpret the type from char to unsigned short as required by the function
53
- buffer_ptr = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_ushort))
54
- flag_buffer = ctypes.c_int()
55
- flag_ptr = ctypes.pointer(flag_buffer)
56
- font_info = pdfium_c.FPDFText_GetFontInfo(textpage, char_index, buffer_ptr, n_bytes, flag_ptr)
57
- if font_info == 0:
58
- return None, None
59
- try:
60
- decoded = buffer.value.decode("utf-8")
61
- except Exception as e:
62
- return None, None
63
- return decoded, flag_buffer.value
64
-
65
-
66
- def page_to_device(page, x, y, page_width, page_height):
67
- device_x = ctypes.c_int()
68
- device_y = ctypes.c_int()
69
- device_x_ptr = ctypes.pointer(device_x)
70
- device_y_ptr = ctypes.pointer(device_y)
71
- rotation = pdfium_c.FPDFPage_GetRotation(page)
72
- width = math.ceil(page_width)
73
- height = math.ceil(page_height)
74
- pdfium_c.FPDF_PageToDevice(page, 0, 0, width, height, rotation, x, y, device_x_ptr, device_y_ptr)
75
- x = device_x.value
76
- y = device_y.value
77
- return x, y
78
-
79
-
80
- def pdfium_page_bbox_to_device_bbox(page, bbox, page_width, page_height, normalize=False):
81
- left_bottom = page_to_device(page, *bbox[:2], page_width, page_height)
82
- top_right = page_to_device(page, *bbox[2:], page_width, page_height)
83
-
84
- dev_bbox = [left_bottom[0], top_right[1], top_right[0], left_bottom[1]]
85
- if normalize:
86
- dev_bbox = [dev_bbox[0] / page_width, dev_bbox[1] / page_height, dev_bbox[2] / page_width, dev_bbox[3] / page_height]
87
- return dev_bbox
88
-
89
-
90
- def fast_page_bbox_to_device_bbox(page, bbox, page_width, page_height, normalize=False):
91
- left, bottom, right, top = bbox
92
-
93
- dev_bbox = [left, page_height-top, right, page_height-bottom]
94
- if normalize:
95
- dev_bbox = [dev_bbox[0] / page_width, dev_bbox[1] / page_height, dev_bbox[2] / page_width, dev_bbox[3] / page_height]
96
- return dev_bbox
97
-
98
-
99
- def page_bbox_to_device_bbox(page, bbox, page_width: int, page_height: int, bl_origin: bool, normalize=False):
100
- if bl_origin:
101
- return fast_page_bbox_to_device_bbox(page, bbox, page_width, page_height, normalize)
102
- return pdfium_page_bbox_to_device_bbox(page, bbox, page_width, page_height, normalize)
File without changes
File without changes
File without changes
File without changes