PyPDFForm 2.3.3__py3-none-any.whl → 2.5.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 +1 -1
- PyPDFForm/filler.py +3 -1
- PyPDFForm/hooks.py +216 -0
- PyPDFForm/middleware/base.py +24 -0
- PyPDFForm/middleware/checkbox.py +4 -0
- PyPDFForm/middleware/text.py +5 -0
- PyPDFForm/patterns.py +3 -38
- PyPDFForm/widgets/base.py +1 -1
- PyPDFForm/widgets/text.py +1 -1
- PyPDFForm/wrapper.py +84 -35
- {pypdfform-2.3.3.dist-info → pypdfform-2.5.0.dist-info}/METADATA +2 -3
- {pypdfform-2.3.3.dist-info → pypdfform-2.5.0.dist-info}/RECORD +15 -14
- {pypdfform-2.3.3.dist-info → pypdfform-2.5.0.dist-info}/WHEEL +1 -1
- {pypdfform-2.3.3.dist-info → pypdfform-2.5.0.dist-info}/licenses/LICENSE +0 -0
- {pypdfform-2.3.3.dist-info → pypdfform-2.5.0.dist-info}/top_level.txt +0 -0
PyPDFForm/__init__.py
CHANGED
PyPDFForm/filler.py
CHANGED
|
@@ -337,6 +337,7 @@ def enable_adobe_mode(reader: PdfReader, writer: PdfWriter, adobe_mode: bool) ->
|
|
|
337
337
|
def simple_fill(
|
|
338
338
|
template: bytes,
|
|
339
339
|
widgets: Dict[str, WIDGET_TYPES],
|
|
340
|
+
use_full_widget_name: bool,
|
|
340
341
|
flatten: bool = False,
|
|
341
342
|
adobe_mode: bool = False,
|
|
342
343
|
) -> bytes:
|
|
@@ -350,6 +351,7 @@ def simple_fill(
|
|
|
350
351
|
Args:
|
|
351
352
|
template: Input PDF form as bytes
|
|
352
353
|
widgets: Dictionary mapping field names to widget middleware
|
|
354
|
+
use_full_widget_name: If True, uses the full widget name as the key in the widgets dictionary
|
|
353
355
|
flatten: If True, makes form fields read-only
|
|
354
356
|
adobe_mode: If True, enables Adobe Acrobat compatibility
|
|
355
357
|
|
|
@@ -367,7 +369,7 @@ def simple_fill(
|
|
|
367
369
|
for page in out.pages:
|
|
368
370
|
for annot in page.get(Annots, []):
|
|
369
371
|
annot = cast(DictionaryObject, annot.get_object())
|
|
370
|
-
key = get_widget_key(annot.get_object(),
|
|
372
|
+
key = get_widget_key(annot.get_object(), use_full_widget_name)
|
|
371
373
|
|
|
372
374
|
widget = widgets.get(key)
|
|
373
375
|
if widget is None or widget.value is None:
|
PyPDFForm/hooks.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Module containing hook functions for PDF form widget manipulation.
|
|
3
|
+
|
|
4
|
+
This module provides functions to apply various transformations and modifications
|
|
5
|
+
to PDF form widgets through a hook system. It allows dynamic modification of
|
|
6
|
+
widget properties like font sizes and other attributes.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from io import BytesIO
|
|
11
|
+
from typing import cast
|
|
12
|
+
|
|
13
|
+
from pypdf import PdfReader, PdfWriter
|
|
14
|
+
from pypdf.generic import (ArrayObject, DictionaryObject, FloatObject,
|
|
15
|
+
NameObject, NumberObject, TextStringObject)
|
|
16
|
+
|
|
17
|
+
from .constants import (COMB, DA, FONT_COLOR_IDENTIFIER, FONT_SIZE_IDENTIFIER,
|
|
18
|
+
MULTILINE, Annots, Ff, Parent, Q, Rect)
|
|
19
|
+
from .template import get_widget_key
|
|
20
|
+
from .utils import stream_to_io
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def trigger_widget_hooks(
|
|
24
|
+
pdf: bytes,
|
|
25
|
+
widgets: dict,
|
|
26
|
+
use_full_widget_name: bool,
|
|
27
|
+
) -> bytes:
|
|
28
|
+
"""Apply all registered widget hooks to a PDF document.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
pdf: The input PDF document as bytes
|
|
32
|
+
widgets: Dictionary mapping widget names to widget objects
|
|
33
|
+
use_full_widget_name: Whether to use full widget names including parent hierarchy
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The modified PDF document as bytes
|
|
37
|
+
|
|
38
|
+
Note:
|
|
39
|
+
This function processes all pages and annotations in the PDF, applying
|
|
40
|
+
any hooks registered in the widget objects.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
pdf_file = PdfReader(stream_to_io(pdf))
|
|
44
|
+
output = PdfWriter()
|
|
45
|
+
output.append(pdf_file)
|
|
46
|
+
|
|
47
|
+
for page in output.pages:
|
|
48
|
+
for annot in page.get(Annots, []):
|
|
49
|
+
annot = cast(DictionaryObject, annot.get_object())
|
|
50
|
+
key = get_widget_key(annot.get_object(), use_full_widget_name)
|
|
51
|
+
|
|
52
|
+
widget = widgets.get(key)
|
|
53
|
+
if widget is None or not widget.hooks_to_trigger:
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
for hook in widget.hooks_to_trigger:
|
|
57
|
+
getattr(sys.modules[__name__], hook[0])(annot, hook[1])
|
|
58
|
+
|
|
59
|
+
for widget in widgets.values():
|
|
60
|
+
widget.hooks_to_trigger = []
|
|
61
|
+
|
|
62
|
+
with BytesIO() as f:
|
|
63
|
+
output.write(f)
|
|
64
|
+
f.seek(0)
|
|
65
|
+
return f.read()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def update_text_field_font_size(annot: DictionaryObject, val: float) -> None:
|
|
69
|
+
"""Update the font size of a text field widget.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
annot: The PDF annotation (widget) dictionary object
|
|
73
|
+
val: The new font size value to apply
|
|
74
|
+
|
|
75
|
+
Note:
|
|
76
|
+
Handles both direct font size specification and inherited font sizes
|
|
77
|
+
from parent objects. Modifies the DA (default appearance) string.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
if Parent in annot and DA not in annot:
|
|
81
|
+
text_appearance = annot[Parent][DA]
|
|
82
|
+
else:
|
|
83
|
+
text_appearance = annot[DA]
|
|
84
|
+
|
|
85
|
+
text_appearance = text_appearance.split(" ")
|
|
86
|
+
font_size_index = 0
|
|
87
|
+
for i, value in enumerate(text_appearance):
|
|
88
|
+
if value.startswith(FONT_SIZE_IDENTIFIER):
|
|
89
|
+
font_size_index = i - 1
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
text_appearance[font_size_index] = str(val)
|
|
93
|
+
new_text_appearance = " ".join(text_appearance)
|
|
94
|
+
|
|
95
|
+
if Parent in annot and DA not in annot:
|
|
96
|
+
annot[NameObject(Parent)][NameObject(DA)] = TextStringObject(
|
|
97
|
+
new_text_appearance
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
annot[NameObject(DA)] = TextStringObject(new_text_appearance)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def update_text_field_font_color(annot: DictionaryObject, val: tuple) -> None:
|
|
104
|
+
"""Update the font color of a text field widget.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
annot: The PDF annotation (widget) dictionary object
|
|
108
|
+
val: Tuple containing RGB color values (e.g. (1, 0, 0) for red)
|
|
109
|
+
|
|
110
|
+
Note:
|
|
111
|
+
Handles both direct font color specification and inherited font colors
|
|
112
|
+
from parent objects. Modifies the DA (default appearance) string to
|
|
113
|
+
include the new color values after the font size identifier.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
if Parent in annot and DA not in annot:
|
|
117
|
+
text_appearance = annot[Parent][DA]
|
|
118
|
+
else:
|
|
119
|
+
text_appearance = annot[DA]
|
|
120
|
+
|
|
121
|
+
text_appearance = text_appearance.split(" ")
|
|
122
|
+
font_size_identifier_index = 0
|
|
123
|
+
for i, value in enumerate(text_appearance):
|
|
124
|
+
if value == FONT_SIZE_IDENTIFIER:
|
|
125
|
+
font_size_identifier_index = i
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
new_text_appearance = (
|
|
129
|
+
text_appearance[:font_size_identifier_index]
|
|
130
|
+
+ [FONT_SIZE_IDENTIFIER]
|
|
131
|
+
+ [str(each) for each in val]
|
|
132
|
+
)
|
|
133
|
+
new_text_appearance = " ".join(new_text_appearance) + FONT_COLOR_IDENTIFIER
|
|
134
|
+
|
|
135
|
+
if Parent in annot and DA not in annot:
|
|
136
|
+
annot[NameObject(Parent)][NameObject(DA)] = TextStringObject(
|
|
137
|
+
new_text_appearance
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
annot[NameObject(DA)] = TextStringObject(new_text_appearance)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def update_text_field_alignment(annot: DictionaryObject, val: int) -> None:
|
|
144
|
+
"""Update text alignment for text field annotations.
|
|
145
|
+
|
|
146
|
+
Modifies the alignment (Q) field of a text field annotation to set the
|
|
147
|
+
specified text alignment.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
annot: PDF text field annotation dictionary to modify
|
|
151
|
+
val: Alignment value to set (typically 0=left, 1=center, 2=right)
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
annot[NameObject(Q)] = NumberObject(val)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def update_text_field_multiline(annot: DictionaryObject, val: bool) -> None:
|
|
158
|
+
"""Update multiline flag for text field annotations.
|
|
159
|
+
|
|
160
|
+
Modifies the field flags (Ff) of a text field annotation to set or
|
|
161
|
+
clear the multiline flag based on the input value.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
annot: PDF text field annotation dictionary to modify
|
|
165
|
+
val: Whether to enable multiline (True) or disable (False)
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
if val:
|
|
169
|
+
annot[NameObject(Ff)] = NumberObject(int(annot[NameObject(Ff)]) | MULTILINE)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def update_text_field_comb(annot: DictionaryObject, val: bool) -> None:
|
|
173
|
+
"""Update comb formatting flag for text field annotations.
|
|
174
|
+
|
|
175
|
+
Modifies the field flags (Ff) of a text field annotation to set or
|
|
176
|
+
clear the comb flag which enables/disables comb formatting.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
annot: PDF text field annotation dictionary to modify
|
|
180
|
+
val: Whether to enable comb formatting (True) or disable (False)
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
if val:
|
|
184
|
+
annot[NameObject(Ff)] = NumberObject(int(annot[NameObject(Ff)]) | COMB)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def update_check_radio_size(annot: DictionaryObject, val: float) -> None:
|
|
188
|
+
"""Update the size of a checkbox or radio button widget while maintaining center position.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
annot: PDF annotation dictionary containing the widget to modify
|
|
192
|
+
val: New size value (width and height) for the widget
|
|
193
|
+
|
|
194
|
+
Note:
|
|
195
|
+
The widget will be resized symmetrically around its center point,
|
|
196
|
+
maintaining the same center position while changing its dimensions.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
rect = annot[Rect]
|
|
200
|
+
center_x = (rect[0] + rect[2]) / 2
|
|
201
|
+
center_y = (rect[1] + rect[3]) / 2
|
|
202
|
+
new_rect = [
|
|
203
|
+
FloatObject(center_x - val / 2),
|
|
204
|
+
FloatObject(center_y - val / 2),
|
|
205
|
+
FloatObject(center_x + val / 2),
|
|
206
|
+
FloatObject(center_y + val / 2),
|
|
207
|
+
]
|
|
208
|
+
annot[NameObject(Rect)] = ArrayObject(new_rect)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# TODO: remove this and switch to hooks
|
|
212
|
+
NON_ACRO_FORM_PARAM_TO_FUNC = {
|
|
213
|
+
("TextWidget", "alignment"): update_text_field_alignment,
|
|
214
|
+
("TextWidget", "multiline"): update_text_field_multiline,
|
|
215
|
+
("TextWidget", "comb"): update_text_field_comb,
|
|
216
|
+
}
|
PyPDFForm/middleware/base.py
CHANGED
|
@@ -26,6 +26,8 @@ class Widget:
|
|
|
26
26
|
- Any widget-specific functionality
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
|
+
SET_ATTR_TRIGGER_HOOK_MAP = {}
|
|
30
|
+
|
|
29
31
|
def __init__(
|
|
30
32
|
self,
|
|
31
33
|
name: str,
|
|
@@ -48,6 +50,28 @@ class Widget:
|
|
|
48
50
|
self.border_style = None
|
|
49
51
|
self.dash_array = None
|
|
50
52
|
self.render_widget = None
|
|
53
|
+
self.hooks_to_trigger = []
|
|
54
|
+
|
|
55
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
56
|
+
"""Sets an attribute on the widget with special handling for hook-triggering attributes.
|
|
57
|
+
|
|
58
|
+
For attributes listed in SET_ATTR_TRIGGER_HOOK_MAP, when set to non-None values,
|
|
59
|
+
adds a hook to hooks_to_trigger list with the mapped hook name and value.
|
|
60
|
+
All other attributes are set normally via the standard object.__setattr__.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
name: Name of the attribute to set
|
|
64
|
+
value: Value to set the attribute to
|
|
65
|
+
|
|
66
|
+
Note:
|
|
67
|
+
The hook triggering only occurs when:
|
|
68
|
+
1. The attribute is in SET_ATTR_TRIGGER_HOOK_MAP
|
|
69
|
+
2. The value being set is not None
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
if name in self.SET_ATTR_TRIGGER_HOOK_MAP and value is not None:
|
|
73
|
+
self.hooks_to_trigger.append((self.SET_ATTR_TRIGGER_HOOK_MAP[name], value))
|
|
74
|
+
super().__setattr__(name, value)
|
|
51
75
|
|
|
52
76
|
@property
|
|
53
77
|
def name(self) -> str:
|
PyPDFForm/middleware/checkbox.py
CHANGED
|
@@ -25,6 +25,10 @@ class Checkbox(Widget):
|
|
|
25
25
|
Inherits from Widget base class and extends it with checkbox-specific features.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
+
SET_ATTR_TRIGGER_HOOK_MAP = {
|
|
29
|
+
"size": "update_check_radio_size",
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
BUTTON_STYLE_MAPPING = {
|
|
29
33
|
"check": "4",
|
|
30
34
|
"cross": "5",
|
PyPDFForm/middleware/text.py
CHANGED
|
@@ -27,6 +27,11 @@ class Text(Widget):
|
|
|
27
27
|
Inherits from Widget base class and extends it with text-specific features.
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
|
+
SET_ATTR_TRIGGER_HOOK_MAP = {
|
|
31
|
+
"font_size": "update_text_field_font_size",
|
|
32
|
+
"font_color": "update_text_field_font_color",
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
def __init__(
|
|
31
36
|
self,
|
|
32
37
|
name: str,
|
PyPDFForm/patterns.py
CHANGED
|
@@ -19,9 +19,9 @@ from pypdf.generic import (ArrayObject, DictionaryObject, NameObject,
|
|
|
19
19
|
NumberObject, TextStringObject)
|
|
20
20
|
|
|
21
21
|
from .constants import (AP, AS, BC, BG, BS, CA, DA, DV, FT,
|
|
22
|
-
IMAGE_FIELD_IDENTIFIER, JS, MK,
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
IMAGE_FIELD_IDENTIFIER, JS, MK, READ_ONLY, TU, A, Btn,
|
|
23
|
+
Ch, D, Ff, I, N, Off, Opt, Parent, Q, S, Sig, T, Tx, V,
|
|
24
|
+
W, Yes)
|
|
25
25
|
from .middleware.checkbox import Checkbox
|
|
26
26
|
from .middleware.dropdown import Dropdown
|
|
27
27
|
from .middleware.image import Image
|
|
@@ -273,38 +273,3 @@ def update_annotation_name(annot: DictionaryObject, val: str) -> None:
|
|
|
273
273
|
annot[NameObject(Parent)][NameObject(T)] = TextStringObject(val)
|
|
274
274
|
else:
|
|
275
275
|
annot[NameObject(T)] = TextStringObject(val)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def update_created_text_field_alignment(annot: DictionaryObject, val: int) -> None:
|
|
279
|
-
"""Update text alignment for created text field annotations.
|
|
280
|
-
|
|
281
|
-
Modifies the alignment (Q) field of a text field annotation created
|
|
282
|
-
by the library to set the specified text alignment.
|
|
283
|
-
|
|
284
|
-
Args:
|
|
285
|
-
annot: PDF text field annotation dictionary to modify
|
|
286
|
-
val: Alignment value to set (typically 0=left, 1=center, 2=right)
|
|
287
|
-
"""
|
|
288
|
-
|
|
289
|
-
annot[NameObject(Q)] = NumberObject(val)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
def update_created_text_field_multiline(annot: DictionaryObject, val: bool) -> None:
|
|
293
|
-
"""Update multiline flag for created text field annotations.
|
|
294
|
-
|
|
295
|
-
Modifies the field flags (Ff) of a text field annotation created by
|
|
296
|
-
the library to set or clear the multiline flag based on the input value.
|
|
297
|
-
|
|
298
|
-
Args:
|
|
299
|
-
annot: PDF text field annotation dictionary to modify
|
|
300
|
-
val: Whether to enable multiline (True) or disable (False)
|
|
301
|
-
"""
|
|
302
|
-
|
|
303
|
-
if val:
|
|
304
|
-
annot[NameObject(Ff)] = NumberObject(int(annot[NameObject(Ff)]) | MULTILINE)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
NON_ACRO_FORM_PARAM_TO_FUNC = {
|
|
308
|
-
("TextWidget", "alignment"): update_created_text_field_alignment,
|
|
309
|
-
("TextWidget", "multiline"): update_created_text_field_multiline,
|
|
310
|
-
}
|
PyPDFForm/widgets/base.py
CHANGED
|
@@ -16,7 +16,7 @@ from reportlab.lib.colors import Color
|
|
|
16
16
|
from reportlab.pdfgen.canvas import Canvas
|
|
17
17
|
|
|
18
18
|
from ..constants import Annots
|
|
19
|
-
from ..
|
|
19
|
+
from ..hooks import NON_ACRO_FORM_PARAM_TO_FUNC
|
|
20
20
|
from ..template import get_widget_key
|
|
21
21
|
from ..utils import stream_to_io
|
|
22
22
|
|
PyPDFForm/widgets/text.py
CHANGED
|
@@ -36,6 +36,6 @@ class TextWidget(Widget):
|
|
|
36
36
|
("max_length", "maxlen"),
|
|
37
37
|
]
|
|
38
38
|
COLOR_PARAMS = ["font_color", "bg_color", "border_color"]
|
|
39
|
-
ALLOWED_NON_ACRO_FORM_PARAMS = ["alignment", "multiline"]
|
|
39
|
+
ALLOWED_NON_ACRO_FORM_PARAMS = ["alignment", "multiline", "comb"]
|
|
40
40
|
NONE_DEFAULTS = ["max_length"]
|
|
41
41
|
ACRO_FORM_FUNC = "textfield"
|
PyPDFForm/wrapper.py
CHANGED
|
@@ -26,6 +26,7 @@ from .constants import (DEFAULT_FONT, DEFAULT_FONT_COLOR, DEFAULT_FONT_SIZE,
|
|
|
26
26
|
from .coordinate import generate_coordinate_grid
|
|
27
27
|
from .filler import fill, simple_fill
|
|
28
28
|
from .font import register_font
|
|
29
|
+
from .hooks import trigger_widget_hooks
|
|
29
30
|
from .image import rotate_image
|
|
30
31
|
from .middleware.dropdown import Dropdown
|
|
31
32
|
from .middleware.text import Text
|
|
@@ -62,16 +63,21 @@ class FormWrapper:
|
|
|
62
63
|
def __init__(
|
|
63
64
|
self,
|
|
64
65
|
template: Union[bytes, str, BinaryIO] = b"",
|
|
66
|
+
**kwargs,
|
|
65
67
|
) -> None:
|
|
66
68
|
"""Initializes the base form wrapper with a PDF template.
|
|
67
69
|
|
|
68
70
|
Args:
|
|
69
71
|
template: PDF form as bytes, file path, or file object. Defaults to
|
|
70
72
|
empty bytes if not provided.
|
|
73
|
+
**kwargs: Additional options:
|
|
74
|
+
use_full_widget_name: If True, uses complete widget names including
|
|
75
|
+
field hierarchy (default: False)
|
|
71
76
|
|
|
72
77
|
Initializes:
|
|
73
78
|
- Internal PDF stream from the template
|
|
74
79
|
- Basic form filling capabilities
|
|
80
|
+
- Widget naming configuration from kwargs
|
|
75
81
|
|
|
76
82
|
Note:
|
|
77
83
|
This base class is designed to be extended by PdfWrapper which adds
|
|
@@ -79,7 +85,8 @@ class FormWrapper:
|
|
|
79
85
|
"""
|
|
80
86
|
|
|
81
87
|
super().__init__()
|
|
82
|
-
self.
|
|
88
|
+
self._stream = fp_or_f_obj_or_stream_to_stream(template)
|
|
89
|
+
self.use_full_widget_name = kwargs.get("use_full_widget_name", False)
|
|
83
90
|
|
|
84
91
|
def read(self) -> bytes:
|
|
85
92
|
"""Returns the raw bytes of the PDF form data.
|
|
@@ -92,7 +99,7 @@ class FormWrapper:
|
|
|
92
99
|
bytes: The complete PDF document as a byte string
|
|
93
100
|
"""
|
|
94
101
|
|
|
95
|
-
return self.
|
|
102
|
+
return self._stream
|
|
96
103
|
|
|
97
104
|
def fill(
|
|
98
105
|
self,
|
|
@@ -102,28 +109,43 @@ class FormWrapper:
|
|
|
102
109
|
"""Fills form fields in the PDF with provided values.
|
|
103
110
|
|
|
104
111
|
Takes a dictionary of field names to values and updates the corresponding
|
|
105
|
-
form fields in the PDF.
|
|
106
|
-
|
|
112
|
+
form fields in the PDF. Supports these value types:
|
|
113
|
+
- Strings for text fields
|
|
114
|
+
- Booleans for checkboxes (True=checked, False=unchecked)
|
|
115
|
+
- Integers for numeric fields and dropdown selections
|
|
116
|
+
|
|
117
|
+
Only fields that exist in the template PDF will be filled - unknown field
|
|
118
|
+
names are silently ignored.
|
|
107
119
|
|
|
108
120
|
Args:
|
|
109
|
-
data: Dictionary mapping field names to values
|
|
121
|
+
data: Dictionary mapping field names to values. Supported types:
|
|
122
|
+
str: For text fields
|
|
123
|
+
bool: For checkboxes (True=checked)
|
|
124
|
+
int: For numeric fields and dropdown selections
|
|
110
125
|
**kwargs: Additional options:
|
|
111
|
-
flatten: If True, makes form fields read-only after filling
|
|
112
|
-
|
|
126
|
+
flatten (bool): If True, makes form fields read-only after filling
|
|
127
|
+
(default: False)
|
|
128
|
+
adobe_mode (bool): If True, uses Adobe-compatible filling logic
|
|
129
|
+
(default: False)
|
|
113
130
|
|
|
114
131
|
Returns:
|
|
115
132
|
FormWrapper: Returns self to allow method chaining
|
|
116
133
|
"""
|
|
117
134
|
|
|
118
|
-
widgets =
|
|
135
|
+
widgets = (
|
|
136
|
+
build_widgets(self.read(), self.use_full_widget_name, False)
|
|
137
|
+
if self.read()
|
|
138
|
+
else {}
|
|
139
|
+
)
|
|
119
140
|
|
|
120
141
|
for key, value in data.items():
|
|
121
142
|
if key in widgets:
|
|
122
143
|
widgets[key].value = value
|
|
123
144
|
|
|
124
|
-
self.
|
|
145
|
+
self._stream = simple_fill(
|
|
125
146
|
self.read(),
|
|
126
147
|
widgets,
|
|
148
|
+
use_full_widget_name=self.use_full_widget_name,
|
|
127
149
|
flatten=kwargs.get("flatten", False),
|
|
128
150
|
adobe_mode=kwargs.get("adobe_mode", False),
|
|
129
151
|
)
|
|
@@ -156,6 +178,8 @@ class PdfWrapper(FormWrapper):
|
|
|
156
178
|
("use_full_widget_name", False),
|
|
157
179
|
("render_widgets", True),
|
|
158
180
|
]
|
|
181
|
+
# TODO: remove, always default to True
|
|
182
|
+
TRIGGER_WIDGET_HOOKS = False
|
|
159
183
|
|
|
160
184
|
def __init__(
|
|
161
185
|
self,
|
|
@@ -234,6 +258,31 @@ class PdfWrapper(FormWrapper):
|
|
|
234
258
|
value.font_size = getattr(self, "global_font_size")
|
|
235
259
|
value.font_color = getattr(self, "global_font_color")
|
|
236
260
|
|
|
261
|
+
def read(self) -> bytes:
|
|
262
|
+
"""Returns the raw bytes of the PDF form data with optional widget hook processing.
|
|
263
|
+
|
|
264
|
+
Extends FormWrapper.read() with additional functionality:
|
|
265
|
+
- Triggers any registered widget hooks if TRIGGER_WIDGET_HOOKS is True
|
|
266
|
+
- Maintains all parent class behavior of returning raw PDF bytes
|
|
267
|
+
|
|
268
|
+
The method first processes any widget hooks that need triggering, then delegates
|
|
269
|
+
to the parent class's read() implementation to return the PDF bytes.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
bytes: The complete PDF document as a byte string, after any hook processing
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
if self.TRIGGER_WIDGET_HOOKS and any(
|
|
276
|
+
widget.hooks_to_trigger for widget in self.widgets.values()
|
|
277
|
+
):
|
|
278
|
+
self._stream = trigger_widget_hooks(
|
|
279
|
+
self._stream,
|
|
280
|
+
self.widgets,
|
|
281
|
+
getattr(self, "use_full_widget_name"),
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return super().read()
|
|
285
|
+
|
|
237
286
|
@property
|
|
238
287
|
def sample_data(self) -> dict:
|
|
239
288
|
"""Generates a dictionary of sample values for all form fields.
|
|
@@ -266,7 +315,7 @@ class PdfWrapper(FormWrapper):
|
|
|
266
315
|
"""
|
|
267
316
|
|
|
268
317
|
for each in VERSION_IDENTIFIERS:
|
|
269
|
-
if self.
|
|
318
|
+
if self.read().startswith(each):
|
|
270
319
|
return each.replace(VERSION_IDENTIFIER_PREFIX, b"").decode()
|
|
271
320
|
|
|
272
321
|
return None
|
|
@@ -288,7 +337,7 @@ class PdfWrapper(FormWrapper):
|
|
|
288
337
|
|
|
289
338
|
return [
|
|
290
339
|
self.__class__(
|
|
291
|
-
copy_watermark_widgets(each, self.
|
|
340
|
+
copy_watermark_widgets(each, self.read(), None, i),
|
|
292
341
|
**{param: getattr(self, param) for param, _ in self.USER_PARAMS},
|
|
293
342
|
)
|
|
294
343
|
for i, each in enumerate(get_page_streams(remove_all_widgets(self.read())))
|
|
@@ -307,7 +356,7 @@ class PdfWrapper(FormWrapper):
|
|
|
307
356
|
PdfWrapper: Returns self to allow method chaining
|
|
308
357
|
"""
|
|
309
358
|
|
|
310
|
-
self.
|
|
359
|
+
self._stream = self.read().replace(
|
|
311
360
|
VERSION_IDENTIFIER_PREFIX + bytes(self.version, "utf-8"),
|
|
312
361
|
VERSION_IDENTIFIER_PREFIX + bytes(version, "utf-8"),
|
|
313
362
|
1,
|
|
@@ -330,10 +379,10 @@ class PdfWrapper(FormWrapper):
|
|
|
330
379
|
PdfWrapper: New wrapper containing merged PDF
|
|
331
380
|
"""
|
|
332
381
|
|
|
333
|
-
if not self.
|
|
382
|
+
if not self.read():
|
|
334
383
|
return other
|
|
335
384
|
|
|
336
|
-
if not other.
|
|
385
|
+
if not other.read():
|
|
337
386
|
return self
|
|
338
387
|
|
|
339
388
|
unique_suffix = generate_unique_suffix()
|
|
@@ -343,7 +392,7 @@ class PdfWrapper(FormWrapper):
|
|
|
343
392
|
|
|
344
393
|
other.commit_widget_key_updates()
|
|
345
394
|
|
|
346
|
-
return self.__class__(merge_two_pdfs(self.
|
|
395
|
+
return self.__class__(merge_two_pdfs(self.read(), other.read()))
|
|
347
396
|
|
|
348
397
|
@property
|
|
349
398
|
def preview(self) -> bytes:
|
|
@@ -360,7 +409,7 @@ class PdfWrapper(FormWrapper):
|
|
|
360
409
|
|
|
361
410
|
return remove_all_widgets(
|
|
362
411
|
fill(
|
|
363
|
-
self.
|
|
412
|
+
self.read(),
|
|
364
413
|
{
|
|
365
414
|
key: preview_widget_to_draw(key, value, True)
|
|
366
415
|
for key, value in self.widgets.items()
|
|
@@ -387,10 +436,10 @@ class PdfWrapper(FormWrapper):
|
|
|
387
436
|
PdfWrapper: Returns self to allow method chaining
|
|
388
437
|
"""
|
|
389
438
|
|
|
390
|
-
self.
|
|
439
|
+
self._stream = generate_coordinate_grid(
|
|
391
440
|
remove_all_widgets(
|
|
392
441
|
fill(
|
|
393
|
-
self.
|
|
442
|
+
self.read(),
|
|
394
443
|
{
|
|
395
444
|
key: preview_widget_to_draw(key, value, False)
|
|
396
445
|
for key, value in self.widgets.items()
|
|
@@ -433,15 +482,15 @@ class PdfWrapper(FormWrapper):
|
|
|
433
482
|
self.widgets[key] = dropdown_to_text(value)
|
|
434
483
|
|
|
435
484
|
update_text_field_attributes(
|
|
436
|
-
self.
|
|
485
|
+
self.read(), self.widgets, getattr(self, "use_full_widget_name")
|
|
437
486
|
)
|
|
438
487
|
if self.read():
|
|
439
488
|
self.widgets = set_character_x_paddings(
|
|
440
|
-
self.
|
|
489
|
+
self.read(), self.widgets, getattr(self, "use_full_widget_name")
|
|
441
490
|
)
|
|
442
491
|
|
|
443
|
-
self.
|
|
444
|
-
fill(self.
|
|
492
|
+
self._stream = remove_all_widgets(
|
|
493
|
+
fill(self.read(), self.widgets, getattr(self, "use_full_widget_name"))
|
|
445
494
|
)
|
|
446
495
|
|
|
447
496
|
return self
|
|
@@ -506,10 +555,10 @@ class PdfWrapper(FormWrapper):
|
|
|
506
555
|
obj = _class(name=name, page_number=page_number, x=x, y=y, **kwargs)
|
|
507
556
|
watermarks = obj.watermarks(self.read())
|
|
508
557
|
|
|
509
|
-
self.
|
|
558
|
+
self._stream = copy_watermark_widgets(self.read(), watermarks, [name], None)
|
|
510
559
|
if obj.non_acro_form_params:
|
|
511
|
-
self.
|
|
512
|
-
self.
|
|
560
|
+
self._stream = handle_non_acro_form_params(
|
|
561
|
+
self.read(), name, obj.non_acro_form_params
|
|
513
562
|
)
|
|
514
563
|
|
|
515
564
|
key_to_refresh = ""
|
|
@@ -548,7 +597,7 @@ class PdfWrapper(FormWrapper):
|
|
|
548
597
|
self._keys_to_update.append((old_key, new_key, index))
|
|
549
598
|
return self
|
|
550
599
|
|
|
551
|
-
self.
|
|
600
|
+
self._stream = update_widget_keys(
|
|
552
601
|
self.read(), self.widgets, [old_key], [new_key], [index]
|
|
553
602
|
)
|
|
554
603
|
self._init_helper()
|
|
@@ -576,7 +625,7 @@ class PdfWrapper(FormWrapper):
|
|
|
576
625
|
new_keys = [each[1] for each in self._keys_to_update]
|
|
577
626
|
indices = [each[2] for each in self._keys_to_update]
|
|
578
627
|
|
|
579
|
-
self.
|
|
628
|
+
self._stream = update_widget_keys(
|
|
580
629
|
self.read(), self.widgets, old_keys, new_keys, indices
|
|
581
630
|
)
|
|
582
631
|
self._init_helper()
|
|
@@ -624,7 +673,7 @@ class PdfWrapper(FormWrapper):
|
|
|
624
673
|
new_widget.text_lines = text.split(NEW_LINE_SYMBOL)
|
|
625
674
|
|
|
626
675
|
watermarks = create_watermarks_and_draw(
|
|
627
|
-
self.
|
|
676
|
+
self.read(),
|
|
628
677
|
page_number,
|
|
629
678
|
"text",
|
|
630
679
|
[
|
|
@@ -637,9 +686,9 @@ class PdfWrapper(FormWrapper):
|
|
|
637
686
|
)
|
|
638
687
|
|
|
639
688
|
stream_with_widgets = self.read()
|
|
640
|
-
self.
|
|
641
|
-
self.
|
|
642
|
-
remove_all_widgets(self.
|
|
689
|
+
self._stream = merge_watermarks_with_pdf(self.read(), watermarks)
|
|
690
|
+
self._stream = copy_watermark_widgets(
|
|
691
|
+
remove_all_widgets(self.read()), stream_with_widgets, None, None
|
|
643
692
|
)
|
|
644
693
|
|
|
645
694
|
return self
|
|
@@ -675,16 +724,16 @@ class PdfWrapper(FormWrapper):
|
|
|
675
724
|
image = fp_or_f_obj_or_stream_to_stream(image)
|
|
676
725
|
image = rotate_image(image, rotation)
|
|
677
726
|
watermarks = create_watermarks_and_draw(
|
|
678
|
-
self.
|
|
727
|
+
self.read(),
|
|
679
728
|
page_number,
|
|
680
729
|
"image",
|
|
681
730
|
[{"stream": image, "x": x, "y": y, "width": width, "height": height}],
|
|
682
731
|
)
|
|
683
732
|
|
|
684
733
|
stream_with_widgets = self.read()
|
|
685
|
-
self.
|
|
686
|
-
self.
|
|
687
|
-
remove_all_widgets(self.
|
|
734
|
+
self._stream = merge_watermarks_with_pdf(self.read(), watermarks)
|
|
735
|
+
self._stream = copy_watermark_widgets(
|
|
736
|
+
remove_all_widgets(self.read()), stream_with_widgets, None, None
|
|
688
737
|
)
|
|
689
738
|
|
|
690
739
|
return self
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPDFForm
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: The Python library for PDF forms.
|
|
5
5
|
Author: Jinge Li
|
|
6
6
|
License-Expression: MIT
|
|
@@ -10,7 +10,6 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.9
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -18,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
18
|
Classifier: Operating System :: OS Independent
|
|
20
19
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.9
|
|
22
21
|
Description-Content-Type: text/markdown
|
|
23
22
|
License-File: LICENSE
|
|
24
23
|
Requires-Dist: cryptography
|
|
@@ -1,34 +1,35 @@
|
|
|
1
|
-
PyPDFForm/__init__.py,sha256=
|
|
1
|
+
PyPDFForm/__init__.py,sha256=wAEMuODSkOqin_uUjzUY5oitVvnunyq9iTT7k_VrRbM,328
|
|
2
2
|
PyPDFForm/adapter.py,sha256=_5fP5UR-NzjDDayJmBRO36DgbnbUzNbjZtHZtPvSM14,1909
|
|
3
3
|
PyPDFForm/constants.py,sha256=3ed0j11cWd9Uo4s-XvZwwJojPSz8aqdmZgaEishWjqE,2342
|
|
4
4
|
PyPDFForm/coordinate.py,sha256=gQI7z-GsdCO33Qny5kXLBs6Y2TW5KO_mJ2in64fvXcg,16412
|
|
5
|
-
PyPDFForm/filler.py,sha256=
|
|
5
|
+
PyPDFForm/filler.py,sha256=oCXDzz8paw8-MIQLiTTv5goYlfCfz_EcczCj8K4JB3g,14116
|
|
6
6
|
PyPDFForm/font.py,sha256=eRbDyQFhXUkHzyZvCtru9Ypg_ukfbBAnSM5xNzPb5ss,7280
|
|
7
|
+
PyPDFForm/hooks.py,sha256=e9mjZgGl7t9Spok3Tgwl9Osmlzx4vod-5z5jVF84x-Q,7117
|
|
7
8
|
PyPDFForm/image.py,sha256=aYk7BC-AHiqt73durGIQ3e6gE5Ggbdr8jmkCUaQdsk8,1627
|
|
8
|
-
PyPDFForm/patterns.py,sha256=
|
|
9
|
+
PyPDFForm/patterns.py,sha256=nkvtwsm1dbHjAH7FBNwMR5YI6_Jj16KYBm1HxB12vOk,8100
|
|
9
10
|
PyPDFForm/template.py,sha256=N_Pj3SaSKs3sqIuIdtgHPXvxIHLnHeR2M9IwRCWWKoQ,20043
|
|
10
11
|
PyPDFForm/utils.py,sha256=QxJSG96FHGch863UdBPT8DdTKN9gVzQzA2ezifTm7z4,8575
|
|
11
12
|
PyPDFForm/watermark.py,sha256=yvtZKcdPAPprBVEWcFZEnZMJcTwVpQ6u72m0jqYHxx8,12357
|
|
12
|
-
PyPDFForm/wrapper.py,sha256=
|
|
13
|
+
PyPDFForm/wrapper.py,sha256=ndk6isS8scu9nxGsgcA9MU1-NNzYsqMLtxm3EY4aqSQ,27678
|
|
13
14
|
PyPDFForm/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
PyPDFForm/middleware/base.py,sha256=
|
|
15
|
-
PyPDFForm/middleware/checkbox.py,sha256=
|
|
15
|
+
PyPDFForm/middleware/base.py,sha256=lHIWBs2axKDYZj4rYRHrBHzSqF7yk0o9S8uAFTWspSA,3783
|
|
16
|
+
PyPDFForm/middleware/checkbox.py,sha256=sUk5hclIKYE8N85ZYvYOI_SYK2WVqlqIP7oBYWlv_yY,2695
|
|
16
17
|
PyPDFForm/middleware/dropdown.py,sha256=McuZl8Pc2IYPkEHRmM1OcEsh9njekySqjqVxRmQKmWU,1773
|
|
17
18
|
PyPDFForm/middleware/image.py,sha256=HlPUsIktj-NryIkwwdZlilvrd6sZYifs9IuDgTHp7uQ,950
|
|
18
19
|
PyPDFForm/middleware/radio.py,sha256=M4yqHYzHj0jvOGbjYdqeYnNAlYhTF-h47qxqrjXDOoU,1921
|
|
19
20
|
PyPDFForm/middleware/signature.py,sha256=0gexCQwHCEOrjrgvUXeJJCGo2plfSEbXlykPJJCqpfA,2380
|
|
20
|
-
PyPDFForm/middleware/text.py,sha256=
|
|
21
|
+
PyPDFForm/middleware/text.py,sha256=VWL2kLXY_UTz1DkUJOgai9VczU3E4yn_Sa5LwUVTgt4,3091
|
|
21
22
|
PyPDFForm/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
PyPDFForm/widgets/base.py,sha256=
|
|
23
|
+
PyPDFForm/widgets/base.py,sha256=vrWsnrFumgZScSMzRtqxapIkFm3yvasV_xIs2lmI2a0,5327
|
|
23
24
|
PyPDFForm/widgets/bedrock.py,sha256=j6beU04kaQzpAIFZHI5VJLaDT5RVAAa6LzkU1luJpN8,137660
|
|
24
25
|
PyPDFForm/widgets/checkbox.py,sha256=_1I5yh1211RgRUyWzd3NNYpI9JchqJNSJWaZAhl2uOo,1248
|
|
25
26
|
PyPDFForm/widgets/dropdown.py,sha256=zszIT5MI6ggBRUEn7oGBKK0pKmDC9LQw3RnqaKG8ocQ,1764
|
|
26
27
|
PyPDFForm/widgets/image.py,sha256=6y8Ysmk49USr_qWOXD6KGL6cch516cUIlrxoj0pJy9Q,797
|
|
27
28
|
PyPDFForm/widgets/radio.py,sha256=ipadJyHbgftDUvjGk15kapzgHPN3HjdF_iB_7amXR6o,2737
|
|
28
29
|
PyPDFForm/widgets/signature.py,sha256=FdXzja3RTuyU9iyA5Y1yPb4Gsfe4rYlM4gcwekIuwog,4997
|
|
29
|
-
PyPDFForm/widgets/text.py,sha256=
|
|
30
|
-
pypdfform-2.
|
|
31
|
-
pypdfform-2.
|
|
32
|
-
pypdfform-2.
|
|
33
|
-
pypdfform-2.
|
|
34
|
-
pypdfform-2.
|
|
30
|
+
PyPDFForm/widgets/text.py,sha256=O9aTZLnpB_k-ZWx6jNbfKbVLq-O1zjwN3reBMwTNG2M,1241
|
|
31
|
+
pypdfform-2.5.0.dist-info/licenses/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
|
|
32
|
+
pypdfform-2.5.0.dist-info/METADATA,sha256=r860fmyW62b7Vl9lIJiIVllhtQOKrunEnL9DQb0Bu-0,4688
|
|
33
|
+
pypdfform-2.5.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
34
|
+
pypdfform-2.5.0.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
|
|
35
|
+
pypdfform-2.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|