PyPDFForm 4.3.0__tar.gz → 4.4.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.
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PKG-INFO +1 -1
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/__init__.py +1 -1
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/template.py +39 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/wrapper.py +13 -2
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm.egg-info/PKG-INFO +1 -1
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm.egg-info/SOURCES.txt +0 -1
- pypdfform-4.4.0/tests/test_bulk_create_fields.py +146 -0
- pypdfform-4.4.0/tests/test_create_widget.py +652 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_draw_elements.py +0 -143
- pypdfform-4.4.0/tests/test_dropdown.py +195 -0
- pypdfform-4.4.0/tests/test_fill_max_length_text_field.py +143 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_functional.py +9 -414
- pypdfform-4.4.0/tests/test_paragraph.py +133 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_signature.py +0 -44
- {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_widget_attr_trigger.py +0 -195
- pypdfform-4.3.0/tests/test_bulk_create_fields.py +0 -296
- pypdfform-4.3.0/tests/test_create_widget.py +0 -1421
- pypdfform-4.3.0/tests/test_dropdown.py +0 -467
- pypdfform-4.3.0/tests/test_fill_max_length_text_field.py +0 -446
- pypdfform-4.3.0/tests/test_fill_method.py +0 -141
- pypdfform-4.3.0/tests/test_paragraph.py +0 -416
- {pypdfform-4.3.0 → pypdfform-4.4.0}/LICENSE +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/adapter.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/ap.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/assets/__init__.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/assets/bedrock.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/assets/blank.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/constants.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/coordinate.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/deprecation.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/filler.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/font.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/hooks.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/image.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/__init__.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/base.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/checkbox.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/dropdown.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/image.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/radio.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/signature.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/text.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/patterns.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/__init__.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/circle.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/ellipse.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/image.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/line.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/rect.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/text.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/types.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/utils.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/watermark.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/__init__.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/base.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/checkbox.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/dropdown.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/image.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/radio.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/signature.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/text.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm.egg-info/requires.txt +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/README.md +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/pyproject.toml +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/setup.cfg +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_extract_values.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_font_widths.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_generate_appearance_streams.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_js.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_need_appearances.py +0 -0
- {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_use_full_widget_name.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__ = "4.
|
|
23
|
+
__version__ = "4.4.0"
|
|
24
24
|
|
|
25
25
|
from .assets.blank import BlankPage
|
|
26
26
|
from .middleware import Widgets
|
|
@@ -28,6 +28,45 @@ from .patterns import (DROPDOWN_CHOICE_PATTERNS, WIDGET_DESCRIPTION_PATTERNS,
|
|
|
28
28
|
from .utils import extract_widget_property, find_pattern_match, stream_to_io
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
@lru_cache
|
|
32
|
+
def get_metadata(pdf: bytes) -> dict:
|
|
33
|
+
"""
|
|
34
|
+
Retrieves the metadata of a PDF.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
pdf (bytes): The PDF stream to extract metadata from.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
dict: A dictionary containing the PDF's metadata.
|
|
41
|
+
"""
|
|
42
|
+
if not pdf:
|
|
43
|
+
return {}
|
|
44
|
+
|
|
45
|
+
reader = PdfReader(stream_to_io(pdf))
|
|
46
|
+
return reader.metadata or {}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def set_metadata(pdf: bytes, metadata: dict) -> bytes:
|
|
50
|
+
"""
|
|
51
|
+
Sets the metadata of a PDF.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
pdf (bytes): The PDF stream to set metadata for.
|
|
55
|
+
metadata (dict): A dictionary containing the metadata to be set.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
bytes: The updated PDF stream with the new metadata.
|
|
59
|
+
"""
|
|
60
|
+
reader = PdfReader(stream_to_io(pdf))
|
|
61
|
+
writer = PdfWriter(clone_from=reader)
|
|
62
|
+
writer.add_metadata(metadata)
|
|
63
|
+
|
|
64
|
+
with BytesIO() as f:
|
|
65
|
+
writer.write(f)
|
|
66
|
+
f.seek(0)
|
|
67
|
+
return f.read()
|
|
68
|
+
|
|
69
|
+
|
|
31
70
|
def build_widgets(
|
|
32
71
|
pdf_stream: bytes,
|
|
33
72
|
use_full_widget_name: bool,
|
|
@@ -38,7 +38,8 @@ from .middleware.dropdown import Dropdown
|
|
|
38
38
|
from .middleware.signature import Signature
|
|
39
39
|
from .middleware.text import Text
|
|
40
40
|
from .raw import RawText, RawTypes
|
|
41
|
-
from .template import build_widgets,
|
|
41
|
+
from .template import (build_widgets, get_metadata, set_metadata,
|
|
42
|
+
update_widget_keys)
|
|
42
43
|
from .types import PdfArray
|
|
43
44
|
from .utils import (generate_unique_suffix, get_page_streams, merge_pdfs,
|
|
44
45
|
remove_all_widgets)
|
|
@@ -68,6 +69,7 @@ class PdfWrapper:
|
|
|
68
69
|
- `use_full_widget_name` (bool): Whether to use the full widget name when filling the form.
|
|
69
70
|
- `need_appearances` (bool): Whether to set the `NeedAppearances` flag in the PDF's AcroForm dictionary.
|
|
70
71
|
- `generate_appearance_streams` (bool): Whether to explicitly generate appearance streams for all form fields using pikepdf.
|
|
72
|
+
- `preserve_metadata` (bool): Whether to preserve the original metadata of the PDF.
|
|
71
73
|
- `title` (str): The title of the PDF document.
|
|
72
74
|
|
|
73
75
|
"""
|
|
@@ -76,6 +78,7 @@ class PdfWrapper:
|
|
|
76
78
|
("use_full_widget_name", False),
|
|
77
79
|
("need_appearances", False),
|
|
78
80
|
("generate_appearance_streams", False),
|
|
81
|
+
("preserve_metadata", False),
|
|
79
82
|
("title", None),
|
|
80
83
|
]
|
|
81
84
|
|
|
@@ -105,6 +108,9 @@ class PdfWrapper:
|
|
|
105
108
|
self._stream = fp_or_f_obj_or_stream_to_stream(template)
|
|
106
109
|
self.widgets = {}
|
|
107
110
|
self.title: str = None
|
|
111
|
+
self._metadata = (
|
|
112
|
+
get_metadata(self._read()) if kwargs.get("preserve_metadata") else {}
|
|
113
|
+
)
|
|
108
114
|
self._on_open_javascript = None
|
|
109
115
|
self._available_fonts = {} # for setting /F1
|
|
110
116
|
self._font_register_events = [] # for reregister
|
|
@@ -356,7 +362,8 @@ class PdfWrapper:
|
|
|
356
362
|
2. If `need_appearances` is enabled, it handles appearance streams and the
|
|
357
363
|
`/NeedAppearances` flag, which may include removing XFA and explicitly
|
|
358
364
|
generating appearance streams.
|
|
359
|
-
3. If
|
|
365
|
+
3. If `preserve_metadata` is enabled, it preserves the original metadata of the PDF.
|
|
366
|
+
4. If a title or on-open JavaScript is set, it updates the PDF properties
|
|
360
367
|
accordingly.
|
|
361
368
|
|
|
362
369
|
Returns:
|
|
@@ -369,6 +376,10 @@ class PdfWrapper:
|
|
|
369
376
|
result, getattr(self, "generate_appearance_streams")
|
|
370
377
|
) # cached
|
|
371
378
|
|
|
379
|
+
if getattr(self, "preserve_metadata"):
|
|
380
|
+
# TODO: refactor with preserve_pdf_properties
|
|
381
|
+
result = set_metadata(result, self._metadata)
|
|
382
|
+
|
|
372
383
|
if any([self.title, self.on_open_javascript]):
|
|
373
384
|
result = preserve_pdf_properties(
|
|
374
385
|
result,
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from PyPDFForm import Fields, PdfWrapper
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.posix_only
|
|
11
|
+
def test_bulk_create_fields_stress_max(pdf_samples, request):
|
|
12
|
+
expected_path = os.path.join(
|
|
13
|
+
pdf_samples, "bulk_create_fields", "test_bulk_create_fields_stress_max.pdf"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
fields = []
|
|
17
|
+
x = 0
|
|
18
|
+
y = 0
|
|
19
|
+
margin = 25
|
|
20
|
+
while x <= 575:
|
|
21
|
+
while y <= 625:
|
|
22
|
+
fields.append(
|
|
23
|
+
Fields.TextField(f"text_{x}_{y}", 1, x, y, width=margin, height=margin)
|
|
24
|
+
)
|
|
25
|
+
fields.append(Fields.CheckBoxField(f"check_{x}_{y}", 2, x, y, size=margin))
|
|
26
|
+
fields.append(
|
|
27
|
+
Fields.DropdownField(
|
|
28
|
+
f"dropdown_{x}_{y}",
|
|
29
|
+
3,
|
|
30
|
+
x,
|
|
31
|
+
y,
|
|
32
|
+
width=margin,
|
|
33
|
+
height=margin,
|
|
34
|
+
options=["foo", "bar"],
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
fields.append(
|
|
38
|
+
Fields.RadioGroup(
|
|
39
|
+
f"radio_{x}_{y}", 4, [x, x + 12.5], [y, y + 12.5], size=12.5
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
fields.append(
|
|
43
|
+
Fields.ImageField(
|
|
44
|
+
f"image_{x}_{y}", 5, x, y, width=margin, height=margin
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
fields.append(
|
|
48
|
+
Fields.SignatureField(
|
|
49
|
+
f"signature_{x}_{y}", 6, x, y, width=margin, height=margin
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
y += margin
|
|
53
|
+
y = 0
|
|
54
|
+
x += margin
|
|
55
|
+
|
|
56
|
+
with open(expected_path, "rb+") as f:
|
|
57
|
+
obj = PdfWrapper()
|
|
58
|
+
for _ in range(6):
|
|
59
|
+
obj += PdfWrapper(os.path.join(pdf_samples, "dummy.pdf"))
|
|
60
|
+
obj.bulk_create_fields(fields)
|
|
61
|
+
|
|
62
|
+
request.config.results["expected_path"] = expected_path
|
|
63
|
+
request.config.results["stream"] = obj.read()
|
|
64
|
+
|
|
65
|
+
expected = f.read()
|
|
66
|
+
|
|
67
|
+
assert len(obj.read()) == len(expected)
|
|
68
|
+
assert obj.read() == expected
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.mark.posix_only
|
|
72
|
+
def test_bulk_create_fields_stress_max_mixed(pdf_samples, request):
|
|
73
|
+
expected_path = os.path.join(
|
|
74
|
+
pdf_samples,
|
|
75
|
+
"bulk_create_fields",
|
|
76
|
+
"test_bulk_create_fields_stress_max_mixed.pdf",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
fields = []
|
|
80
|
+
x = 0
|
|
81
|
+
y = 0
|
|
82
|
+
margin = 25
|
|
83
|
+
while x <= 450:
|
|
84
|
+
while y <= 625:
|
|
85
|
+
fields.append(
|
|
86
|
+
Fields.TextField(f"text_{x}_{y}", 1, x, y, width=margin, height=margin)
|
|
87
|
+
)
|
|
88
|
+
fields.append(
|
|
89
|
+
Fields.CheckBoxField(f"check_{x}_{y}", 1, x + margin, y, size=margin)
|
|
90
|
+
)
|
|
91
|
+
fields.append(
|
|
92
|
+
Fields.DropdownField(
|
|
93
|
+
f"dropdown_{x}_{y}",
|
|
94
|
+
1,
|
|
95
|
+
x,
|
|
96
|
+
y + margin,
|
|
97
|
+
width=margin,
|
|
98
|
+
height=margin,
|
|
99
|
+
options=["foo", "bar"],
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
fields.append(
|
|
103
|
+
Fields.RadioGroup(
|
|
104
|
+
f"radio_{x}_{y}",
|
|
105
|
+
1,
|
|
106
|
+
[x + margin, x + margin + 12.5],
|
|
107
|
+
[y + margin, y + margin + 12.5],
|
|
108
|
+
size=12.5,
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
fields.append(
|
|
112
|
+
Fields.ImageField(
|
|
113
|
+
f"image_{x}_{y}",
|
|
114
|
+
1,
|
|
115
|
+
x + margin + margin,
|
|
116
|
+
y,
|
|
117
|
+
width=margin,
|
|
118
|
+
height=margin,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
fields.append(
|
|
122
|
+
Fields.SignatureField(
|
|
123
|
+
f"signature_{x}_{y}",
|
|
124
|
+
1,
|
|
125
|
+
x + margin + margin,
|
|
126
|
+
y + margin,
|
|
127
|
+
width=margin,
|
|
128
|
+
height=margin,
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
y += 50
|
|
132
|
+
y = 0
|
|
133
|
+
x += 75
|
|
134
|
+
|
|
135
|
+
with open(expected_path, "rb+") as f:
|
|
136
|
+
obj = PdfWrapper(os.path.join(pdf_samples, "dummy.pdf")).bulk_create_fields(
|
|
137
|
+
fields
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
request.config.results["expected_path"] = expected_path
|
|
141
|
+
request.config.results["stream"] = obj.read()
|
|
142
|
+
|
|
143
|
+
expected = f.read()
|
|
144
|
+
|
|
145
|
+
assert len(obj.read()) == len(expected)
|
|
146
|
+
assert obj.read() == expected
|