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.
- {pdftext-0.1.2 → pdftext-0.2.0}/PKG-INFO +3 -1
- {pdftext-0.1.2 → pdftext-0.2.0}/README.md +2 -0
- pdftext-0.2.0/models/dt.joblib +0 -0
- {pdftext-0.1.2 → pdftext-0.2.0}/pdftext/extraction.py +3 -13
- {pdftext-0.1.2 → pdftext-0.2.0}/pdftext/inference.py +8 -4
- {pdftext-0.1.2 → pdftext-0.2.0}/pdftext/pdf/chars.py +22 -7
- pdftext-0.2.0/pdftext/pdf/utils.py +148 -0
- {pdftext-0.1.2 → pdftext-0.2.0}/pyproject.toml +1 -1
- pdftext-0.1.2/models/dt.joblib +0 -0
- pdftext-0.1.2/pdftext/pdf/utils.py +0 -102
- {pdftext-0.1.2 → pdftext-0.2.0}/LICENSE +0 -0
- {pdftext-0.1.2 → pdftext-0.2.0}/extract_text.py +0 -0
- {pdftext-0.1.2 → pdftext-0.2.0}/pdftext/model.py +0 -0
- {pdftext-0.1.2 → pdftext-0.2.0}/pdftext/postprocessing.py +0 -0
- {pdftext-0.1.2 → pdftext-0.2.0}/pdftext/settings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pdftext
|
|
3
|
-
Version: 0.
|
|
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["
|
|
43
|
+
char["bbox"] = unnormalize_bbox(char["bbox"], page["width"], page["height"])
|
|
45
44
|
char["char"] = postprocess_text(char["char"])
|
|
46
|
-
|
|
47
|
-
|
|
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 = {
|
|
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
|
-
|
|
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":
|
|
44
|
-
"bbox":
|
|
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=
|
|
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
|
pdftext-0.1.2/models/dt.joblib
DELETED
|
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
|
|
File without changes
|