PyPDFForm 3.1.0__tar.gz → 3.1.2__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.1.0 → pypdfform-3.1.2}/PKG-INFO +1 -1
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/__init__.py +1 -1
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/constants.py +2 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/patterns.py +86 -4
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/template.py +11 -1
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/utils.py +3 -1
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm.egg-info/PKG-INFO +1 -1
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm.egg-info/SOURCES.txt +1 -0
- pypdfform-3.1.2/tests/test_extract_values.py +72 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/LICENSE +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/adapter.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/coordinate.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/filler.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/font.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/hooks.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/image.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/middleware/__init__.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/middleware/base.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/middleware/checkbox.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/middleware/dropdown.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/middleware/image.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/middleware/radio.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/middleware/signature.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/middleware/text.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/watermark.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/widgets/__init__.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/widgets/base.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/widgets/bedrock.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/widgets/checkbox.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/widgets/dropdown.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/widgets/image.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/widgets/radio.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/widgets/signature.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/widgets/text.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm/wrapper.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm.egg-info/requires.txt +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/README.md +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/pyproject.toml +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/setup.cfg +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/tests/test_adobe_mode.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/tests/test_create_widget.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/tests/test_dropdown.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/tests/test_fill_method.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/tests/test_functional.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/tests/test_paragraph.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/tests/test_signature.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/tests/test_use_full_widget_name.py +0 -0
- {pypdfform-3.1.0 → pypdfform-3.1.2}/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.1.
|
|
23
|
+
__version__ = "3.1.2"
|
|
24
24
|
|
|
25
25
|
from .middleware.text import Text # exposing for setting global font attrs
|
|
26
26
|
from .wrapper import PdfWrapper
|
|
@@ -8,11 +8,13 @@ properties in the PDF's annotation dictionary. It also provides utility function
|
|
|
8
8
|
for updating these widgets.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
from typing import Union
|
|
12
|
+
|
|
11
13
|
from pypdf.generic import (ArrayObject, DictionaryObject, NameObject,
|
|
12
14
|
NumberObject, TextStringObject)
|
|
13
15
|
|
|
14
|
-
from .constants import (AP, AS, DV, FT, IMAGE_FIELD_IDENTIFIER, JS,
|
|
15
|
-
Ch, I, N, Off, Opt, Parent, Sig, T, Tx, V, Yes)
|
|
16
|
+
from .constants import (AP, AS, DV, FT, IMAGE_FIELD_IDENTIFIER, JS, SLASH, TU,
|
|
17
|
+
A, Btn, Ch, I, N, Off, Opt, Parent, Sig, T, Tx, V, Yes)
|
|
16
18
|
from .middleware.checkbox import Checkbox
|
|
17
19
|
from .middleware.dropdown import Dropdown
|
|
18
20
|
from .middleware.image import Image
|
|
@@ -42,7 +44,7 @@ WIDGET_TYPE_PATTERNS = [
|
|
|
42
44
|
(
|
|
43
45
|
{FT: Btn},
|
|
44
46
|
{Parent: {FT: Btn}},
|
|
45
|
-
{AS: (Yes, Off)},
|
|
47
|
+
{AS: (Yes, Off, SLASH)},
|
|
46
48
|
),
|
|
47
49
|
Radio,
|
|
48
50
|
),
|
|
@@ -76,7 +78,7 @@ WIDGET_TYPE_PATTERNS = [
|
|
|
76
78
|
(
|
|
77
79
|
(
|
|
78
80
|
{Parent: {FT: Btn}},
|
|
79
|
-
{AS: (Yes, Off)},
|
|
81
|
+
{AS: (Yes, Off, SLASH)},
|
|
80
82
|
),
|
|
81
83
|
Radio,
|
|
82
84
|
),
|
|
@@ -113,6 +115,23 @@ def update_checkbox_value(annot: DictionaryObject, check: bool = False) -> None:
|
|
|
113
115
|
break
|
|
114
116
|
|
|
115
117
|
|
|
118
|
+
def get_checkbox_value(annot: DictionaryObject) -> Union[bool, None]:
|
|
119
|
+
"""
|
|
120
|
+
Retrieves the boolean value of a checkbox annotation.
|
|
121
|
+
|
|
122
|
+
This function checks the value (V) of the checkbox annotation. If the value
|
|
123
|
+
is not 'Off', it means the checkbox is checked, and True is returned.
|
|
124
|
+
Otherwise, if the value is 'Off' or not present, None is returned.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
annot (DictionaryObject): The checkbox annotation dictionary.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Union[bool, None]: True if the checkbox is checked, None otherwise.
|
|
131
|
+
"""
|
|
132
|
+
return True if annot.get(V, Off) != Off else None
|
|
133
|
+
|
|
134
|
+
|
|
116
135
|
def update_radio_value(annot: DictionaryObject) -> None:
|
|
117
136
|
"""
|
|
118
137
|
Updates the value of a radio button annotation, selecting it.
|
|
@@ -133,6 +152,28 @@ def update_radio_value(annot: DictionaryObject) -> None:
|
|
|
133
152
|
break
|
|
134
153
|
|
|
135
154
|
|
|
155
|
+
def get_radio_value(annot: DictionaryObject) -> bool:
|
|
156
|
+
"""
|
|
157
|
+
Retrieves the boolean value of a radio button annotation.
|
|
158
|
+
|
|
159
|
+
This function iterates through the appearance states (AP) of the radio button
|
|
160
|
+
annotation. If the value (V) of the parent dictionary matches any of these
|
|
161
|
+
appearance states, it means the radio button is selected, and True is returned.
|
|
162
|
+
Otherwise, False is returned.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
annot (DictionaryObject): The radio button annotation dictionary.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
bool: True if the radio button is selected, False otherwise.
|
|
169
|
+
"""
|
|
170
|
+
for each in annot.get(AP, {}).get(N, []):
|
|
171
|
+
if annot.get(Parent, {}).get(V) == each:
|
|
172
|
+
return True
|
|
173
|
+
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
|
|
136
177
|
def update_dropdown_value(annot: DictionaryObject, widget: Dropdown) -> None:
|
|
137
178
|
"""
|
|
138
179
|
Updates the value of a dropdown annotation, selecting an option from the list.
|
|
@@ -157,6 +198,29 @@ def update_dropdown_value(annot: DictionaryObject, widget: Dropdown) -> None:
|
|
|
157
198
|
annot[NameObject(I)] = ArrayObject([NumberObject(widget.value)])
|
|
158
199
|
|
|
159
200
|
|
|
201
|
+
def get_dropdown_value(annot: DictionaryObject, widget: Dropdown) -> None:
|
|
202
|
+
"""
|
|
203
|
+
Retrieves the selected value of a dropdown annotation and updates the widget.
|
|
204
|
+
|
|
205
|
+
This function determines the current value of the dropdown, considering
|
|
206
|
+
whether it's a child annotation or a top-level one. It then iterates
|
|
207
|
+
through the widget's choices to find a match and sets the widget's
|
|
208
|
+
value to the index of the matched choice.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
annot (DictionaryObject): The dropdown annotation dictionary.
|
|
212
|
+
widget (Dropdown): The Dropdown widget object to update with the retrieved value.
|
|
213
|
+
"""
|
|
214
|
+
if Parent in annot and T not in annot:
|
|
215
|
+
to_compare = annot.get(Parent, {}).get(V)
|
|
216
|
+
else:
|
|
217
|
+
to_compare = annot.get(V)
|
|
218
|
+
|
|
219
|
+
for i, each in enumerate(widget.choices):
|
|
220
|
+
if each == to_compare:
|
|
221
|
+
widget.value = i or None # set None when 0
|
|
222
|
+
|
|
223
|
+
|
|
160
224
|
def update_text_value(annot: DictionaryObject, widget: Text) -> None:
|
|
161
225
|
"""
|
|
162
226
|
Updates the value of a text annotation, setting the text content.
|
|
@@ -176,6 +240,24 @@ def update_text_value(annot: DictionaryObject, widget: Text) -> None:
|
|
|
176
240
|
annot[NameObject(AP)] = TextStringObject(widget.value)
|
|
177
241
|
|
|
178
242
|
|
|
243
|
+
def get_text_value(annot: DictionaryObject, widget: Text) -> None:
|
|
244
|
+
"""
|
|
245
|
+
Retrieves the text value of a text annotation and updates the widget.
|
|
246
|
+
|
|
247
|
+
This function determines the current text value of the annotation, considering
|
|
248
|
+
whether it's a child annotation or a top-level one, and then sets the
|
|
249
|
+
widget's value accordingly.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
annot (DictionaryObject): The text annotation dictionary.
|
|
253
|
+
widget (Text): The Text widget object to update with the retrieved value.
|
|
254
|
+
"""
|
|
255
|
+
if Parent in annot and T not in annot:
|
|
256
|
+
widget.value = annot[Parent].get(V)
|
|
257
|
+
else:
|
|
258
|
+
widget.value = annot.get(V)
|
|
259
|
+
|
|
260
|
+
|
|
179
261
|
def update_annotation_name(annot: DictionaryObject, val: str) -> None:
|
|
180
262
|
"""
|
|
181
263
|
Updates the name of an annotation, setting the T (title) entry.
|
|
@@ -16,12 +16,14 @@ from pypdf import PdfReader, PdfWriter
|
|
|
16
16
|
from pypdf.generic import DictionaryObject
|
|
17
17
|
|
|
18
18
|
from .constants import WIDGET_TYPES, Annots, MaxLen, Parent, T
|
|
19
|
+
from .middleware.checkbox import Checkbox
|
|
19
20
|
from .middleware.dropdown import Dropdown
|
|
20
21
|
from .middleware.radio import Radio
|
|
21
22
|
from .middleware.text import Text
|
|
22
23
|
from .patterns import (DROPDOWN_CHOICE_PATTERNS, WIDGET_DESCRIPTION_PATTERNS,
|
|
23
24
|
WIDGET_KEY_PATTERNS, WIDGET_TYPE_PATTERNS,
|
|
24
|
-
|
|
25
|
+
get_checkbox_value, get_dropdown_value, get_radio_value,
|
|
26
|
+
get_text_value, update_annotation_name)
|
|
25
27
|
from .utils import extract_widget_property, find_pattern_match, stream_to_io
|
|
26
28
|
|
|
27
29
|
|
|
@@ -61,11 +63,16 @@ def build_widgets(
|
|
|
61
63
|
if isinstance(_widget, Text):
|
|
62
64
|
# mostly for schema for now
|
|
63
65
|
_widget.max_length = get_text_field_max_length(widget)
|
|
66
|
+
get_text_value(widget, _widget)
|
|
67
|
+
|
|
68
|
+
if type(_widget) is Checkbox:
|
|
69
|
+
_widget.value = get_checkbox_value(widget)
|
|
64
70
|
|
|
65
71
|
if isinstance(_widget, Dropdown):
|
|
66
72
|
# actually used for filling value
|
|
67
73
|
# doesn't trigger hook
|
|
68
74
|
_widget.__dict__["choices"] = get_dropdown_choices(widget)
|
|
75
|
+
get_dropdown_value(widget, _widget)
|
|
69
76
|
|
|
70
77
|
if isinstance(_widget, Radio):
|
|
71
78
|
if key not in results:
|
|
@@ -73,6 +80,9 @@ def build_widgets(
|
|
|
73
80
|
|
|
74
81
|
# for schema
|
|
75
82
|
results[key].number_of_options += 1
|
|
83
|
+
|
|
84
|
+
if get_radio_value(widget):
|
|
85
|
+
results[key].value = results[key].number_of_options - 1
|
|
76
86
|
continue
|
|
77
87
|
|
|
78
88
|
results[key] = _widget
|
|
@@ -23,7 +23,7 @@ from typing import Any, BinaryIO, List, Union
|
|
|
23
23
|
from pypdf import PdfReader, PdfWriter
|
|
24
24
|
from pypdf.generic import ArrayObject, DictionaryObject, NameObject
|
|
25
25
|
|
|
26
|
-
from .constants import UNIQUE_SUFFIX_LENGTH, XFA, AcroForm, Annots, Root
|
|
26
|
+
from .constants import SLASH, UNIQUE_SUFFIX_LENGTH, XFA, AcroForm, Annots, Root
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@lru_cache
|
|
@@ -220,6 +220,8 @@ def find_pattern_match(pattern: dict, widget: Union[dict, DictionaryObject]) ->
|
|
|
220
220
|
else:
|
|
221
221
|
if isinstance(pattern[key], tuple):
|
|
222
222
|
result = value in pattern[key]
|
|
223
|
+
if not result and SLASH in pattern[key] and value.startswith(SLASH):
|
|
224
|
+
result = True
|
|
223
225
|
else:
|
|
224
226
|
result = pattern[key] == value
|
|
225
227
|
if result:
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from PyPDFForm import PdfWrapper
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_text_check_values(pdf_samples, data_dict):
|
|
9
|
+
obj = PdfWrapper(os.path.join(pdf_samples, "test_fill.pdf"))
|
|
10
|
+
|
|
11
|
+
for k, v in data_dict.items():
|
|
12
|
+
if v is False:
|
|
13
|
+
assert obj.widgets[k].value is None
|
|
14
|
+
else:
|
|
15
|
+
assert obj.widgets[k].value == v
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_radio_values(pdf_samples):
|
|
19
|
+
data_dict = {
|
|
20
|
+
"radio_1": 0,
|
|
21
|
+
"radio_2": 1,
|
|
22
|
+
"radio_3": 2,
|
|
23
|
+
}
|
|
24
|
+
obj = PdfWrapper(os.path.join(pdf_samples, "test_fill_radiobutton.pdf"))
|
|
25
|
+
|
|
26
|
+
for k, v in data_dict.items():
|
|
27
|
+
assert obj.widgets[k].value == v
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_sejda_values(pdf_samples, sejda_data):
|
|
31
|
+
obj = PdfWrapper(os.path.join(pdf_samples, "test_fill_sejda.pdf"))
|
|
32
|
+
|
|
33
|
+
for k, v in sejda_data.items():
|
|
34
|
+
if v is False:
|
|
35
|
+
assert obj.widgets[k].value is None
|
|
36
|
+
else:
|
|
37
|
+
assert obj.widgets[k].value == v
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_dropdown_values(pdf_samples):
|
|
41
|
+
obj = PdfWrapper(os.path.join(pdf_samples, "dropdown", "test_dropdown_two.pdf"))
|
|
42
|
+
|
|
43
|
+
assert obj.widgets["dropdown_1"].value == 1
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_dropdown_default_values(pdf_samples):
|
|
47
|
+
obj = PdfWrapper(os.path.join(pdf_samples, "dropdown", "test_dropdown_one.pdf"))
|
|
48
|
+
|
|
49
|
+
assert obj.widgets["dropdown_1"].value is None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_sejda_dropdown_values(pdf_samples):
|
|
53
|
+
obj = PdfWrapper(
|
|
54
|
+
os.path.join(pdf_samples, "dropdown", "test_dropdown_alignment_sejda.pdf")
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
assert obj.widgets["dropdown_left"].value is None
|
|
58
|
+
assert obj.widgets["dropdown_center"].value == 1
|
|
59
|
+
assert obj.widgets["dropdown_right"].value == 2
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_addition_operator_3_times_values(template_stream, data_dict):
|
|
63
|
+
result = PdfWrapper()
|
|
64
|
+
|
|
65
|
+
for _ in range(3):
|
|
66
|
+
result += PdfWrapper(template_stream).fill(data_dict)
|
|
67
|
+
|
|
68
|
+
obj = PdfWrapper(result.read())
|
|
69
|
+
|
|
70
|
+
for k, v in obj.widgets.items():
|
|
71
|
+
if k.split("-")[0] in data_dict:
|
|
72
|
+
assert (v.value or False) == data_dict[k.split("-")[0]]
|
|
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
|
|
File without changes
|