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.

Files changed (51) hide show
  1. {pypdfform-3.2.0 → pypdfform-3.3.0}/PKG-INFO +1 -1
  2. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/__init__.py +1 -1
  3. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/constants.py +1 -0
  4. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/hooks.py +47 -1
  5. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/base.py +4 -0
  6. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/checkbox.py +2 -1
  7. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/signature.py +6 -0
  8. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/text.py +2 -1
  9. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm.egg-info/PKG-INFO +1 -1
  10. {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_create_widget.py +145 -0
  11. {pypdfform-3.2.0 → pypdfform-3.3.0}/LICENSE +0 -0
  12. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/adapter.py +0 -0
  13. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/coordinate.py +0 -0
  14. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/filler.py +0 -0
  15. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/font.py +0 -0
  16. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/image.py +0 -0
  17. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/__init__.py +0 -0
  18. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/checkbox.py +0 -0
  19. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/dropdown.py +0 -0
  20. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/image.py +0 -0
  21. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/radio.py +0 -0
  22. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/signature.py +0 -0
  23. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/middleware/text.py +0 -0
  24. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/patterns.py +0 -0
  25. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/template.py +0 -0
  26. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/utils.py +0 -0
  27. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/watermark.py +0 -0
  28. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/__init__.py +0 -0
  29. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/base.py +0 -0
  30. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/bedrock.py +0 -0
  31. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/dropdown.py +0 -0
  32. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/image.py +0 -0
  33. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/widgets/radio.py +0 -0
  34. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm/wrapper.py +0 -0
  35. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm.egg-info/SOURCES.txt +0 -0
  36. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  37. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm.egg-info/requires.txt +0 -0
  38. {pypdfform-3.2.0 → pypdfform-3.3.0}/PyPDFForm.egg-info/top_level.txt +0 -0
  39. {pypdfform-3.2.0 → pypdfform-3.3.0}/README.md +0 -0
  40. {pypdfform-3.2.0 → pypdfform-3.3.0}/pyproject.toml +0 -0
  41. {pypdfform-3.2.0 → pypdfform-3.3.0}/setup.cfg +0 -0
  42. {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_adobe_mode.py +0 -0
  43. {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_dropdown.py +0 -0
  44. {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_extract_values.py +0 -0
  45. {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_fill_max_length_text_field.py +0 -0
  46. {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_fill_method.py +0 -0
  47. {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_functional.py +0 -0
  48. {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_paragraph.py +0 -0
  49. {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_signature.py +0 -0
  50. {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_use_full_widget_name.py +0 -0
  51. {pypdfform-3.2.0 → pypdfform-3.3.0}/tests/test_widget_attr_trigger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -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.2.0"
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
@@ -95,6 +95,7 @@ XFA = "/XFA"
95
95
 
96
96
  # Field flag bits
97
97
  READ_ONLY = 1 << 0
98
+ REQUIRED = 1 << 1
98
99
  MULTILINE = 1 << 12
99
100
  COMB = 1 << 24
100
101
 
@@ -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, Annots, Ff, Opt, Parent, Q, Rect)
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -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