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 +1 -1
- PyPDFForm/constants.py +5 -0
- PyPDFForm/hooks.py +48 -1
- PyPDFForm/middleware/base.py +4 -0
- PyPDFForm/widgets/base.py +36 -0
- PyPDFForm/widgets/checkbox.py +2 -0
- PyPDFForm/widgets/signature.py +6 -0
- PyPDFForm/widgets/text.py +2 -0
- {pypdfform-3.2.0.dist-info → pypdfform-3.3.1.dist-info}/METADATA +1 -1
- {pypdfform-3.2.0.dist-info → pypdfform-3.3.1.dist-info}/RECORD +13 -13
- {pypdfform-3.2.0.dist-info → pypdfform-3.3.1.dist-info}/WHEEL +0 -0
- {pypdfform-3.2.0.dist-info → pypdfform-3.3.1.dist-info}/licenses/LICENSE +0 -0
- {pypdfform-3.2.0.dist-info → pypdfform-3.3.1.dist-info}/top_level.txt +0 -0
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.
|
|
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,
|
|
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
|
+
)
|
PyPDFForm/middleware/base.py
CHANGED
|
@@ -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()
|
PyPDFForm/widgets/checkbox.py
CHANGED
PyPDFForm/widgets/signature.py
CHANGED
|
@@ -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
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
PyPDFForm/__init__.py,sha256=
|
|
1
|
+
PyPDFForm/__init__.py,sha256=kzlOedFQykVQ-7y-ntC2MDQNY0fFVu8B86etcEa60yY,925
|
|
2
2
|
PyPDFForm/adapter.py,sha256=LBxHth0qJFB6rdByRJbsn4x0dftCOAolKVutZeFZm9E,2634
|
|
3
|
-
PyPDFForm/constants.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
23
|
+
PyPDFForm/widgets/base.py,sha256=iWsZ4LDtFVmrVUwvG_ORnH8fd614saI6wfNN4LrgOjQ,7230
|
|
24
24
|
PyPDFForm/widgets/bedrock.py,sha256=j6beU04kaQzpAIFZHI5VJLaDT5RVAAa6LzkU1luJpN8,137660
|
|
25
|
-
PyPDFForm/widgets/checkbox.py,sha256=
|
|
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=
|
|
30
|
-
PyPDFForm/widgets/text.py,sha256=
|
|
31
|
-
pypdfform-3.
|
|
32
|
-
pypdfform-3.
|
|
33
|
-
pypdfform-3.
|
|
34
|
-
pypdfform-3.
|
|
35
|
-
pypdfform-3.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|