PyPDFForm 3.2.0__py3-none-any.whl → 3.3.1__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 CHANGED
@@ -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.1"
24
24
 
25
25
  from .middleware.text import Text # exposing for setting global font attrs
26
26
  from .wrapper import PdfWrapper
PyPDFForm/constants.py CHANGED
@@ -95,9 +95,14 @@ 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
 
102
+ # reportlab acroform func
103
+ fieldFlags = "fieldFlags"
104
+ required = "required"
105
+
101
106
  FONT_SIZE_IDENTIFIER = "Tf"
102
107
  FONT_COLOR_IDENTIFIER = " rg"
103
108
  DEFAULT_FONT = "Helvetica"
PyPDFForm/hooks.py CHANGED
@@ -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,49 @@ 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
+ # TODO: add a test case when supporting edit required
363
+ # if Parent in annot and Ff not in annot:
364
+ # annot[NameObject(Parent)][NameObject(Ff)] = NumberObject(
365
+ # (
366
+ # int(annot.get(NameObject(Ff), 0)) | REQUIRED
367
+ # if val
368
+ # else int(annot.get(NameObject(Ff), 0)) & ~REQUIRED
369
+ # )
370
+ # )
371
+ # else:
372
+ annot[NameObject(Ff)] = NumberObject(
373
+ (
374
+ int(annot.get(NameObject(Ff), 0)) | REQUIRED
375
+ if val
376
+ else int(annot.get(NameObject(Ff), 0)) & ~REQUIRED
377
+ )
378
+ )
@@ -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:
PyPDFForm/widgets/base.py CHANGED
@@ -10,6 +10,7 @@ for rendering the widget on a PDF page.
10
10
  # TODO: In `watermarks`, `PdfReader(stream_to_io(stream))` is called, which re-parses the PDF for each widget. If multiple widgets are being processed, consider passing the `PdfReader` object directly to avoid redundant parsing.
11
11
  # TODO: In `watermarks`, the list comprehension `[watermark.read() if i == self.page_number - 1 else b"" for i in range(page_count)]` creates a new `BytesIO` object and reads from it for each widget. If many widgets are created, this could be optimized by creating the `BytesIO` object once and passing it around, or by directly returning the watermark bytes and its page number.
12
12
 
13
+ from inspect import signature
13
14
  from io import BytesIO
14
15
  from typing import List, Union
15
16
 
@@ -17,6 +18,7 @@ from pypdf import PdfReader
17
18
  from reportlab.lib.colors import Color
18
19
  from reportlab.pdfgen.canvas import Canvas
19
20
 
21
+ from ..constants import fieldFlags, required
20
22
  from ..utils import stream_to_io
21
23
 
22
24
 
@@ -95,6 +97,39 @@ class Widget:
95
97
  if each in kwargs:
96
98
  self.hook_params.append((each, kwargs.get(each)))
97
99
 
100
+ def _required_handler(self, canvas: Canvas) -> None:
101
+ """
102
+ Handles the 'Required' flag for the widget's AcroForm field.
103
+
104
+ This method inspects the default flags of the AcroForm function associated
105
+ with the widget and modifies them based on the widget's 'required' parameter.
106
+ If the widget is marked as required, the 'required' flag is added to the
107
+ AcroForm field flags; otherwise, it is removed. This ensures the PDF form
108
+ field's required status is correctly reflected.
109
+
110
+ Args:
111
+ canvas (Canvas): The ReportLab canvas object used for PDF operations.
112
+ """
113
+ default_flags = signature(
114
+ getattr(canvas.acroForm, self.ACRO_FORM_FUNC)
115
+ ).parameters.get(fieldFlags)
116
+ default_flags = (
117
+ default_flags.default.split(" ")
118
+ if default_flags and default_flags.default
119
+ else []
120
+ )
121
+
122
+ if self.acro_form_params.get(required):
123
+ default_flags.append(required)
124
+ else:
125
+ if required in default_flags:
126
+ default_flags.remove(required)
127
+
128
+ default_flags = " ".join(list(set(default_flags)))
129
+ self.acro_form_params[fieldFlags] = default_flags
130
+ if required in self.acro_form_params:
131
+ del self.acro_form_params[required]
132
+
98
133
  def canvas_operations(self, canvas: Canvas) -> None:
99
134
  """
100
135
  Performs canvas operations for the widget.
@@ -141,6 +176,7 @@ class Widget:
141
176
  ),
142
177
  )
143
178
 
179
+ self._required_handler(canvas)
144
180
  self.canvas_operations(canvas)
145
181
 
146
182
  canvas.showPage()
@@ -25,6 +25,8 @@ class CheckBoxWidget(Widget):
25
25
  """
26
26
 
27
27
  USER_PARAMS = [
28
+ ("required", "required"),
29
+ ("tooltip", "tooltip"),
28
30
  ("button_style", "buttonStyle"),
29
31
  ("tick_color", "textColor"),
30
32
  ("bg_color", "fillColor"),
@@ -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
  """
PyPDFForm/widgets/text.py CHANGED
@@ -27,6 +27,8 @@ class TextWidget(Widget):
27
27
  """
28
28
 
29
29
  USER_PARAMS = [
30
+ ("required", "required"),
31
+ ("tooltip", "tooltip"),
30
32
  ("width", "width"),
31
33
  ("height", "height"),
32
34
  ("font_size", "fontSize"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 3.2.0
3
+ Version: 3.3.1
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -1,10 +1,10 @@
1
- PyPDFForm/__init__.py,sha256=MdhapOSXGvZvnL2gmvBhLy8__hBxfCI3beVkrNljOcg,925
1
+ PyPDFForm/__init__.py,sha256=kzlOedFQykVQ-7y-ntC2MDQNY0fFVu8B86etcEa60yY,925
2
2
  PyPDFForm/adapter.py,sha256=LBxHth0qJFB6rdByRJbsn4x0dftCOAolKVutZeFZm9E,2634
3
- PyPDFForm/constants.py,sha256=GU0LcNbN-ttYQVVoFGQLysKByJYF4lKoMideU65z_wI,2523
3
+ PyPDFForm/constants.py,sha256=rxE9KyygdMkuPfpO6PRV6DasvIta0sZqadVPtX9Q_1U,2616
4
4
  PyPDFForm/coordinate.py,sha256=veYOlRyFKIvzLISYA_f-drNBiKOzFwr0EIFCaUAzGgo,4428
5
5
  PyPDFForm/filler.py,sha256=fqGIxT3FR3cWo3SMTDYud6Ocs9SZBmSpFv5yg1v19Wk,8450
6
6
  PyPDFForm/font.py,sha256=opZjAacsIRFcERXWegPXkOSpmnRrv4y_50yD0_BjWPM,10273
7
- PyPDFForm/hooks.py,sha256=lXw3nNxYmgRp_IYe3aEXPeGCQxO_l6ruFzjmSeqfvIc,12659
7
+ PyPDFForm/hooks.py,sha256=E-KVXQGIcyJfCOJpIIf8otgZw1eQ6FqifBZ92kPze5o,14327
8
8
  PyPDFForm/image.py,sha256=P1P3Ejm8PVPQwpJFGAesjtwS5hxnVItrj75TE3WnFhM,4607
9
9
  PyPDFForm/patterns.py,sha256=HbTqzFllQ1cW3CqyNEfVh0qUMeFerbvOd0-HQnkifQQ,9765
10
10
  PyPDFForm/template.py,sha256=Jvx99HjLcEG8fZQeGSPZEFcITa4jauPSvenj3XgAf3c,11046
@@ -12,7 +12,7 @@ PyPDFForm/utils.py,sha256=JavhAO4HmYRdujlsPXcZWGXTf7wDXzj4uU1XGRFsAaA,13257
12
12
  PyPDFForm/watermark.py,sha256=BJ8NeZLKf-MuJ2XusHiALaQpoqE8j6hHGbWcNhpjxN0,11299
13
13
  PyPDFForm/wrapper.py,sha256=KTFou6cXrHtLHVKwngoIr4Pwu4vOfjXY0cWRNNDlW0U,28866
14
14
  PyPDFForm/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- PyPDFForm/middleware/base.py,sha256=zBO9YP01dAEfFKoHKDg10XcpXEuYdFd-pb5wSFmJJj0,3091
15
+ PyPDFForm/middleware/base.py,sha256=ZmJFh3nSxj6PFjqBqsLih0pXKtcm1o-ctJVWn0v6bbI,3278
16
16
  PyPDFForm/middleware/checkbox.py,sha256=OCSZEFD8wQG_Y9qO7Os6VXTaxJCpkRYTxI4wDgG0GZc,1870
17
17
  PyPDFForm/middleware/dropdown.py,sha256=pfiMuAOr3ze7eboCB55UKaSR89oLNhvHGvNmDGWHVS0,3855
18
18
  PyPDFForm/middleware/image.py,sha256=eKM7anU56jbaECnK6rq0jGsBRY3HW_fM86fgA3hq7xA,585
@@ -20,16 +20,16 @@ PyPDFForm/middleware/radio.py,sha256=PuGDJ8RN1C-MkL9Jf14ABWYV67cN18R66dI4nR-03DU
20
20
  PyPDFForm/middleware/signature.py,sha256=P6Mg9AZP5jML7GawsteVZjDaunKb9Yazu5iy0qF60bo,2432
21
21
  PyPDFForm/middleware/text.py,sha256=GLKuYvG4BUtNvj-3NkDeIlV1jcouhn7gAqfm9TBWduQ,3936
22
22
  PyPDFForm/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- PyPDFForm/widgets/base.py,sha256=vudfjwlybj82pxQhy6K8Qds9osFqn219Ze3yMs8QQuU,5786
23
+ PyPDFForm/widgets/base.py,sha256=iWsZ4LDtFVmrVUwvG_ORnH8fd614saI6wfNN4LrgOjQ,7230
24
24
  PyPDFForm/widgets/bedrock.py,sha256=j6beU04kaQzpAIFZHI5VJLaDT5RVAAa6LzkU1luJpN8,137660
25
- PyPDFForm/widgets/checkbox.py,sha256=s4a0a1pAemQyrz3SpZHzIPoVLvJZAV72KEfxKp15dyk,1281
25
+ PyPDFForm/widgets/checkbox.py,sha256=gqqulFdjrLd6Ry-BM4sHwGu0QogfKnG_6v3RcYLeYMo,1347
26
26
  PyPDFForm/widgets/dropdown.py,sha256=6zZwt6eU9Hgwl-57QfyT3G6c37FkQTJ-XSsXGluWevs,1459
27
27
  PyPDFForm/widgets/image.py,sha256=aSD-3MEZFIRL7HYVuO6Os8irfSUOLHA_rHGkqcEIPPA,855
28
28
  PyPDFForm/widgets/radio.py,sha256=oFw8Um4g414UH93QJv6dZHRxpq0yuYog09B2W3eE8wo,2612
29
- PyPDFForm/widgets/signature.py,sha256=vk9w9YmUpABvRhRcvllXw_Wr2SvKyCXDVQEFsR77ugM,4820
30
- PyPDFForm/widgets/text.py,sha256=gtheE6_w0vQPRJJ9oj_l9FaMDEGnPtvVR6_axsrmxKI,1540
31
- pypdfform-3.2.0.dist-info/licenses/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
32
- pypdfform-3.2.0.dist-info/METADATA,sha256=qjn9PkGilefmwJ0MocWv3DI8hvEDnJjh6buHCWzVpqA,4538
33
- pypdfform-3.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- pypdfform-3.2.0.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
35
- pypdfform-3.2.0.dist-info/RECORD,,
29
+ PyPDFForm/widgets/signature.py,sha256=L4Et6pxtrEh7U-lnnLZrnvb_dKwGNpI6TZ11HCD0lvY,5147
30
+ PyPDFForm/widgets/text.py,sha256=rmI0EYUTVf06AKsnxsVqewdTjt_mhBASEHyj5nAvYjE,1606
31
+ pypdfform-3.3.1.dist-info/licenses/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
32
+ pypdfform-3.3.1.dist-info/METADATA,sha256=zbqEKsATFK12_iuYaAfc3p1K7MDw4lripco_cyGFKj8,4538
33
+ pypdfform-3.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ pypdfform-3.3.1.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
35
+ pypdfform-3.3.1.dist-info/RECORD,,