PyPDFForm 2.4.0__py3-none-any.whl → 3.0.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 +22 -6
- PyPDFForm/adapter.py +28 -26
- PyPDFForm/constants.py +29 -34
- PyPDFForm/coordinate.py +23 -399
- PyPDFForm/filler.py +79 -303
- PyPDFForm/font.py +166 -164
- PyPDFForm/hooks.py +256 -0
- PyPDFForm/image.py +72 -22
- PyPDFForm/middleware/base.py +54 -48
- PyPDFForm/middleware/checkbox.py +29 -56
- PyPDFForm/middleware/dropdown.py +41 -30
- PyPDFForm/middleware/image.py +10 -22
- PyPDFForm/middleware/radio.py +30 -31
- PyPDFForm/middleware/signature.py +32 -47
- PyPDFForm/middleware/text.py +59 -48
- PyPDFForm/patterns.py +61 -141
- PyPDFForm/template.py +80 -427
- PyPDFForm/utils.py +142 -128
- PyPDFForm/watermark.py +77 -208
- PyPDFForm/widgets/base.py +57 -76
- PyPDFForm/widgets/checkbox.py +18 -21
- PyPDFForm/widgets/dropdown.py +18 -25
- PyPDFForm/widgets/image.py +11 -9
- PyPDFForm/widgets/radio.py +25 -35
- PyPDFForm/widgets/signature.py +29 -40
- PyPDFForm/widgets/text.py +18 -17
- PyPDFForm/wrapper.py +373 -437
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/METADATA +6 -7
- pypdfform-3.0.0.dist-info/RECORD +35 -0
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/WHEEL +1 -1
- pypdfform-2.4.0.dist-info/RECORD +0 -34
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/top_level.txt +0 -0
PyPDFForm/template.py
CHANGED
|
@@ -1,90 +1,52 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- Supporting comb fields and multiline text
|
|
2
|
+
"""
|
|
3
|
+
Module for handling PDF form templates.
|
|
4
|
+
|
|
5
|
+
This module provides functionalities to extract, build, and update widgets
|
|
6
|
+
in PDF form templates. It leverages the pypdf library for PDF manipulation
|
|
7
|
+
and defines specific patterns for identifying and constructing different
|
|
8
|
+
types of widgets.
|
|
10
9
|
"""
|
|
11
10
|
|
|
12
11
|
from functools import lru_cache
|
|
13
12
|
from io import BytesIO
|
|
14
|
-
from sys import maxsize
|
|
15
13
|
from typing import Dict, List, Tuple, Union, cast
|
|
16
14
|
|
|
17
15
|
from pypdf import PdfReader, PdfWriter
|
|
18
16
|
from pypdf.generic import DictionaryObject
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
from .constants import (COMB, DEFAULT_BORDER_WIDTH, DEFAULT_FONT_SIZE,
|
|
22
|
-
MULTILINE, NEW_LINE_SYMBOL, WIDGET_TYPES, Annots,
|
|
23
|
-
MaxLen, Parent, Rect, T)
|
|
24
|
-
from .font import (adjust_paragraph_font_size, adjust_text_field_font_size,
|
|
25
|
-
auto_detect_font, get_text_field_font_color,
|
|
26
|
-
get_text_field_font_size, text_field_font_size)
|
|
27
|
-
from .middleware.checkbox import Checkbox
|
|
17
|
+
|
|
18
|
+
from .constants import WIDGET_TYPES, Annots, MaxLen, Parent, T
|
|
28
19
|
from .middleware.dropdown import Dropdown
|
|
29
20
|
from .middleware.radio import Radio
|
|
30
21
|
from .middleware.text import Text
|
|
31
|
-
from .patterns import (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
WIDGET_DESCRIPTION_PATTERNS, WIDGET_KEY_PATTERNS,
|
|
36
|
-
WIDGET_TYPE_PATTERNS, update_annotation_name)
|
|
37
|
-
from .utils import (extract_widget_property, find_pattern_match, handle_color,
|
|
38
|
-
stream_to_io)
|
|
22
|
+
from .patterns import (DROPDOWN_CHOICE_PATTERNS, WIDGET_DESCRIPTION_PATTERNS,
|
|
23
|
+
WIDGET_KEY_PATTERNS, WIDGET_TYPE_PATTERNS,
|
|
24
|
+
update_annotation_name)
|
|
25
|
+
from .utils import extract_widget_property, find_pattern_match, stream_to_io
|
|
39
26
|
|
|
40
27
|
|
|
41
|
-
def
|
|
28
|
+
def build_widgets(
|
|
42
29
|
pdf_stream: bytes,
|
|
43
|
-
widgets: Dict[str, WIDGET_TYPES],
|
|
44
30
|
use_full_widget_name: bool,
|
|
45
31
|
) -> Dict[str, WIDGET_TYPES]:
|
|
46
|
-
"""Calculates and sets character spacing for comb text fields in PDF forms.
|
|
47
|
-
|
|
48
|
-
This function processes each widget in the PDF form and calculates the horizontal spacing
|
|
49
|
-
between characters for comb text fields (fixed-width text fields). The spacing is stored
|
|
50
|
-
in the widget's character_paddings property.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
pdf_stream (bytes): PDF form as bytes
|
|
54
|
-
widgets (Dict[str, WIDGET_TYPES]): Dictionary of widget middleware objects
|
|
55
|
-
use_full_widget_name (bool): Whether to use full widget names including parent names
|
|
56
|
-
|
|
57
|
-
Returns:
|
|
58
|
-
Dict[str, WIDGET_TYPES]: Updated widget dictionary with character paddings set for comb fields
|
|
59
32
|
"""
|
|
33
|
+
Builds a dictionary of widgets from a PDF stream.
|
|
60
34
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if isinstance(_widget, Text) and _widget.comb is True:
|
|
67
|
-
_widget.character_paddings = get_character_x_paddings(widget, _widget)
|
|
68
|
-
|
|
69
|
-
return widgets
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def build_widgets(
|
|
73
|
-
pdf_stream: bytes,
|
|
74
|
-
use_full_widget_name: bool,
|
|
75
|
-
render_widgets: bool,
|
|
76
|
-
) -> Dict[str, WIDGET_TYPES]:
|
|
77
|
-
"""Constructs widget middleware objects from a PDF form.
|
|
35
|
+
This function parses a PDF stream to identify and construct widgets
|
|
36
|
+
present in the PDF form. It iterates through each page and its annotations,
|
|
37
|
+
extracting widget properties such as key, description, max length (for text fields),
|
|
38
|
+
and choices (for dropdowns). The constructed widgets are stored in a dictionary
|
|
39
|
+
where the keys are the widget keys and the values are the widget objects.
|
|
78
40
|
|
|
79
41
|
Args:
|
|
80
|
-
pdf_stream: PDF
|
|
81
|
-
use_full_widget_name: Whether to
|
|
82
|
-
|
|
42
|
+
pdf_stream (bytes): The PDF stream to parse.
|
|
43
|
+
use_full_widget_name (bool): Whether to use the full widget name
|
|
44
|
+
(including parent names) as the widget key.
|
|
83
45
|
|
|
84
46
|
Returns:
|
|
85
|
-
Dict[str, WIDGET_TYPES]:
|
|
47
|
+
Dict[str, WIDGET_TYPES]: A dictionary of widgets, where keys are widget
|
|
48
|
+
keys and values are widget objects.
|
|
86
49
|
"""
|
|
87
|
-
|
|
88
50
|
results = {}
|
|
89
51
|
|
|
90
52
|
for widgets in get_widgets_by_page(pdf_stream).values():
|
|
@@ -92,47 +54,24 @@ def build_widgets(
|
|
|
92
54
|
key = get_widget_key(widget, use_full_widget_name)
|
|
93
55
|
_widget = construct_widget(widget, key)
|
|
94
56
|
if _widget is not None:
|
|
95
|
-
|
|
96
|
-
_widget.render_widget = render_widgets
|
|
97
57
|
_widget.desc = extract_widget_property(
|
|
98
58
|
widget, WIDGET_DESCRIPTION_PATTERNS, None, str
|
|
99
59
|
)
|
|
100
|
-
_widget.border_color = extract_widget_property(
|
|
101
|
-
widget, BORDER_COLOR_PATTERNS, None, handle_color
|
|
102
|
-
)
|
|
103
|
-
_widget.background_color = extract_widget_property(
|
|
104
|
-
widget, BACKGROUND_COLOR_PATTERNS, None, handle_color
|
|
105
|
-
)
|
|
106
|
-
_widget.border_width = extract_widget_property(
|
|
107
|
-
widget, BORDER_WIDTH_PATTERNS, DEFAULT_BORDER_WIDTH, float
|
|
108
|
-
)
|
|
109
|
-
_widget.border_style = extract_widget_property(
|
|
110
|
-
widget, BORDER_STYLE_PATTERNS, None, str
|
|
111
|
-
)
|
|
112
|
-
_widget.dash_array = extract_widget_property(
|
|
113
|
-
widget, BORDER_DASH_ARRAY_PATTERNS, None, list
|
|
114
|
-
)
|
|
115
60
|
|
|
116
61
|
if isinstance(_widget, Text):
|
|
62
|
+
# mostly for schema for now
|
|
117
63
|
_widget.max_length = get_text_field_max_length(widget)
|
|
118
|
-
if _widget.max_length is not None and is_text_field_comb(widget):
|
|
119
|
-
_widget.comb = True
|
|
120
|
-
|
|
121
|
-
if isinstance(_widget, (Checkbox, Radio)):
|
|
122
|
-
_widget.button_style = (
|
|
123
|
-
extract_widget_property(
|
|
124
|
-
widget, BUTTON_STYLE_PATTERNS, None, str
|
|
125
|
-
)
|
|
126
|
-
or _widget.button_style
|
|
127
|
-
)
|
|
128
64
|
|
|
129
65
|
if isinstance(_widget, Dropdown):
|
|
130
|
-
|
|
66
|
+
# actually used for filling value
|
|
67
|
+
# doesn't trigger hook
|
|
68
|
+
_widget.__dict__["choices"] = get_dropdown_choices(widget)
|
|
131
69
|
|
|
132
70
|
if isinstance(_widget, Radio):
|
|
133
71
|
if key not in results:
|
|
134
72
|
results[key] = _widget
|
|
135
73
|
|
|
74
|
+
# for schema
|
|
136
75
|
results[key].number_of_options += 1
|
|
137
76
|
continue
|
|
138
77
|
|
|
@@ -141,103 +80,22 @@ def build_widgets(
|
|
|
141
80
|
return results
|
|
142
81
|
|
|
143
82
|
|
|
144
|
-
def dropdown_to_text(dropdown: Dropdown) -> Text:
|
|
145
|
-
"""Converts a dropdown widget to a text widget while preserving properties.
|
|
146
|
-
|
|
147
|
-
Args:
|
|
148
|
-
dropdown: Dropdown widget to convert
|
|
149
|
-
|
|
150
|
-
Returns:
|
|
151
|
-
Text: New text widget with dropdown's selected value and styling
|
|
152
|
-
"""
|
|
153
|
-
|
|
154
|
-
result = Text(dropdown.name)
|
|
155
|
-
result.border_color = dropdown.border_color
|
|
156
|
-
result.background_color = dropdown.background_color
|
|
157
|
-
result.border_width = dropdown.border_width
|
|
158
|
-
result.render_widget = dropdown.render_widget
|
|
159
|
-
|
|
160
|
-
if dropdown.value is not None:
|
|
161
|
-
result.value = (
|
|
162
|
-
dropdown.choices[dropdown.value]
|
|
163
|
-
if dropdown.value < len(dropdown.choices)
|
|
164
|
-
else ""
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
return result
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def update_text_field_attributes(
|
|
171
|
-
template_stream: bytes,
|
|
172
|
-
widgets: Dict[str, WIDGET_TYPES],
|
|
173
|
-
use_full_widget_name: bool,
|
|
174
|
-
) -> None:
|
|
175
|
-
"""Update text field properties in a PDF form template.
|
|
176
|
-
|
|
177
|
-
Processes text field widgets in the template to update their visual attributes including:
|
|
178
|
-
- Font properties (family, size, color)
|
|
179
|
-
- Text alignment and wrapping behavior
|
|
180
|
-
- Auto-sizing of text to fit within field bounds
|
|
181
|
-
- Multi-line text field formatting
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
template_stream: Raw bytes of the PDF template containing form fields
|
|
185
|
-
widgets: Dictionary mapping field names to widget objects to update
|
|
186
|
-
use_full_widget_name: If True, uses full hierarchical widget names including parent keys.
|
|
187
|
-
When False (default), uses simple field names.
|
|
188
|
-
|
|
189
|
-
Returns:
|
|
190
|
-
None: Modifies widget objects in-place
|
|
191
|
-
|
|
192
|
-
Note:
|
|
193
|
-
This function modifies the widget objects in-place and does not return anything.
|
|
194
|
-
Changes include font properties, text alignment, and auto-wrapping settings.
|
|
195
|
-
"""
|
|
196
|
-
|
|
197
|
-
for _widgets in get_widgets_by_page(template_stream).values():
|
|
198
|
-
for _widget in _widgets:
|
|
199
|
-
key = get_widget_key(_widget, use_full_widget_name)
|
|
200
|
-
|
|
201
|
-
if isinstance(widgets[key], Text):
|
|
202
|
-
should_adjust_font_size = False
|
|
203
|
-
is_paragraph = is_text_multiline(_widget)
|
|
204
|
-
if widgets[key].font is None:
|
|
205
|
-
widgets[key].font = auto_detect_font(_widget)
|
|
206
|
-
if widgets[key].font_size is None:
|
|
207
|
-
template_font_size = get_text_field_font_size(_widget)
|
|
208
|
-
widgets[key].font_size = template_font_size or (
|
|
209
|
-
text_field_font_size(_widget)
|
|
210
|
-
if not is_paragraph
|
|
211
|
-
else DEFAULT_FONT_SIZE
|
|
212
|
-
)
|
|
213
|
-
should_adjust_font_size = (
|
|
214
|
-
not template_font_size and widgets[key].max_length is None
|
|
215
|
-
)
|
|
216
|
-
if widgets[key].font_color is None:
|
|
217
|
-
widgets[key].font_color = get_text_field_font_color(_widget)
|
|
218
|
-
if is_paragraph and widgets[key].text_wrap_length is None:
|
|
219
|
-
widgets[key].text_lines = get_paragraph_lines(_widget, widgets[key])
|
|
220
|
-
widgets[key].text_wrap_length = get_paragraph_auto_wrap_length(
|
|
221
|
-
widgets[key]
|
|
222
|
-
)
|
|
223
|
-
if widgets[key].value and should_adjust_font_size:
|
|
224
|
-
if is_paragraph:
|
|
225
|
-
adjust_paragraph_font_size(_widget, widgets[key])
|
|
226
|
-
else:
|
|
227
|
-
adjust_text_field_font_size(_widget, widgets[key])
|
|
228
|
-
|
|
229
|
-
|
|
230
83
|
@lru_cache()
|
|
231
84
|
def get_widgets_by_page(pdf: bytes) -> Dict[int, List[dict]]:
|
|
232
|
-
"""
|
|
85
|
+
"""
|
|
86
|
+
Retrieves widgets from a PDF stream, organized by page number.
|
|
87
|
+
|
|
88
|
+
This function parses a PDF stream and extracts all the widgets (annotations)
|
|
89
|
+
present on each page. It returns a dictionary where the keys are the page
|
|
90
|
+
numbers and the values are lists of widget dictionaries.
|
|
233
91
|
|
|
234
92
|
Args:
|
|
235
|
-
pdf: PDF
|
|
93
|
+
pdf (bytes): The PDF stream to parse.
|
|
236
94
|
|
|
237
95
|
Returns:
|
|
238
|
-
Dict[int, List[dict]]:
|
|
96
|
+
Dict[int, List[dict]]: A dictionary where keys are page numbers (1-indexed)
|
|
97
|
+
and values are lists of widget dictionaries.
|
|
239
98
|
"""
|
|
240
|
-
|
|
241
99
|
pdf_file = PdfReader(stream_to_io(pdf))
|
|
242
100
|
|
|
243
101
|
result = {}
|
|
@@ -261,19 +119,21 @@ def get_widgets_by_page(pdf: bytes) -> Dict[int, List[dict]]:
|
|
|
261
119
|
|
|
262
120
|
|
|
263
121
|
def get_widget_key(widget: dict, use_full_widget_name: bool) -> str:
|
|
264
|
-
"""
|
|
122
|
+
"""
|
|
123
|
+
Extracts the widget key from a widget dictionary.
|
|
265
124
|
|
|
266
|
-
This function
|
|
267
|
-
If `use_full_widget_name` is
|
|
125
|
+
This function extracts the widget key from a widget dictionary based on
|
|
126
|
+
predefined patterns. If `use_full_widget_name` is True, it recursively
|
|
127
|
+
constructs the full widget name by concatenating the parent widget keys.
|
|
268
128
|
|
|
269
129
|
Args:
|
|
270
|
-
widget (dict):
|
|
271
|
-
use_full_widget_name (bool): Whether to
|
|
130
|
+
widget (dict): The widget dictionary to extract the key from.
|
|
131
|
+
use_full_widget_name (bool): Whether to use the full widget name
|
|
132
|
+
(including parent names) as the widget key.
|
|
272
133
|
|
|
273
134
|
Returns:
|
|
274
|
-
str:
|
|
135
|
+
str: The extracted widget key.
|
|
275
136
|
"""
|
|
276
|
-
|
|
277
137
|
key = extract_widget_property(widget, WIDGET_KEY_PATTERNS, None, str)
|
|
278
138
|
if not use_full_widget_name:
|
|
279
139
|
return key
|
|
@@ -281,7 +141,7 @@ def get_widget_key(widget: dict, use_full_widget_name: bool) -> str:
|
|
|
281
141
|
if (
|
|
282
142
|
Parent in widget
|
|
283
143
|
and T in widget[Parent].get_object()
|
|
284
|
-
and widget[Parent].get_object()[T] != key
|
|
144
|
+
and widget[Parent].get_object()[T] != key # sejda case
|
|
285
145
|
):
|
|
286
146
|
key = (
|
|
287
147
|
f"{get_widget_key(widget[Parent].get_object(), use_full_widget_name)}.{key}"
|
|
@@ -291,17 +151,20 @@ def get_widget_key(widget: dict, use_full_widget_name: bool) -> str:
|
|
|
291
151
|
|
|
292
152
|
|
|
293
153
|
def construct_widget(widget: dict, key: str) -> Union[WIDGET_TYPES, None]:
|
|
294
|
-
"""
|
|
154
|
+
"""
|
|
155
|
+
Constructs a widget object based on the widget dictionary and key.
|
|
156
|
+
|
|
157
|
+
This function determines the type of widget based on predefined patterns
|
|
158
|
+
and constructs the corresponding widget object.
|
|
295
159
|
|
|
296
160
|
Args:
|
|
297
|
-
widget:
|
|
298
|
-
key:
|
|
161
|
+
widget (dict): The widget dictionary to construct the object from.
|
|
162
|
+
key (str): The key of the widget.
|
|
299
163
|
|
|
300
164
|
Returns:
|
|
301
|
-
Union[WIDGET_TYPES, None]:
|
|
302
|
-
|
|
165
|
+
Union[WIDGET_TYPES, None]: The constructed widget object, or None
|
|
166
|
+
if the widget type is not recognized.
|
|
303
167
|
"""
|
|
304
|
-
|
|
305
168
|
result = None
|
|
306
169
|
for each in WIDGET_TYPE_PATTERNS:
|
|
307
170
|
patterns, _type = each
|
|
@@ -315,74 +178,31 @@ def construct_widget(widget: dict, key: str) -> Union[WIDGET_TYPES, None]:
|
|
|
315
178
|
|
|
316
179
|
|
|
317
180
|
def get_text_field_max_length(widget: dict) -> Union[int, None]:
|
|
318
|
-
"""Extracts max length constraint from a text field widget.
|
|
319
|
-
|
|
320
|
-
Args:
|
|
321
|
-
widget: PDF widget dictionary
|
|
322
|
-
|
|
323
|
-
Returns:
|
|
324
|
-
Union[int, None]: Max character length if specified, otherwise None
|
|
325
181
|
"""
|
|
326
|
-
|
|
327
|
-
return int(widget[MaxLen]) or None if MaxLen in widget else None
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
def check_field_flag_bit(widget: dict, bit: int) -> bool:
|
|
331
|
-
"""Tests whether a specific flag bit is set in a widget's field flags.
|
|
182
|
+
Extracts the maximum length of a text field from a widget dictionary.
|
|
332
183
|
|
|
333
184
|
Args:
|
|
334
|
-
widget:
|
|
335
|
-
bit: Flag bit to check (e.g. COMB, MULTILINE)
|
|
185
|
+
widget (dict): The widget dictionary to extract the max length from.
|
|
336
186
|
|
|
337
187
|
Returns:
|
|
338
|
-
|
|
188
|
+
Union[int, None]: The maximum length of the text field, or None
|
|
189
|
+
if the max length is not specified.
|
|
339
190
|
"""
|
|
191
|
+
return int(widget[MaxLen]) or None if MaxLen in widget else None
|
|
340
192
|
|
|
341
|
-
field_flag = extract_widget_property(widget, TEXT_FIELD_FLAG_PATTERNS, None, int)
|
|
342
|
-
|
|
343
|
-
if field_flag is None:
|
|
344
|
-
return False
|
|
345
|
-
|
|
346
|
-
return bool(int(field_flag) & bit)
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def is_text_field_comb(widget: dict) -> bool:
|
|
350
|
-
"""Determines if a text field uses comb formatting (fixed character spacing).
|
|
351
|
-
|
|
352
|
-
Args:
|
|
353
|
-
widget: PDF widget dictionary
|
|
354
|
-
|
|
355
|
-
Returns:
|
|
356
|
-
bool: True if field uses comb formatting
|
|
357
|
-
"""
|
|
358
|
-
|
|
359
|
-
return check_field_flag_bit(widget, COMB)
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
def is_text_multiline(widget: dict) -> bool:
|
|
363
|
-
"""Determines if a text field supports multiple lines/paragraphs.
|
|
364
|
-
|
|
365
|
-
Args:
|
|
366
|
-
widget: PDF widget dictionary
|
|
367
193
|
|
|
368
|
-
|
|
369
|
-
bool: True if field supports multiline text
|
|
194
|
+
def get_dropdown_choices(widget: dict) -> Union[Tuple[str, ...], None]:
|
|
370
195
|
"""
|
|
196
|
+
Extracts the choices from a dropdown widget dictionary.
|
|
371
197
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
def get_dropdown_choices(widget: dict) -> Union[Tuple[str, ...], None]:
|
|
376
|
-
"""Extracts available choices from a dropdown widget.
|
|
198
|
+
This function extracts the choices from a dropdown widget dictionary.
|
|
377
199
|
|
|
378
200
|
Args:
|
|
379
|
-
widget:
|
|
201
|
+
widget (dict): The widget dictionary to extract the choices from.
|
|
380
202
|
|
|
381
203
|
Returns:
|
|
382
|
-
Union[Tuple[str, ...], None]:
|
|
383
|
-
otherwise None
|
|
204
|
+
Union[Tuple[str, ...], None]: A tuple of strings representing the choices in the dropdown, or None if the choices are not specified.
|
|
384
205
|
"""
|
|
385
|
-
|
|
386
206
|
return tuple(
|
|
387
207
|
(each if isinstance(each, str) else str(each[1]))
|
|
388
208
|
for each in extract_widget_property(
|
|
@@ -391,176 +211,6 @@ def get_dropdown_choices(widget: dict) -> Union[Tuple[str, ...], None]:
|
|
|
391
211
|
)
|
|
392
212
|
|
|
393
213
|
|
|
394
|
-
def get_char_rect_width(widget: dict, widget_middleware: Text) -> float:
|
|
395
|
-
"""Calculates per-character width for comb text fields.
|
|
396
|
-
|
|
397
|
-
Args:
|
|
398
|
-
widget: PDF widget dictionary
|
|
399
|
-
widget_middleware: Text middleware instance
|
|
400
|
-
|
|
401
|
-
Returns:
|
|
402
|
-
float: Width in points allocated per character
|
|
403
|
-
"""
|
|
404
|
-
|
|
405
|
-
rect_width = abs(float(widget[Rect][0]) - float(widget[Rect][2]))
|
|
406
|
-
return rect_width / widget_middleware.max_length
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
def get_character_x_paddings(widget: dict, widget_middleware: Text) -> List[float]:
|
|
410
|
-
"""Calculates horizontal positioning for each character in comb fields.
|
|
411
|
-
|
|
412
|
-
Args:
|
|
413
|
-
widget: PDF widget dictionary
|
|
414
|
-
widget_middleware: Text middleware instance
|
|
415
|
-
|
|
416
|
-
Returns:
|
|
417
|
-
List[float]: X-offsets for centering each character in its comb cell
|
|
418
|
-
"""
|
|
419
|
-
|
|
420
|
-
length = min(len(widget_middleware.value or ""), widget_middleware.max_length)
|
|
421
|
-
char_rect_width = get_char_rect_width(widget, widget_middleware)
|
|
422
|
-
|
|
423
|
-
result = []
|
|
424
|
-
|
|
425
|
-
current_x = 0
|
|
426
|
-
for char in (widget_middleware.value or "")[:length]:
|
|
427
|
-
current_mid_point = current_x + char_rect_width / 2
|
|
428
|
-
result.append(
|
|
429
|
-
current_mid_point
|
|
430
|
-
- stringWidth(char, widget_middleware.font, widget_middleware.font_size) / 2
|
|
431
|
-
)
|
|
432
|
-
current_x += char_rect_width
|
|
433
|
-
|
|
434
|
-
return result
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
def split_characters_into_lines(
|
|
438
|
-
split_by_new_line_symbol: List[str], middleware: Text, width: float
|
|
439
|
-
) -> List[str]:
|
|
440
|
-
"""Splits text into lines that fit within a paragraph field's width.
|
|
441
|
-
|
|
442
|
-
Args:
|
|
443
|
-
split_by_new_line_symbol: Text already split by newlines
|
|
444
|
-
middleware: Text middleware with font properties
|
|
445
|
-
width: Available width in points
|
|
446
|
-
|
|
447
|
-
Returns:
|
|
448
|
-
List[str]: Lines of text fitting within the specified width
|
|
449
|
-
"""
|
|
450
|
-
|
|
451
|
-
lines = []
|
|
452
|
-
for line in split_by_new_line_symbol:
|
|
453
|
-
characters = line.split(" ")
|
|
454
|
-
current_line = ""
|
|
455
|
-
for each in characters:
|
|
456
|
-
line_extended = f"{current_line} {each}" if current_line else each
|
|
457
|
-
if (
|
|
458
|
-
stringWidth(line_extended, middleware.font, middleware.font_size)
|
|
459
|
-
<= width
|
|
460
|
-
):
|
|
461
|
-
current_line = line_extended
|
|
462
|
-
else:
|
|
463
|
-
lines.append(current_line)
|
|
464
|
-
current_line = each
|
|
465
|
-
lines.append(
|
|
466
|
-
current_line + NEW_LINE_SYMBOL
|
|
467
|
-
if len(split_by_new_line_symbol) > 1
|
|
468
|
-
else current_line
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
return lines
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
def adjust_each_line(lines: List[str], middleware: Text, width: float) -> List[str]:
|
|
475
|
-
"""Optimizes line breaks to minimize empty space in paragraph fields.
|
|
476
|
-
|
|
477
|
-
Args:
|
|
478
|
-
lines: Text lines from split_characters_into_lines()
|
|
479
|
-
middleware: Text middleware with font properties
|
|
480
|
-
width: Available width in points
|
|
481
|
-
|
|
482
|
-
Returns:
|
|
483
|
-
List[str]: Optimized lines with balanced word wrapping
|
|
484
|
-
"""
|
|
485
|
-
|
|
486
|
-
result = []
|
|
487
|
-
for each in lines:
|
|
488
|
-
tracker = ""
|
|
489
|
-
for char in each:
|
|
490
|
-
check = tracker + char
|
|
491
|
-
if stringWidth(check, middleware.font, middleware.font_size) > width:
|
|
492
|
-
result.append(tracker)
|
|
493
|
-
tracker = char
|
|
494
|
-
else:
|
|
495
|
-
tracker = check
|
|
496
|
-
|
|
497
|
-
each = tracker
|
|
498
|
-
if each:
|
|
499
|
-
if (
|
|
500
|
-
result
|
|
501
|
-
and stringWidth(
|
|
502
|
-
f"{each} {result[-1]}",
|
|
503
|
-
middleware.font,
|
|
504
|
-
middleware.font_size,
|
|
505
|
-
)
|
|
506
|
-
<= width
|
|
507
|
-
and NEW_LINE_SYMBOL not in result[-1]
|
|
508
|
-
):
|
|
509
|
-
result[-1] = f"{result[-1]}{each} "
|
|
510
|
-
else:
|
|
511
|
-
result.append(f"{each} ")
|
|
512
|
-
|
|
513
|
-
for i, each in enumerate(result):
|
|
514
|
-
result[i] = each.replace(NEW_LINE_SYMBOL, "")
|
|
515
|
-
|
|
516
|
-
if result:
|
|
517
|
-
result[-1] = result[-1][:-1]
|
|
518
|
-
|
|
519
|
-
return result
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
def get_paragraph_lines(widget: dict, widget_middleware: Text) -> List[str]:
|
|
523
|
-
"""Generates properly wrapped lines for a paragraph text field.
|
|
524
|
-
|
|
525
|
-
Args:
|
|
526
|
-
widget: PDF widget dictionary
|
|
527
|
-
widget_middleware: Text middleware instance
|
|
528
|
-
|
|
529
|
-
Returns:
|
|
530
|
-
List[str]: Wrapped lines fitting the paragraph field dimensions
|
|
531
|
-
"""
|
|
532
|
-
|
|
533
|
-
value = widget_middleware.value or ""
|
|
534
|
-
if widget_middleware.max_length is not None:
|
|
535
|
-
value = value[: widget_middleware.max_length]
|
|
536
|
-
|
|
537
|
-
width = abs(float(widget[Rect][0]) - float(widget[Rect][2]))
|
|
538
|
-
|
|
539
|
-
split_by_new_line_symbol = value.split(NEW_LINE_SYMBOL)
|
|
540
|
-
lines = split_characters_into_lines(
|
|
541
|
-
split_by_new_line_symbol, widget_middleware, width
|
|
542
|
-
)
|
|
543
|
-
|
|
544
|
-
return adjust_each_line(lines, widget_middleware, width)
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
def get_paragraph_auto_wrap_length(widget_middleware: Text) -> int:
|
|
548
|
-
"""Determines optimal line length for paragraph text wrapping.
|
|
549
|
-
|
|
550
|
-
Args:
|
|
551
|
-
widget_middleware: Text middleware instance
|
|
552
|
-
|
|
553
|
-
Returns:
|
|
554
|
-
int: Suggested maximum characters per line
|
|
555
|
-
"""
|
|
556
|
-
|
|
557
|
-
result = maxsize
|
|
558
|
-
for line in widget_middleware.text_lines:
|
|
559
|
-
result = min(result, len(line))
|
|
560
|
-
|
|
561
|
-
return result
|
|
562
|
-
|
|
563
|
-
|
|
564
214
|
def update_widget_keys(
|
|
565
215
|
template: bytes,
|
|
566
216
|
widgets: Dict[str, WIDGET_TYPES],
|
|
@@ -568,20 +218,23 @@ def update_widget_keys(
|
|
|
568
218
|
new_keys: List[str],
|
|
569
219
|
indices: List[int],
|
|
570
220
|
) -> bytes:
|
|
571
|
-
"""
|
|
221
|
+
"""
|
|
222
|
+
Updates the keys of widgets in a PDF template.
|
|
223
|
+
|
|
224
|
+
This function updates the keys of widgets in a PDF template based on the provided old keys and new keys.
|
|
225
|
+
It iterates through each page and annotation, finds the widgets with the old keys, and updates their names with the corresponding new keys.
|
|
226
|
+
The `indices` parameter is used when multiple widgets have the same name, to differentiate which one to update.
|
|
572
227
|
|
|
573
228
|
Args:
|
|
574
|
-
template: PDF
|
|
575
|
-
widgets:
|
|
576
|
-
old_keys
|
|
577
|
-
new_keys
|
|
578
|
-
indices
|
|
229
|
+
template (bytes): The PDF template to update.
|
|
230
|
+
widgets (Dict[str, WIDGET_TYPES]): A dictionary of widgets in the template.
|
|
231
|
+
old_keys (List[str]): A list of the old widget keys to be replaced.
|
|
232
|
+
new_keys (List[str]): A list of the new widget keys to replace the old keys.
|
|
233
|
+
indices (List[int]): A list of indices to handle the case where multiple widgets have the same name.
|
|
579
234
|
|
|
580
235
|
Returns:
|
|
581
|
-
bytes:
|
|
236
|
+
bytes: The updated PDF template as a byte stream.
|
|
582
237
|
"""
|
|
583
|
-
# pylint: disable=R0801
|
|
584
|
-
|
|
585
238
|
pdf = PdfReader(stream_to_io(template))
|
|
586
239
|
out = PdfWriter()
|
|
587
240
|
out.append(pdf)
|