PyPDFForm 1.3.4__py3-none-any.whl → 1.4.0__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.
Potentially problematic release.
This version of PyPDFForm might be problematic. Click here for more details.
- PyPDFForm/__init__.py +3 -5
- PyPDFForm/core/constants.py +1 -1
- PyPDFForm/core/coordinate.py +65 -65
- PyPDFForm/core/filler.py +17 -17
- PyPDFForm/core/font.py +38 -42
- PyPDFForm/core/patterns.py +15 -14
- PyPDFForm/core/template.py +95 -72
- PyPDFForm/core/utils.py +53 -36
- PyPDFForm/core/watermark.py +30 -32
- PyPDFForm/middleware/checkbox.py +6 -6
- PyPDFForm/middleware/constants.py +3 -1
- PyPDFForm/middleware/dropdown.py +6 -6
- PyPDFForm/middleware/radio.py +6 -6
- PyPDFForm/middleware/template.py +30 -32
- PyPDFForm/middleware/text.py +6 -6
- PyPDFForm/middleware/{element.py → widget.py} +10 -10
- PyPDFForm/wrapper.py +88 -34
- {PyPDFForm-1.3.4.dist-info → PyPDFForm-1.4.0.dist-info}/METADATA +24 -4
- PyPDFForm-1.4.0.dist-info/RECORD +26 -0
- PyPDFForm-1.3.4.dist-info/RECORD +0 -26
- {PyPDFForm-1.3.4.dist-info → PyPDFForm-1.4.0.dist-info}/LICENSE +0 -0
- {PyPDFForm-1.3.4.dist-info → PyPDFForm-1.4.0.dist-info}/WHEEL +0 -0
- {PyPDFForm-1.3.4.dist-info → PyPDFForm-1.4.0.dist-info}/top_level.txt +0 -0
PyPDFForm/core/template.py
CHANGED
|
@@ -6,19 +6,19 @@ from typing import Dict, List, Tuple, Union
|
|
|
6
6
|
from pdfrw import PdfDict, PdfReader
|
|
7
7
|
from reportlab.pdfbase.pdfmetrics import stringWidth
|
|
8
8
|
|
|
9
|
-
from ..middleware.constants import
|
|
9
|
+
from ..middleware.constants import WIDGET_TYPES
|
|
10
10
|
from ..middleware.text import Text
|
|
11
11
|
from .constants import (ANNOTATION_KEY, ANNOTATION_RECTANGLE_KEY,
|
|
12
12
|
FIELD_FLAG_KEY, NEW_LINE_SYMBOL,
|
|
13
13
|
TEXT_FIELD_MAX_LENGTH_KEY)
|
|
14
|
-
from .patterns import (DROPDOWN_CHOICE_PATTERNS,
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
from .patterns import (DROPDOWN_CHOICE_PATTERNS, TEXT_FIELD_FLAG_PATTERNS,
|
|
15
|
+
WIDGET_ALIGNMENT_PATTERNS, WIDGET_KEY_PATTERNS,
|
|
16
|
+
WIDGET_TYPE_PATTERNS)
|
|
17
17
|
from .utils import find_pattern_match, traverse_pattern
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def
|
|
21
|
-
"""Iterates through a PDF and returns all
|
|
20
|
+
def get_widgets_by_page(pdf: Union[bytes, PdfReader]) -> Dict[int, List[PdfDict]]:
|
|
21
|
+
"""Iterates through a PDF and returns all widgets found grouped by page."""
|
|
22
22
|
|
|
23
23
|
if isinstance(pdf, bytes):
|
|
24
24
|
pdf = PdfReader(fdata=pdf)
|
|
@@ -26,86 +26,86 @@ def get_elements_by_page(pdf: Union[bytes, PdfReader]) -> Dict[int, List[PdfDict
|
|
|
26
26
|
result = {}
|
|
27
27
|
|
|
28
28
|
for i, page in enumerate(pdf.pages):
|
|
29
|
-
|
|
29
|
+
widgets = page[ANNOTATION_KEY]
|
|
30
30
|
result[i + 1] = []
|
|
31
|
-
if
|
|
32
|
-
for
|
|
33
|
-
for each in
|
|
31
|
+
if widgets:
|
|
32
|
+
for widget in widgets:
|
|
33
|
+
for each in WIDGET_TYPE_PATTERNS:
|
|
34
34
|
patterns = each[0]
|
|
35
35
|
check = True
|
|
36
36
|
for pattern in patterns:
|
|
37
|
-
check = check and find_pattern_match(pattern,
|
|
37
|
+
check = check and find_pattern_match(pattern, widget)
|
|
38
38
|
if check:
|
|
39
|
-
result[i + 1].append(
|
|
39
|
+
result[i + 1].append(widget)
|
|
40
40
|
break
|
|
41
41
|
|
|
42
42
|
return result
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def
|
|
46
|
-
"""Finds a PDF
|
|
45
|
+
def get_widget_key(widget: PdfDict) -> Union[str, None]:
|
|
46
|
+
"""Finds a PDF widget's annotated key by pattern matching."""
|
|
47
47
|
|
|
48
48
|
result = None
|
|
49
|
-
for pattern in
|
|
50
|
-
value = traverse_pattern(pattern,
|
|
49
|
+
for pattern in WIDGET_KEY_PATTERNS:
|
|
50
|
+
value = traverse_pattern(pattern, widget)
|
|
51
51
|
if value:
|
|
52
52
|
result = value[1:-1]
|
|
53
53
|
break
|
|
54
54
|
return result
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def
|
|
58
|
-
"""Finds a PDF
|
|
57
|
+
def get_widget_alignment(widget: PdfDict) -> Union[str, None]:
|
|
58
|
+
"""Finds a PDF widget's alignment by pattern matching."""
|
|
59
59
|
|
|
60
60
|
result = None
|
|
61
|
-
for pattern in
|
|
62
|
-
value = traverse_pattern(pattern,
|
|
61
|
+
for pattern in WIDGET_ALIGNMENT_PATTERNS:
|
|
62
|
+
value = traverse_pattern(pattern, widget)
|
|
63
63
|
if value:
|
|
64
64
|
result = value
|
|
65
65
|
break
|
|
66
66
|
return result
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def
|
|
70
|
-
"""Finds a PDF
|
|
69
|
+
def construct_widget(widget: PdfDict, key: str) -> Union[WIDGET_TYPES, None]:
|
|
70
|
+
"""Finds a PDF widget's annotated type by pattern matching."""
|
|
71
71
|
|
|
72
72
|
result = None
|
|
73
|
-
for each in
|
|
73
|
+
for each in WIDGET_TYPE_PATTERNS:
|
|
74
74
|
patterns, _type = each
|
|
75
75
|
check = True
|
|
76
76
|
for pattern in patterns:
|
|
77
|
-
check = check and find_pattern_match(pattern,
|
|
77
|
+
check = check and find_pattern_match(pattern, widget)
|
|
78
78
|
if check:
|
|
79
79
|
result = _type(key)
|
|
80
80
|
break
|
|
81
81
|
return result
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
def get_text_field_max_length(
|
|
84
|
+
def get_text_field_max_length(widget: PdfDict) -> Union[int, None]:
|
|
85
85
|
"""Returns the max length of the text field if presented or None."""
|
|
86
86
|
|
|
87
87
|
return (
|
|
88
|
-
int(
|
|
89
|
-
if TEXT_FIELD_MAX_LENGTH_KEY in
|
|
88
|
+
int(widget[TEXT_FIELD_MAX_LENGTH_KEY])
|
|
89
|
+
if TEXT_FIELD_MAX_LENGTH_KEY in widget
|
|
90
90
|
else None
|
|
91
91
|
)
|
|
92
92
|
|
|
93
93
|
|
|
94
|
-
def is_text_field_comb(
|
|
94
|
+
def is_text_field_comb(widget: PdfDict) -> bool:
|
|
95
95
|
"""Returns true if characters in a text field needs to be formatted into combs."""
|
|
96
96
|
|
|
97
97
|
try:
|
|
98
|
-
return "{0:b}".format(int(
|
|
98
|
+
return "{0:b}".format(int(widget[FIELD_FLAG_KEY]))[::-1][24] == "1"
|
|
99
99
|
except (IndexError, TypeError):
|
|
100
100
|
return False
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
def is_text_multiline(
|
|
103
|
+
def is_text_multiline(widget: PdfDict) -> bool:
|
|
104
104
|
"""Returns true if a text field is a paragraph field."""
|
|
105
105
|
|
|
106
106
|
field_flag = None
|
|
107
107
|
for pattern in TEXT_FIELD_FLAG_PATTERNS:
|
|
108
|
-
field_flag = traverse_pattern(pattern,
|
|
108
|
+
field_flag = traverse_pattern(pattern, widget)
|
|
109
109
|
if field_flag is not None:
|
|
110
110
|
break
|
|
111
111
|
|
|
@@ -118,12 +118,12 @@ def is_text_multiline(element: PdfDict) -> bool:
|
|
|
118
118
|
return False
|
|
119
119
|
|
|
120
120
|
|
|
121
|
-
def get_dropdown_choices(
|
|
121
|
+
def get_dropdown_choices(widget: PdfDict) -> Union[Tuple[str], None]:
|
|
122
122
|
"""Returns string options of a dropdown field."""
|
|
123
123
|
|
|
124
124
|
result = None
|
|
125
125
|
for pattern in DROPDOWN_CHOICE_PATTERNS:
|
|
126
|
-
choices = traverse_pattern(pattern,
|
|
126
|
+
choices = traverse_pattern(pattern, widget)
|
|
127
127
|
if choices:
|
|
128
128
|
result = tuple(
|
|
129
129
|
(each if isinstance(each, str) else str(each[1]))
|
|
@@ -136,46 +136,73 @@ def get_dropdown_choices(element: PdfDict) -> Union[Tuple[str], None]:
|
|
|
136
136
|
return result
|
|
137
137
|
|
|
138
138
|
|
|
139
|
-
def get_char_rect_width(
|
|
139
|
+
def get_char_rect_width(widget: PdfDict, widget_middleware: Text) -> float:
|
|
140
140
|
"""Returns rectangular width of each character for combed text fields."""
|
|
141
141
|
|
|
142
142
|
rect_width = abs(
|
|
143
|
-
float(
|
|
144
|
-
- float(
|
|
143
|
+
float(widget[ANNOTATION_RECTANGLE_KEY][0])
|
|
144
|
+
- float(widget[ANNOTATION_RECTANGLE_KEY][2])
|
|
145
145
|
)
|
|
146
|
-
return rect_width /
|
|
146
|
+
return rect_width / widget_middleware.max_length
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
def get_character_x_paddings(
|
|
149
|
+
def get_character_x_paddings(widget: PdfDict, widget_middleware: Text) -> List[float]:
|
|
150
150
|
"""Returns paddings between characters for combed text fields."""
|
|
151
151
|
|
|
152
|
-
length = min(len(
|
|
153
|
-
char_rect_width = get_char_rect_width(
|
|
152
|
+
length = min(len(widget_middleware.value or ""), widget_middleware.max_length)
|
|
153
|
+
char_rect_width = get_char_rect_width(widget, widget_middleware)
|
|
154
154
|
|
|
155
155
|
result = []
|
|
156
156
|
|
|
157
157
|
current_x = 0
|
|
158
|
-
for char in (
|
|
158
|
+
for char in (widget_middleware.value or "")[:length]:
|
|
159
159
|
current_mid_point = current_x + char_rect_width / 2
|
|
160
160
|
result.append(
|
|
161
161
|
current_mid_point
|
|
162
|
-
- stringWidth(char,
|
|
163
|
-
/ 2
|
|
162
|
+
- stringWidth(char, widget_middleware.font, widget_middleware.font_size) / 2
|
|
164
163
|
)
|
|
165
164
|
current_x += char_rect_width
|
|
166
165
|
|
|
167
166
|
return result
|
|
168
167
|
|
|
169
168
|
|
|
170
|
-
def
|
|
169
|
+
def calculate_wrap_length(widget: PdfDict, widget_middleware: Text, v: str) -> int:
|
|
170
|
+
"""Increments the substring until reaching maximum horizontal width."""
|
|
171
|
+
|
|
172
|
+
width = abs(
|
|
173
|
+
float(widget[ANNOTATION_RECTANGLE_KEY][0])
|
|
174
|
+
- float(widget[ANNOTATION_RECTANGLE_KEY][2])
|
|
175
|
+
)
|
|
176
|
+
value = widget_middleware.value or ""
|
|
177
|
+
value = value.replace(NEW_LINE_SYMBOL, " ")
|
|
178
|
+
|
|
179
|
+
counter = 0
|
|
180
|
+
_width = 0
|
|
181
|
+
while _width <= width and counter < len(value):
|
|
182
|
+
counter += 1
|
|
183
|
+
_width = stringWidth(
|
|
184
|
+
v[:counter],
|
|
185
|
+
widget_middleware.font,
|
|
186
|
+
widget_middleware.font_size,
|
|
187
|
+
)
|
|
188
|
+
return counter - 1
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def get_paragraph_lines(widget: PdfDict, widget_middleware: Text) -> List[str]:
|
|
171
192
|
"""Splits the paragraph field's text to a list of lines."""
|
|
172
193
|
|
|
194
|
+
# pylint: disable=R0912
|
|
173
195
|
lines = []
|
|
174
196
|
result = []
|
|
175
|
-
text_wrap_length =
|
|
176
|
-
value =
|
|
177
|
-
if
|
|
178
|
-
value = value[:
|
|
197
|
+
text_wrap_length = widget_middleware.text_wrap_length
|
|
198
|
+
value = widget_middleware.value or ""
|
|
199
|
+
if widget_middleware.max_length is not None:
|
|
200
|
+
value = value[: widget_middleware.max_length]
|
|
201
|
+
|
|
202
|
+
width = abs(
|
|
203
|
+
float(widget[ANNOTATION_RECTANGLE_KEY][0])
|
|
204
|
+
- float(widget[ANNOTATION_RECTANGLE_KEY][2])
|
|
205
|
+
)
|
|
179
206
|
|
|
180
207
|
split_by_new_line_symbol = value.split(NEW_LINE_SYMBOL)
|
|
181
208
|
for line in split_by_new_line_symbol:
|
|
@@ -194,11 +221,21 @@ def get_paragraph_lines(element_middleware: Text) -> List[str]:
|
|
|
194
221
|
else current_line
|
|
195
222
|
)
|
|
196
223
|
|
|
224
|
+
for line in lines:
|
|
225
|
+
while (
|
|
226
|
+
stringWidth(
|
|
227
|
+
line[:text_wrap_length],
|
|
228
|
+
widget_middleware.font,
|
|
229
|
+
widget_middleware.font_size,
|
|
230
|
+
)
|
|
231
|
+
> width
|
|
232
|
+
):
|
|
233
|
+
text_wrap_length -= 1
|
|
234
|
+
|
|
197
235
|
for each in lines:
|
|
198
236
|
while len(each) > text_wrap_length:
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
each = each[last_index:]
|
|
237
|
+
result.append(each[:text_wrap_length])
|
|
238
|
+
each = each[text_wrap_length:]
|
|
202
239
|
if each:
|
|
203
240
|
if (
|
|
204
241
|
result
|
|
@@ -218,40 +255,26 @@ def get_paragraph_lines(element_middleware: Text) -> List[str]:
|
|
|
218
255
|
return result
|
|
219
256
|
|
|
220
257
|
|
|
221
|
-
def get_paragraph_auto_wrap_length(
|
|
258
|
+
def get_paragraph_auto_wrap_length(widget: PdfDict, widget_middleware: Text) -> int:
|
|
222
259
|
"""Calculates the text wrap length of a paragraph field."""
|
|
223
260
|
|
|
224
|
-
|
|
225
|
-
"""Increments the substring until reaching maximum horizontal width."""
|
|
226
|
-
|
|
227
|
-
counter = 0
|
|
228
|
-
_width = 0
|
|
229
|
-
while _width <= width and counter < len(value):
|
|
230
|
-
counter += 1
|
|
231
|
-
_width = stringWidth(
|
|
232
|
-
v[:counter],
|
|
233
|
-
element_middleware.font,
|
|
234
|
-
element_middleware.font_size,
|
|
235
|
-
)
|
|
236
|
-
return counter - 1
|
|
237
|
-
|
|
238
|
-
value = element_middleware.value or ""
|
|
261
|
+
value = widget_middleware.value or ""
|
|
239
262
|
value = value.replace(NEW_LINE_SYMBOL, " ")
|
|
240
263
|
width = abs(
|
|
241
|
-
float(
|
|
242
|
-
- float(
|
|
264
|
+
float(widget[ANNOTATION_RECTANGLE_KEY][0])
|
|
265
|
+
- float(widget[ANNOTATION_RECTANGLE_KEY][2])
|
|
243
266
|
)
|
|
244
267
|
text_width = stringWidth(
|
|
245
268
|
value,
|
|
246
|
-
|
|
247
|
-
|
|
269
|
+
widget_middleware.font,
|
|
270
|
+
widget_middleware.font_size,
|
|
248
271
|
)
|
|
249
272
|
|
|
250
273
|
lines = text_width / width
|
|
251
274
|
if lines > 1:
|
|
252
275
|
current_min = 0
|
|
253
276
|
while len(value) and current_min < len(value):
|
|
254
|
-
result = calculate_wrap_length(value)
|
|
277
|
+
result = calculate_wrap_length(widget, widget_middleware, value)
|
|
255
278
|
value = value[result:]
|
|
256
279
|
if current_min == 0:
|
|
257
280
|
current_min = result
|
PyPDFForm/core/utils.py
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"""Contains utility helpers."""
|
|
3
3
|
|
|
4
4
|
from io import BytesIO
|
|
5
|
-
from typing import Union
|
|
5
|
+
from typing import List, Union
|
|
6
6
|
|
|
7
7
|
from pdfrw import PdfDict, PdfReader, PdfWriter
|
|
8
8
|
|
|
9
9
|
from ..middleware.checkbox import Checkbox
|
|
10
|
-
from ..middleware.constants import
|
|
10
|
+
from ..middleware.constants import WIDGET_TYPES
|
|
11
11
|
from ..middleware.radio import Radio
|
|
12
12
|
from ..middleware.text import Text
|
|
13
13
|
from .constants import (ANNOTATION_KEY, CHECKBOX_TO_DRAW, DEFAULT_FONT,
|
|
@@ -30,55 +30,72 @@ def generate_stream(pdf: PdfReader) -> bytes:
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def checkbox_radio_to_draw(
|
|
33
|
-
|
|
33
|
+
widget: Union[Checkbox, Radio], font_size: Union[float, int]
|
|
34
34
|
) -> Text:
|
|
35
|
-
"""Converts a checkbox/radio
|
|
35
|
+
"""Converts a checkbox/radio widget to a drawable text widget."""
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
new_widget = Text(
|
|
38
|
+
name=widget.name,
|
|
39
|
+
value="",
|
|
40
40
|
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
new_widget.font = DEFAULT_FONT
|
|
42
|
+
new_widget.font_size = font_size
|
|
43
|
+
new_widget.font_color = DEFAULT_FONT_COLOR
|
|
44
44
|
|
|
45
|
-
if isinstance(
|
|
46
|
-
|
|
47
|
-
elif isinstance(
|
|
48
|
-
|
|
45
|
+
if isinstance(widget, Checkbox):
|
|
46
|
+
new_widget.value = CHECKBOX_TO_DRAW
|
|
47
|
+
elif isinstance(widget, Radio):
|
|
48
|
+
new_widget.value = RADIO_TO_DRAW
|
|
49
49
|
|
|
50
|
-
return
|
|
50
|
+
return new_widget
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def
|
|
54
|
-
"""Converts
|
|
53
|
+
def preview_widget_to_draw(widget: WIDGET_TYPES) -> Text:
|
|
54
|
+
"""Converts a widget to a preview text widget."""
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
new_widget = Text(
|
|
57
|
+
name=widget.name,
|
|
58
|
+
value="{" + f" {widget.name} " + "}",
|
|
59
59
|
)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
new_widget.font = DEFAULT_FONT
|
|
61
|
+
new_widget.font_size = DEFAULT_FONT_SIZE
|
|
62
|
+
new_widget.font_color = PREVIEW_FONT_COLOR
|
|
63
|
+
new_widget.preview = True
|
|
64
64
|
|
|
65
|
-
return
|
|
65
|
+
return new_widget
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
def
|
|
69
|
-
"""Removes all
|
|
68
|
+
def remove_all_widgets(pdf: bytes) -> bytes:
|
|
69
|
+
"""Removes all widgets from a pdfrw parsed PDF form."""
|
|
70
70
|
|
|
71
71
|
pdf = PdfReader(fdata=pdf)
|
|
72
72
|
|
|
73
73
|
for page in pdf.pages:
|
|
74
|
-
|
|
75
|
-
if
|
|
76
|
-
for j in reversed(range(len(
|
|
77
|
-
|
|
74
|
+
widgets = page[ANNOTATION_KEY]
|
|
75
|
+
if widgets:
|
|
76
|
+
for j in reversed(range(len(widgets))):
|
|
77
|
+
widgets.pop(j)
|
|
78
78
|
|
|
79
79
|
return generate_stream(pdf)
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def get_page_streams(pdf: bytes) -> List[bytes]:
|
|
83
|
+
"""Returns a list of streams where each is a page of the input PDF."""
|
|
84
|
+
|
|
85
|
+
pdf = PdfReader(fdata=pdf)
|
|
86
|
+
result = []
|
|
87
|
+
|
|
88
|
+
for page in pdf.pages:
|
|
89
|
+
writer = PdfWriter()
|
|
90
|
+
writer.addPage(page)
|
|
91
|
+
with BytesIO() as f:
|
|
92
|
+
writer.write(f)
|
|
93
|
+
f.seek(0)
|
|
94
|
+
result.append(f.read())
|
|
95
|
+
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
|
|
82
99
|
def merge_two_pdfs(pdf: bytes, other: bytes) -> bytes:
|
|
83
100
|
"""Merges two PDFs into one PDF."""
|
|
84
101
|
|
|
@@ -97,10 +114,10 @@ def merge_two_pdfs(pdf: bytes, other: bytes) -> bytes:
|
|
|
97
114
|
return result
|
|
98
115
|
|
|
99
116
|
|
|
100
|
-
def find_pattern_match(pattern: dict,
|
|
101
|
-
"""Checks if a PDF dict pattern exists in a PDF
|
|
117
|
+
def find_pattern_match(pattern: dict, widget: PdfDict) -> bool:
|
|
118
|
+
"""Checks if a PDF dict pattern exists in a PDF widget."""
|
|
102
119
|
|
|
103
|
-
for key, value in
|
|
120
|
+
for key, value in widget.items():
|
|
104
121
|
result = False
|
|
105
122
|
if key in pattern:
|
|
106
123
|
if isinstance(pattern[key], dict) and isinstance(value, PdfDict):
|
|
@@ -112,10 +129,10 @@ def find_pattern_match(pattern: dict, element: PdfDict) -> bool:
|
|
|
112
129
|
return False
|
|
113
130
|
|
|
114
131
|
|
|
115
|
-
def traverse_pattern(pattern: dict,
|
|
132
|
+
def traverse_pattern(pattern: dict, widget: PdfDict) -> Union[str, list, None]:
|
|
116
133
|
"""Traverses down a PDF dict pattern and find the value."""
|
|
117
134
|
|
|
118
|
-
for key, value in
|
|
135
|
+
for key, value in widget.items():
|
|
119
136
|
result = None
|
|
120
137
|
if key in pattern:
|
|
121
138
|
if isinstance(pattern[key], dict) and isinstance(value, PdfDict):
|
PyPDFForm/core/watermark.py
CHANGED
|
@@ -23,70 +23,68 @@ def draw_text(
|
|
|
23
23
|
) -> None:
|
|
24
24
|
"""Draws a text on the watermark."""
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
canvas = args[0]
|
|
27
|
+
widget = args[1]
|
|
28
28
|
coordinate_x = args[2]
|
|
29
29
|
coordinate_y = args[3]
|
|
30
30
|
|
|
31
|
-
text_to_draw =
|
|
31
|
+
text_to_draw = widget.value
|
|
32
32
|
|
|
33
33
|
if not text_to_draw:
|
|
34
34
|
text_to_draw = ""
|
|
35
35
|
|
|
36
|
-
if
|
|
37
|
-
text_to_draw = text_to_draw[:
|
|
36
|
+
if widget.max_length is not None:
|
|
37
|
+
text_to_draw = text_to_draw[: widget.max_length]
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
canvas.setFont(widget.font, widget.font_size)
|
|
40
|
+
canvas.setFillColorRGB(
|
|
41
|
+
widget.font_color[0], widget.font_color[1], widget.font_color[2]
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
-
if
|
|
44
|
+
if widget.comb is True:
|
|
45
45
|
for i, char in enumerate(text_to_draw):
|
|
46
|
-
|
|
47
|
-
coordinate_x +
|
|
46
|
+
canvas.drawString(
|
|
47
|
+
coordinate_x + widget.character_paddings[i],
|
|
48
48
|
coordinate_y,
|
|
49
49
|
char,
|
|
50
50
|
)
|
|
51
51
|
elif (
|
|
52
|
-
|
|
53
|
-
) and
|
|
54
|
-
|
|
52
|
+
widget.text_wrap_length is None or len(text_to_draw) < widget.text_wrap_length
|
|
53
|
+
) and widget.text_lines is None:
|
|
54
|
+
canvas.drawString(
|
|
55
55
|
coordinate_x,
|
|
56
56
|
coordinate_y,
|
|
57
57
|
text_to_draw,
|
|
58
58
|
)
|
|
59
59
|
else:
|
|
60
|
-
text_obj =
|
|
61
|
-
for i, line in enumerate(
|
|
60
|
+
text_obj = canvas.beginText(0, 0)
|
|
61
|
+
for i, line in enumerate(widget.text_lines):
|
|
62
62
|
cursor_moved = False
|
|
63
63
|
if (
|
|
64
|
-
|
|
65
|
-
and
|
|
64
|
+
widget.text_line_x_coordinates is not None
|
|
65
|
+
and widget.text_line_x_coordinates[i] - coordinate_x != 0
|
|
66
66
|
):
|
|
67
|
-
text_obj.moveCursor(
|
|
68
|
-
element.text_line_x_coordinates[i] - coordinate_x, 0
|
|
69
|
-
)
|
|
67
|
+
text_obj.moveCursor(widget.text_line_x_coordinates[i] - coordinate_x, 0)
|
|
70
68
|
cursor_moved = True
|
|
71
69
|
text_obj.textLine(line)
|
|
72
70
|
if cursor_moved:
|
|
73
71
|
text_obj.moveCursor(
|
|
74
|
-
-1 * (
|
|
72
|
+
-1 * (widget.text_line_x_coordinates[i] - coordinate_x), 0
|
|
75
73
|
)
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
canvas.saveState()
|
|
76
|
+
canvas.translate(
|
|
79
77
|
coordinate_x,
|
|
80
78
|
coordinate_y,
|
|
81
79
|
)
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
canvas.drawText(text_obj)
|
|
81
|
+
canvas.restoreState()
|
|
84
82
|
|
|
85
83
|
|
|
86
84
|
def draw_image(*args: Union[Canvas, bytes, float, int]) -> None:
|
|
87
85
|
"""Draws an image on the watermark."""
|
|
88
86
|
|
|
89
|
-
|
|
87
|
+
canvas = args[0]
|
|
90
88
|
image_stream = args[1]
|
|
91
89
|
coordinate_x = args[2]
|
|
92
90
|
coordinate_y = args[3]
|
|
@@ -97,7 +95,7 @@ def draw_image(*args: Union[Canvas, bytes, float, int]) -> None:
|
|
|
97
95
|
image_buff.write(image_stream)
|
|
98
96
|
image_buff.seek(0)
|
|
99
97
|
|
|
100
|
-
|
|
98
|
+
canvas.drawImage(
|
|
101
99
|
ImageReader(image_buff),
|
|
102
100
|
coordinate_x,
|
|
103
101
|
coordinate_y,
|
|
@@ -129,7 +127,7 @@ def create_watermarks_and_draw(
|
|
|
129
127
|
pdf_file = PdfReader(fdata=pdf)
|
|
130
128
|
buff = BytesIO()
|
|
131
129
|
|
|
132
|
-
|
|
130
|
+
canvas = Canvas(
|
|
133
131
|
buff,
|
|
134
132
|
pagesize=(
|
|
135
133
|
float(pdf_file.pages[page_number - 1].MediaBox[2]),
|
|
@@ -139,12 +137,12 @@ def create_watermarks_and_draw(
|
|
|
139
137
|
|
|
140
138
|
if action_type == "image":
|
|
141
139
|
for each in actions:
|
|
142
|
-
draw_image(*([
|
|
140
|
+
draw_image(*([canvas, *each]))
|
|
143
141
|
elif action_type == "text":
|
|
144
142
|
for each in actions:
|
|
145
|
-
draw_text(*([
|
|
143
|
+
draw_text(*([canvas, *each]))
|
|
146
144
|
|
|
147
|
-
|
|
145
|
+
canvas.save()
|
|
148
146
|
buff.seek(0)
|
|
149
147
|
|
|
150
148
|
watermark = buff.read()
|
PyPDFForm/middleware/checkbox.py
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""Contains checkbox middleware."""
|
|
3
3
|
|
|
4
|
-
from .
|
|
4
|
+
from .widget import Widget
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class Checkbox(
|
|
8
|
-
"""A class to represent a checkbox
|
|
7
|
+
class Checkbox(Widget):
|
|
8
|
+
"""A class to represent a checkbox widget."""
|
|
9
9
|
|
|
10
10
|
def __init__(
|
|
11
11
|
self,
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
name: str,
|
|
13
|
+
value: bool = None,
|
|
14
14
|
) -> None:
|
|
15
15
|
"""Constructs all attributes for the checkbox."""
|
|
16
16
|
|
|
17
|
-
super().__init__(
|
|
17
|
+
super().__init__(name, value)
|
|
18
18
|
|
|
19
19
|
@property
|
|
20
20
|
def schema_definition(self) -> dict:
|
PyPDFForm/middleware/dropdown.py
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""Contains dropdown middleware."""
|
|
3
3
|
|
|
4
|
-
from .
|
|
4
|
+
from .widget import Widget
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
class Dropdown(
|
|
8
|
-
"""A class to represent a dropdown
|
|
7
|
+
class Dropdown(Widget):
|
|
8
|
+
"""A class to represent a dropdown widget."""
|
|
9
9
|
|
|
10
10
|
def __init__(
|
|
11
11
|
self,
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
name: str,
|
|
13
|
+
value: int = None,
|
|
14
14
|
) -> None:
|
|
15
15
|
"""Constructs all attributes for the dropdown."""
|
|
16
16
|
|
|
17
|
-
super().__init__(
|
|
17
|
+
super().__init__(name, value)
|
|
18
18
|
|
|
19
19
|
self.choices = None
|
|
20
20
|
|