PyPDFForm 3.2.0__tar.gz → 3.3.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.
Potentially problematic release.
This version of PyPDFForm might be problematic. Click here for more details.
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PKG-INFO +1 -1
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/__init__.py +1 -1
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/constants.py +1 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/hooks.py +47 -1
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/base.py +4 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/checkbox.py +2 -1
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/signature.py +6 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/text.py +2 -1
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm.egg-info/PKG-INFO +1 -1
- {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_create_widget.py +145 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/LICENSE +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/adapter.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/coordinate.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/filler.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/font.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/image.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/__init__.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/checkbox.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/dropdown.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/image.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/radio.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/signature.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/text.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/patterns.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/template.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/utils.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/watermark.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/__init__.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/base.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/bedrock.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/dropdown.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/image.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/radio.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/wrapper.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm.egg-info/SOURCES.txt +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm.egg-info/requires.txt +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/README.md +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/pyproject.toml +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/setup.cfg +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_adobe_mode.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_dropdown.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_extract_values.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_fill_method.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_functional.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_paragraph.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_signature.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_use_full_widget_name.py +0 -0
- {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_widget_attr_trigger.py +0 -0
|
@@ -20,7 +20,7 @@ The library supports various PDF form features, including:
|
|
|
20
20
|
PyPDFForm aims to simplify PDF form manipulation, making it accessible to developers of all skill levels.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
__version__ = "3.
|
|
23
|
+
__version__ = "3.3.0"
|
|
24
24
|
|
|
25
25
|
from .middleware.text import Text # exposing for setting global font attrs
|
|
26
26
|
from .wrapper import PdfWrapper
|
|
@@ -22,7 +22,8 @@ from pypdf.generic import (ArrayObject, DictionaryObject, FloatObject,
|
|
|
22
22
|
NameObject, NumberObject, TextStringObject)
|
|
23
23
|
|
|
24
24
|
from .constants import (COMB, DA, FONT_COLOR_IDENTIFIER, FONT_SIZE_IDENTIFIER,
|
|
25
|
-
MULTILINE, READ_ONLY,
|
|
25
|
+
MULTILINE, READ_ONLY, REQUIRED, TU, Annots, Ff, Opt,
|
|
26
|
+
Parent, Q, Rect)
|
|
26
27
|
from .template import get_widget_key
|
|
27
28
|
from .utils import stream_to_io
|
|
28
29
|
|
|
@@ -329,3 +330,48 @@ def flatten_generic(annot: DictionaryObject, val: bool) -> None:
|
|
|
329
330
|
else int(annot.get(NameObject(Ff), 0)) & ~READ_ONLY
|
|
330
331
|
)
|
|
331
332
|
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def update_field_tooltip(annot: DictionaryObject, val: str) -> None:
|
|
336
|
+
"""
|
|
337
|
+
Updates the tooltip (alternate field name) of a form field annotation.
|
|
338
|
+
|
|
339
|
+
This function sets the 'TU' entry in the annotation dictionary, which
|
|
340
|
+
provides a text string that can be used as a tooltip for the field.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
annot (DictionaryObject): The annotation dictionary for the form field.
|
|
344
|
+
val (str): The new tooltip string for the field.
|
|
345
|
+
"""
|
|
346
|
+
if val:
|
|
347
|
+
annot[NameObject(TU)] = TextStringObject(val)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def update_field_required(annot: DictionaryObject, val: bool) -> None:
|
|
351
|
+
"""
|
|
352
|
+
Updates the 'Required' flag of a form field annotation.
|
|
353
|
+
|
|
354
|
+
This function modifies the Ff (flags) entry in the annotation dictionary
|
|
355
|
+
(or its parent if applicable) to set or unset the 'Required' flag,
|
|
356
|
+
making the field mandatory or optional.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
annot (DictionaryObject): The annotation dictionary for the form field.
|
|
360
|
+
val (bool): True to set the field as required, False to make it optional.
|
|
361
|
+
"""
|
|
362
|
+
if Parent in annot and Ff not in annot:
|
|
363
|
+
annot[NameObject(Parent)][NameObject(Ff)] = NumberObject(
|
|
364
|
+
(
|
|
365
|
+
int(annot.get(NameObject(Ff), 0)) | REQUIRED
|
|
366
|
+
if val
|
|
367
|
+
else int(annot.get(NameObject(Ff), 0)) & ~REQUIRED
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
else:
|
|
371
|
+
annot[NameObject(Ff)] = NumberObject(
|
|
372
|
+
(
|
|
373
|
+
int(annot.get(NameObject(Ff), 0)) | REQUIRED
|
|
374
|
+
if val
|
|
375
|
+
else int(annot.get(NameObject(Ff), 0)) & ~REQUIRED
|
|
376
|
+
)
|
|
377
|
+
)
|
|
@@ -23,6 +23,8 @@ class Widget:
|
|
|
23
23
|
|
|
24
24
|
SET_ATTR_TRIGGER_HOOK_MAP = {
|
|
25
25
|
"readonly": "flatten_generic",
|
|
26
|
+
"required": "update_field_required",
|
|
27
|
+
"tooltip": "update_field_tooltip",
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
def __init__(
|
|
@@ -41,7 +43,9 @@ class Widget:
|
|
|
41
43
|
self._name = name
|
|
42
44
|
self._value = value
|
|
43
45
|
self.desc: str = None
|
|
46
|
+
self.tooltip: str = None # TODO: sync tooltip and desc
|
|
44
47
|
self.readonly: bool = None
|
|
48
|
+
self.required: bool = None
|
|
45
49
|
self.hooks_to_trigger: list = []
|
|
46
50
|
|
|
47
51
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
@@ -25,6 +25,7 @@ class CheckBoxWidget(Widget):
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
USER_PARAMS = [
|
|
28
|
+
("tooltip", "tooltip"),
|
|
28
29
|
("button_style", "buttonStyle"),
|
|
29
30
|
("tick_color", "textColor"),
|
|
30
31
|
("bg_color", "fillColor"),
|
|
@@ -32,5 +33,5 @@ class CheckBoxWidget(Widget):
|
|
|
32
33
|
("border_width", "borderWidth"),
|
|
33
34
|
]
|
|
34
35
|
COLOR_PARAMS = ["tick_color", "bg_color", "border_color"]
|
|
35
|
-
ALLOWED_HOOK_PARAMS = ["size"]
|
|
36
|
+
ALLOWED_HOOK_PARAMS = ["required", "size"]
|
|
36
37
|
ACRO_FORM_FUNC = "checkbox"
|
|
@@ -34,6 +34,8 @@ class SignatureWidget:
|
|
|
34
34
|
Attributes:
|
|
35
35
|
OPTIONAL_PARAMS (list): A list of tuples, where each tuple contains the
|
|
36
36
|
parameter name and its default value.
|
|
37
|
+
ALLOWED_HOOK_PARAMS (list): A list of parameter names that can be
|
|
38
|
+
used as hooks to trigger dynamic modifications.
|
|
37
39
|
BEDROCK_WIDGET_TO_COPY (str): The name of the bedrock widget to copy.
|
|
38
40
|
"""
|
|
39
41
|
|
|
@@ -41,6 +43,7 @@ class SignatureWidget:
|
|
|
41
43
|
("width", 160),
|
|
42
44
|
("height", 90),
|
|
43
45
|
]
|
|
46
|
+
ALLOWED_HOOK_PARAMS = ["required", "tooltip"]
|
|
44
47
|
BEDROCK_WIDGET_TO_COPY = "signature"
|
|
45
48
|
|
|
46
49
|
def __init__(
|
|
@@ -71,6 +74,9 @@ class SignatureWidget:
|
|
|
71
74
|
self.optional_params = {
|
|
72
75
|
each[0]: kwargs.get(each[0], each[1]) for each in self.OPTIONAL_PARAMS
|
|
73
76
|
}
|
|
77
|
+
for each in self.ALLOWED_HOOK_PARAMS:
|
|
78
|
+
if each in kwargs:
|
|
79
|
+
self.hook_params.append((each, kwargs.get(each)))
|
|
74
80
|
|
|
75
81
|
def watermarks(self, stream: bytes) -> List[bytes]:
|
|
76
82
|
"""
|
|
@@ -27,6 +27,7 @@ class TextWidget(Widget):
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
USER_PARAMS = [
|
|
30
|
+
("tooltip", "tooltip"),
|
|
30
31
|
("width", "width"),
|
|
31
32
|
("height", "height"),
|
|
32
33
|
("font_size", "fontSize"),
|
|
@@ -37,6 +38,6 @@ class TextWidget(Widget):
|
|
|
37
38
|
("max_length", "maxlen"),
|
|
38
39
|
]
|
|
39
40
|
COLOR_PARAMS = ["font_color", "bg_color", "border_color"]
|
|
40
|
-
ALLOWED_HOOK_PARAMS = ["alignment", "multiline", "comb", "font"]
|
|
41
|
+
ALLOWED_HOOK_PARAMS = ["required", "alignment", "multiline", "comb", "font"]
|
|
41
42
|
NONE_DEFAULTS = ["max_length"]
|
|
42
43
|
ACRO_FORM_FUNC = "textfield"
|
|
@@ -1161,3 +1161,148 @@ def test_create_image_default_filled_flatten(
|
|
|
1161
1161
|
|
|
1162
1162
|
assert len(obj.read()) == len(expected)
|
|
1163
1163
|
assert obj.read() == expected
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
def test_create_required_fields(pdf_samples, request):
|
|
1167
|
+
expected_path = os.path.join(
|
|
1168
|
+
pdf_samples, "widget", "test_create_required_fields.pdf"
|
|
1169
|
+
)
|
|
1170
|
+
with open(expected_path, "rb+") as f:
|
|
1171
|
+
obj = (
|
|
1172
|
+
PdfWrapper(os.path.join(pdf_samples, "dummy.pdf"))
|
|
1173
|
+
.create_widget("text", "new_text", 1, 100, 100, required=True)
|
|
1174
|
+
.create_widget("checkbox", "new_check", 1, 100, 200, required=True)
|
|
1175
|
+
.create_widget(
|
|
1176
|
+
"radio",
|
|
1177
|
+
"new_radio_group",
|
|
1178
|
+
1,
|
|
1179
|
+
[300, 350, 400],
|
|
1180
|
+
[100, 150, 200],
|
|
1181
|
+
required=True,
|
|
1182
|
+
)
|
|
1183
|
+
.create_widget(
|
|
1184
|
+
"dropdown",
|
|
1185
|
+
"new_dropdown",
|
|
1186
|
+
1,
|
|
1187
|
+
400,
|
|
1188
|
+
400,
|
|
1189
|
+
required=True,
|
|
1190
|
+
options=["apple", "banana", "cherry"],
|
|
1191
|
+
)
|
|
1192
|
+
.create_widget(
|
|
1193
|
+
"image",
|
|
1194
|
+
"new_image",
|
|
1195
|
+
1,
|
|
1196
|
+
300,
|
|
1197
|
+
600,
|
|
1198
|
+
required=True,
|
|
1199
|
+
)
|
|
1200
|
+
.create_widget("signature", "new_signature", 1, 100, 600, required=True)
|
|
1201
|
+
)
|
|
1202
|
+
|
|
1203
|
+
request.config.results["expected_path"] = expected_path
|
|
1204
|
+
request.config.results["stream"] = obj.read()
|
|
1205
|
+
|
|
1206
|
+
expected = f.read()
|
|
1207
|
+
|
|
1208
|
+
assert len(obj.read()) == len(expected)
|
|
1209
|
+
assert obj.read() == expected
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
def test_create_not_required_fields(pdf_samples, request):
|
|
1213
|
+
expected_path = os.path.join(
|
|
1214
|
+
pdf_samples, "widget", "test_create_not_required_fields.pdf"
|
|
1215
|
+
)
|
|
1216
|
+
with open(expected_path, "rb+") as f:
|
|
1217
|
+
obj = (
|
|
1218
|
+
PdfWrapper(os.path.join(pdf_samples, "dummy.pdf"))
|
|
1219
|
+
.create_widget("text", "new_text", 1, 100, 100, required=False)
|
|
1220
|
+
.create_widget("checkbox", "new_check", 1, 100, 200, required=False)
|
|
1221
|
+
.create_widget(
|
|
1222
|
+
"radio",
|
|
1223
|
+
"new_radio_group",
|
|
1224
|
+
1,
|
|
1225
|
+
[300, 350, 400],
|
|
1226
|
+
[100, 150, 200],
|
|
1227
|
+
required=False,
|
|
1228
|
+
)
|
|
1229
|
+
.create_widget(
|
|
1230
|
+
"dropdown",
|
|
1231
|
+
"new_dropdown",
|
|
1232
|
+
1,
|
|
1233
|
+
400,
|
|
1234
|
+
400,
|
|
1235
|
+
required=False,
|
|
1236
|
+
options=["apple", "banana", "cherry"],
|
|
1237
|
+
)
|
|
1238
|
+
.create_widget(
|
|
1239
|
+
"image",
|
|
1240
|
+
"new_image",
|
|
1241
|
+
1,
|
|
1242
|
+
300,
|
|
1243
|
+
600,
|
|
1244
|
+
required=False,
|
|
1245
|
+
)
|
|
1246
|
+
.create_widget("signature", "new_signature", 1, 100, 600, required=False)
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
request.config.results["expected_path"] = expected_path
|
|
1250
|
+
request.config.results["stream"] = obj.read()
|
|
1251
|
+
|
|
1252
|
+
expected = f.read()
|
|
1253
|
+
|
|
1254
|
+
assert len(obj.read()) == len(expected)
|
|
1255
|
+
assert obj.read() == expected
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
def test_create_fields_with_tooltips(pdf_samples, request):
|
|
1259
|
+
expected_path = os.path.join(
|
|
1260
|
+
pdf_samples, "widget", "test_create_fields_with_tooltips.pdf"
|
|
1261
|
+
)
|
|
1262
|
+
with open(expected_path, "rb+") as f:
|
|
1263
|
+
obj = (
|
|
1264
|
+
PdfWrapper(os.path.join(pdf_samples, "dummy.pdf"))
|
|
1265
|
+
.create_widget("text", "new_text", 1, 100, 100, tooltip="new_text")
|
|
1266
|
+
.create_widget("checkbox", "new_check", 1, 100, 200, tooltip="new_checkbox")
|
|
1267
|
+
.create_widget(
|
|
1268
|
+
"radio",
|
|
1269
|
+
"new_radio_group",
|
|
1270
|
+
1,
|
|
1271
|
+
[300, 350, 400],
|
|
1272
|
+
[100, 150, 200],
|
|
1273
|
+
tooltip="new_radio_group",
|
|
1274
|
+
)
|
|
1275
|
+
.create_widget(
|
|
1276
|
+
"dropdown",
|
|
1277
|
+
"new_dropdown",
|
|
1278
|
+
1,
|
|
1279
|
+
400,
|
|
1280
|
+
400,
|
|
1281
|
+
options=["apple", "banana", "cherry"],
|
|
1282
|
+
tooltip="new_dropdown",
|
|
1283
|
+
)
|
|
1284
|
+
.create_widget(
|
|
1285
|
+
"image",
|
|
1286
|
+
"new_image",
|
|
1287
|
+
1,
|
|
1288
|
+
300,
|
|
1289
|
+
600,
|
|
1290
|
+
tooltip="new_image",
|
|
1291
|
+
)
|
|
1292
|
+
.create_widget(
|
|
1293
|
+
"signature",
|
|
1294
|
+
"new_signature",
|
|
1295
|
+
1,
|
|
1296
|
+
100,
|
|
1297
|
+
600,
|
|
1298
|
+
tooltip="new_signature",
|
|
1299
|
+
)
|
|
1300
|
+
)
|
|
1301
|
+
|
|
1302
|
+
request.config.results["expected_path"] = expected_path
|
|
1303
|
+
request.config.results["stream"] = obj.read()
|
|
1304
|
+
|
|
1305
|
+
expected = f.read()
|
|
1306
|
+
|
|
1307
|
+
assert len(obj.read()) == len(expected)
|
|
1308
|
+
assert obj.read() == expected
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|