PyPDFForm 2.2.5__tar.gz → 2.2.6__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-2.2.5 → pypdfform-2.2.6}/PKG-INFO +1 -1
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/__init__.py +1 -1
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/constants.py +2 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/filler.py +25 -9
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/patterns.py +14 -9
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm.egg-info/PKG-INFO +1 -1
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_adobe_mode.py +65 -4
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_dropdown_simple.py +1 -1
- {pypdfform-2.2.5 → pypdfform-2.2.6}/LICENSE +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/adapter.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/coordinate.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/font.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/image.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/middleware/__init__.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/middleware/base.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/middleware/checkbox.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/middleware/dropdown.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/middleware/image.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/middleware/radio.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/middleware/signature.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/middleware/text.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/template.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/utils.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/watermark.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/widgets/__init__.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/widgets/base.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/widgets/bedrock.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/widgets/checkbox.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/widgets/dropdown.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/widgets/image.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/widgets/radio.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/widgets/signature.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/widgets/text.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm/wrapper.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm.egg-info/SOURCES.txt +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm.egg-info/requires.txt +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/README.md +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/pyproject.toml +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/setup.cfg +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_create_widget.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_dropdown.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_fill_max_length_text_field_simple.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_fill_method.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_functional.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_functional_simple.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_paragraph.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_paragraph_simple.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_preview.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_signature.py +0 -0
- {pypdfform-2.2.5 → pypdfform-2.2.6}/tests/test_use_full_widget_name.py +0 -0
|
@@ -50,6 +50,7 @@ Ff = "/Ff"
|
|
|
50
50
|
Tx = "/Tx"
|
|
51
51
|
V = "/V"
|
|
52
52
|
AP = "/AP"
|
|
53
|
+
I = "/I" # noqa: E741
|
|
53
54
|
N = "/N"
|
|
54
55
|
Sig = "/Sig"
|
|
55
56
|
DA = "/DA"
|
|
@@ -75,6 +76,7 @@ Off = "/Off"
|
|
|
75
76
|
# For Adobe Acrobat
|
|
76
77
|
AcroForm = "/AcroForm"
|
|
77
78
|
Root = "/Root"
|
|
79
|
+
Fields = "/Fields"
|
|
78
80
|
NeedAppearances = "/NeedAppearances"
|
|
79
81
|
|
|
80
82
|
# Field flag bits
|
|
@@ -16,10 +16,11 @@ from io import BytesIO
|
|
|
16
16
|
from typing import Dict, Tuple, Union, cast
|
|
17
17
|
|
|
18
18
|
from pypdf import PdfReader, PdfWriter
|
|
19
|
-
from pypdf.generic import BooleanObject, DictionaryObject,
|
|
19
|
+
from pypdf.generic import (ArrayObject, BooleanObject, DictionaryObject,
|
|
20
|
+
IndirectObject, NameObject)
|
|
20
21
|
|
|
21
22
|
from .constants import (BUTTON_STYLES, DEFAULT_RADIO_STYLE, WIDGET_TYPES,
|
|
22
|
-
AcroForm, Annots, NeedAppearances, Root, U)
|
|
23
|
+
AcroForm, Annots, Fields, NeedAppearances, Root, U)
|
|
23
24
|
from .coordinate import (get_draw_border_coordinates,
|
|
24
25
|
get_draw_checkbox_radio_coordinates,
|
|
25
26
|
get_draw_image_coordinates_resolutions,
|
|
@@ -305,19 +306,34 @@ def fill(
|
|
|
305
306
|
return result
|
|
306
307
|
|
|
307
308
|
|
|
308
|
-
def enable_adobe_mode(
|
|
309
|
-
"""Configures PDF for Adobe Acrobat compatibility
|
|
309
|
+
def enable_adobe_mode(reader: PdfReader, writer: PdfWriter, adobe_mode: bool) -> None:
|
|
310
|
+
"""Configures the PDF for Adobe Acrobat compatibility by setting the NeedAppearances flag
|
|
311
|
+
and ensuring the AcroForm structure is properly initialized.
|
|
310
312
|
|
|
311
313
|
Args:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
+
reader: PdfReader instance of the PDF
|
|
315
|
+
writer: PdfWriter instance to configure
|
|
316
|
+
adobe_mode: If True, enables Adobe Acrobat compatibility mode
|
|
314
317
|
"""
|
|
315
318
|
|
|
316
|
-
if adobe_mode
|
|
317
|
-
|
|
319
|
+
if not adobe_mode:
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
# https://stackoverflow.com/questions/47288578/pdf-form-filled-with-pypdf2-does-not-show-in-print
|
|
323
|
+
if AcroForm in reader.trailer[Root]:
|
|
324
|
+
reader.trailer[Root][AcroForm].update(
|
|
318
325
|
{NameObject(NeedAppearances): BooleanObject(True)}
|
|
319
326
|
)
|
|
320
327
|
|
|
328
|
+
if AcroForm not in writer.root_object:
|
|
329
|
+
writer.root_object.update(
|
|
330
|
+
{NameObject(AcroForm): IndirectObject(len(writer.root_object), 0, writer)}
|
|
331
|
+
)
|
|
332
|
+
writer.root_object[AcroForm][NameObject(NeedAppearances)] = BooleanObject( # noqa
|
|
333
|
+
True
|
|
334
|
+
)
|
|
335
|
+
writer.root_object[AcroForm][NameObject(Fields)] = ArrayObject() # noqa
|
|
336
|
+
|
|
321
337
|
|
|
322
338
|
def simple_fill(
|
|
323
339
|
template: bytes,
|
|
@@ -343,8 +359,8 @@ def simple_fill(
|
|
|
343
359
|
"""
|
|
344
360
|
|
|
345
361
|
pdf = PdfReader(stream_to_io(template))
|
|
346
|
-
enable_adobe_mode(pdf, adobe_mode)
|
|
347
362
|
out = PdfWriter()
|
|
363
|
+
enable_adobe_mode(pdf, out, adobe_mode)
|
|
348
364
|
out.append(pdf)
|
|
349
365
|
|
|
350
366
|
radio_button_tracker = {}
|
|
@@ -15,13 +15,13 @@ The module also contains utility functions for common PDF form operations
|
|
|
15
15
|
like updating field values and flattening form fields.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
from pypdf.generic import (DictionaryObject, NameObject,
|
|
19
|
-
TextStringObject)
|
|
18
|
+
from pypdf.generic import (ArrayObject, DictionaryObject, NameObject,
|
|
19
|
+
NumberObject, TextStringObject)
|
|
20
20
|
|
|
21
21
|
from .constants import (AP, AS, BC, BG, BS, CA, DA, DV, FT,
|
|
22
22
|
IMAGE_FIELD_IDENTIFIER, JS, MK, MULTILINE, READ_ONLY,
|
|
23
|
-
TU, A, Btn, Ch, D, Ff, N, Off, Opt, Parent, Q, S,
|
|
24
|
-
T, Tx, V, W, Yes)
|
|
23
|
+
TU, A, Btn, Ch, D, Ff, I, N, Off, Opt, Parent, Q, S,
|
|
24
|
+
Sig, T, Tx, V, W, Yes)
|
|
25
25
|
from .middleware.checkbox import Checkbox
|
|
26
26
|
from .middleware.dropdown import Dropdown
|
|
27
27
|
from .middleware.image import Image
|
|
@@ -160,13 +160,17 @@ def simple_update_radio_value(annot: DictionaryObject) -> None:
|
|
|
160
160
|
"""Update radio button annotation values to selected state.
|
|
161
161
|
|
|
162
162
|
Modifies the appearance state (AS) of a radio button annotation and updates
|
|
163
|
-
the parent's value (V) to reflect the selected state.
|
|
164
|
-
|
|
163
|
+
the parent's value (V) to reflect the selected state. Removes 'Opt' entry
|
|
164
|
+
from parent dictionary if present. Uses the annotation's appearance
|
|
165
|
+
dictionary (AP/N) to determine valid states.
|
|
165
166
|
|
|
166
167
|
Args:
|
|
167
168
|
annot: PDF radio button annotation dictionary to modify
|
|
168
169
|
"""
|
|
169
170
|
|
|
171
|
+
if Opt in annot[Parent]:
|
|
172
|
+
del annot[Parent][Opt] # noqa
|
|
173
|
+
|
|
170
174
|
for each in annot[AP][N]: # noqa
|
|
171
175
|
if str(each) != Off:
|
|
172
176
|
annot[NameObject(AS)] = NameObject(each)
|
|
@@ -177,9 +181,9 @@ def simple_update_radio_value(annot: DictionaryObject) -> None:
|
|
|
177
181
|
def simple_update_dropdown_value(annot: DictionaryObject, widget: Dropdown) -> None:
|
|
178
182
|
"""Update dropdown annotation values based on widget selection.
|
|
179
183
|
|
|
180
|
-
Modifies the value (V)
|
|
181
|
-
reflect the currently selected choice from the widget.
|
|
182
|
-
standalone dropdowns and those with parent annotations.
|
|
184
|
+
Modifies the value (V), appearance (AP), and index (I) of a dropdown
|
|
185
|
+
annotation to reflect the currently selected choice from the widget.
|
|
186
|
+
Handles both standalone dropdowns and those with parent annotations.
|
|
183
187
|
|
|
184
188
|
Args:
|
|
185
189
|
annot: PDF dropdown annotation dictionary to modify
|
|
@@ -194,6 +198,7 @@ def simple_update_dropdown_value(annot: DictionaryObject, widget: Dropdown) -> N
|
|
|
194
198
|
else:
|
|
195
199
|
annot[NameObject(V)] = TextStringObject(widget.choices[widget.value])
|
|
196
200
|
annot[NameObject(AP)] = TextStringObject(widget.choices[widget.value])
|
|
201
|
+
annot[NameObject(I)] = ArrayObject([NumberObject(widget.value)])
|
|
197
202
|
|
|
198
203
|
|
|
199
204
|
def simple_update_text_value(annot: DictionaryObject, widget: Text) -> None:
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
|
-
from PyPDFForm import FormWrapper
|
|
5
|
+
from PyPDFForm import FormWrapper, PdfWrapper
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def
|
|
8
|
+
def test_dropdown_two(sample_template_with_dropdown, pdf_samples, request):
|
|
9
9
|
expected_path = os.path.join(
|
|
10
|
-
pdf_samples, "adobe_mode", "dropdown", "
|
|
10
|
+
pdf_samples, "adobe_mode", "dropdown", "dropdown_two.pdf"
|
|
11
11
|
)
|
|
12
12
|
with open(expected_path, "rb+") as f:
|
|
13
13
|
obj = FormWrapper(sample_template_with_dropdown).fill(
|
|
@@ -19,7 +19,7 @@ def test_dropdown_one(sample_template_with_dropdown, pdf_samples, request):
|
|
|
19
19
|
"check_2": True,
|
|
20
20
|
"check_3": True,
|
|
21
21
|
"radio_1": 1,
|
|
22
|
-
"dropdown_1":
|
|
22
|
+
"dropdown_1": 1,
|
|
23
23
|
},
|
|
24
24
|
adobe_mode=True,
|
|
25
25
|
)
|
|
@@ -95,3 +95,64 @@ def test_issue_613(pdf_samples, request):
|
|
|
95
95
|
|
|
96
96
|
assert len(obj.read()) == len(expected)
|
|
97
97
|
assert obj.stream == expected
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_sample_template_libary(pdf_samples, request):
|
|
101
|
+
expected_path = os.path.join(
|
|
102
|
+
pdf_samples, "adobe_mode", "test_sample_template_libary.pdf"
|
|
103
|
+
)
|
|
104
|
+
template = (
|
|
105
|
+
PdfWrapper(os.path.join(pdf_samples, "dummy.pdf"))
|
|
106
|
+
.create_widget(
|
|
107
|
+
widget_type="text",
|
|
108
|
+
name="new_text_field_widget",
|
|
109
|
+
page_number=1,
|
|
110
|
+
x=60,
|
|
111
|
+
y=710,
|
|
112
|
+
)
|
|
113
|
+
.create_widget(
|
|
114
|
+
widget_type="checkbox",
|
|
115
|
+
name="new_checkbox_widget",
|
|
116
|
+
page_number=1,
|
|
117
|
+
x=100,
|
|
118
|
+
y=600,
|
|
119
|
+
)
|
|
120
|
+
.create_widget(
|
|
121
|
+
widget_type="radio",
|
|
122
|
+
name="new_radio_group",
|
|
123
|
+
page_number=1,
|
|
124
|
+
x=[50, 100, 150],
|
|
125
|
+
y=[50, 100, 150],
|
|
126
|
+
)
|
|
127
|
+
.create_widget(
|
|
128
|
+
widget_type="dropdown",
|
|
129
|
+
name="new_dropdown_widget",
|
|
130
|
+
page_number=1,
|
|
131
|
+
x=300,
|
|
132
|
+
y=710,
|
|
133
|
+
options=[
|
|
134
|
+
"foo",
|
|
135
|
+
"bar",
|
|
136
|
+
"foobar",
|
|
137
|
+
],
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
with open(expected_path, "rb+") as f:
|
|
142
|
+
obj = FormWrapper(template.read()).fill(
|
|
143
|
+
{
|
|
144
|
+
"new_text_field_widget": "test text",
|
|
145
|
+
"new_checkbox_widget": True,
|
|
146
|
+
"new_radio_group": 1,
|
|
147
|
+
"new_dropdown_widget": 2,
|
|
148
|
+
},
|
|
149
|
+
adobe_mode=True,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
request.config.results["expected_path"] = expected_path
|
|
153
|
+
request.config.results["stream"] = obj.read()
|
|
154
|
+
|
|
155
|
+
expected = f.read()
|
|
156
|
+
|
|
157
|
+
assert len(obj.read()) == len(expected)
|
|
158
|
+
assert obj.stream == expected
|
|
@@ -75,7 +75,7 @@ def test_dropdown_two(sample_template_with_dropdown, pdf_samples, request):
|
|
|
75
75
|
|
|
76
76
|
def test_dropdown_two_flatten(sample_template_with_dropdown, pdf_samples, request):
|
|
77
77
|
expected_path = os.path.join(
|
|
78
|
-
pdf_samples, "simple", "dropdown", "
|
|
78
|
+
pdf_samples, "simple", "dropdown", "dropdown_two_flatten.pdf"
|
|
79
79
|
)
|
|
80
80
|
with open(expected_path, "rb+") as f:
|
|
81
81
|
obj = FormWrapper(sample_template_with_dropdown).fill(
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|