PyPDFForm 4.5.0__tar.gz → 4.5.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.
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PKG-INFO +1 -1
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/__init__.py +1 -1
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/adapter.py +8 -11
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/annotations/__init__.py +1 -2
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/constants.py +1 -3
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/filler.py +88 -35
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/font.py +4 -2
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/hooks.py +15 -15
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/image.py +3 -3
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/middleware/base.py +15 -15
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/middleware/checkbox.py +4 -4
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/middleware/dropdown.py +9 -9
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/middleware/signature.py +2 -3
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/middleware/text.py +9 -7
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/patterns.py +9 -9
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/raw/__init__.py +1 -2
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/raw/image.py +2 -2
- pypdfform-4.5.2/PyPDFForm/template.py +480 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/types.py +4 -4
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/utils.py +38 -27
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/watermark.py +192 -43
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/widgets/__init__.py +3 -4
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/widgets/base.py +5 -5
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/widgets/dropdown.py +3 -3
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/widgets/signature.py +5 -3
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/wrapper.py +28 -28
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm.egg-info/PKG-INFO +1 -1
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_create_widget.py +0 -32
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_font_widths.py +4 -1
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_functional.py +1 -1
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_generate_appearance_streams.py +4 -23
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_need_appearances.py +4 -23
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_paragraph.py +8 -48
- pypdfform-4.5.0/PyPDFForm/template.py +0 -342
- {pypdfform-4.5.0 → pypdfform-4.5.2}/LICENSE +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/annotations/base.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/annotations/text.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/ap.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/assets/__init__.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/assets/bedrock.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/assets/blank.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/coordinate.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/deprecation.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/middleware/__init__.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/middleware/image.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/middleware/radio.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/raw/circle.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/raw/ellipse.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/raw/line.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/raw/rect.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/raw/text.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/widgets/checkbox.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/widgets/image.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/widgets/radio.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm/widgets/text.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm.egg-info/SOURCES.txt +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm.egg-info/requires.txt +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/README.md +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/pyproject.toml +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/setup.cfg +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_bulk_create_fields.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_draw_elements.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_dropdown.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_extract_middleware_attributes.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_js.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_signature.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.2}/tests/test_use_full_widget_name.py +0 -0
- {pypdfform-4.5.0 → pypdfform-4.5.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__ = "4.5.
|
|
23
|
+
__version__ = "4.5.2"
|
|
24
24
|
|
|
25
25
|
from .annotations import Annotations
|
|
26
26
|
from .assets.blank import BlankPage
|
|
@@ -11,7 +11,7 @@ stream before further processing.
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
from os.path import isfile
|
|
14
|
-
from typing import Any, BinaryIO
|
|
14
|
+
from typing import Any, BinaryIO
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def readable(obj: Any) -> bool:
|
|
@@ -32,7 +32,7 @@ def readable(obj: Any) -> bool:
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def fp_or_f_obj_or_stream_to_stream(
|
|
35
|
-
fp_or_f_obj_or_stream:
|
|
35
|
+
fp_or_f_obj_or_stream: bytes | str | BinaryIO,
|
|
36
36
|
) -> bytes:
|
|
37
37
|
"""
|
|
38
38
|
Adapt a file path, file object, or stream to a byte stream.
|
|
@@ -44,7 +44,7 @@ def fp_or_f_obj_or_stream_to_stream(
|
|
|
44
44
|
- file-like objects with a read() method (BinaryIO)
|
|
45
45
|
|
|
46
46
|
Args:
|
|
47
|
-
fp_or_f_obj_or_stream (
|
|
47
|
+
fp_or_f_obj_or_stream (bytes | str | BinaryIO): The input to adapt.
|
|
48
48
|
It can be a byte stream, a file path (string), or a file object.
|
|
49
49
|
|
|
50
50
|
Returns:
|
|
@@ -59,17 +59,14 @@ def fp_or_f_obj_or_stream_to_stream(
|
|
|
59
59
|
elif readable(fp_or_f_obj_or_stream):
|
|
60
60
|
result = fp_or_f_obj_or_stream.read()
|
|
61
61
|
|
|
62
|
-
elif isinstance(fp_or_f_obj_or_stream, str):
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
else:
|
|
66
|
-
with open(fp_or_f_obj_or_stream, "rb") as _file:
|
|
67
|
-
result = _file.read()
|
|
62
|
+
elif isinstance(fp_or_f_obj_or_stream, str) and isfile(fp_or_f_obj_or_stream):
|
|
63
|
+
with open(fp_or_f_obj_or_stream, "rb") as _file:
|
|
64
|
+
result = _file.read()
|
|
68
65
|
return result
|
|
69
66
|
|
|
70
67
|
|
|
71
68
|
def fp_or_f_obj_or_f_content_to_content(
|
|
72
|
-
fp_or_f_obj_or_f_content:
|
|
69
|
+
fp_or_f_obj_or_f_content: str | BinaryIO,
|
|
73
70
|
) -> str:
|
|
74
71
|
"""
|
|
75
72
|
Adapt a file path, file object, or file content to file content.
|
|
@@ -81,7 +78,7 @@ def fp_or_f_obj_or_f_content_to_content(
|
|
|
81
78
|
- file-like objects with a read() method (BinaryIO)
|
|
82
79
|
|
|
83
80
|
Args:
|
|
84
|
-
fp_or_f_obj_or_f_content (
|
|
81
|
+
fp_or_f_obj_or_f_content (str | BinaryIO): The input to adapt.
|
|
85
82
|
It can be file content, a file path (string), or a file object.
|
|
86
83
|
|
|
87
84
|
Returns:
|
|
@@ -10,11 +10,10 @@ annotations, facilitating their creation and manipulation within PDF documents.
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from dataclasses import dataclass
|
|
13
|
-
from typing import Union
|
|
14
13
|
|
|
15
14
|
from .text import TextAnnotation
|
|
16
15
|
|
|
17
|
-
AnnotationTypes =
|
|
16
|
+
AnnotationTypes = TextAnnotation
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
@dataclass
|
|
@@ -15,8 +15,6 @@ Using constants improves code readability and maintainability by providing
|
|
|
15
15
|
meaningful names for frequently used values and reducing the risk of typos.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
from typing import Union
|
|
19
|
-
|
|
20
18
|
from .middleware.checkbox import Checkbox
|
|
21
19
|
from .middleware.dropdown import Dropdown
|
|
22
20
|
from .middleware.image import Image
|
|
@@ -37,7 +35,7 @@ VERSION_IDENTIFIERS = [
|
|
|
37
35
|
]
|
|
38
36
|
VERSION_IDENTIFIER_PREFIX = "%PDF-".encode("utf-8")
|
|
39
37
|
|
|
40
|
-
WIDGET_TYPES =
|
|
38
|
+
WIDGET_TYPES = Text | Checkbox | Radio | Dropdown | Signature | Image
|
|
41
39
|
|
|
42
40
|
DEPRECATION_NOTICE = "{} will be deprecated soon. Use {} instead."
|
|
43
41
|
|
|
@@ -9,7 +9,7 @@ supports flattening the filled form to prevent further modifications.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from io import BytesIO
|
|
12
|
-
from typing import Dict,
|
|
12
|
+
from typing import Dict, cast
|
|
13
13
|
|
|
14
14
|
from pypdf import PdfReader, PdfWriter
|
|
15
15
|
from pypdf.generic import DictionaryObject
|
|
@@ -31,7 +31,7 @@ from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def signature_image_handler(
|
|
34
|
-
widget: dict, middleware:
|
|
34
|
+
widget: dict, middleware: Signature | Image, images_to_draw: list
|
|
35
35
|
) -> bool:
|
|
36
36
|
"""Handles signature and image widgets by extracting image data and preparing it for drawing.
|
|
37
37
|
|
|
@@ -43,7 +43,7 @@ def signature_image_handler(
|
|
|
43
43
|
|
|
44
44
|
Args:
|
|
45
45
|
widget (dict): The widget dictionary representing the signature or image field.
|
|
46
|
-
middleware (
|
|
46
|
+
middleware (Signature | Image): The middleware object containing the image data and properties.
|
|
47
47
|
images_to_draw (list): A list to store image data for drawing.
|
|
48
48
|
|
|
49
49
|
Returns:
|
|
@@ -70,6 +70,81 @@ def signature_image_handler(
|
|
|
70
70
|
return any_image_to_draw
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
def update_widget(
|
|
74
|
+
annot: DictionaryObject,
|
|
75
|
+
widget: WIDGET_TYPES,
|
|
76
|
+
key: str,
|
|
77
|
+
radio_button_tracker: Dict[str, int],
|
|
78
|
+
images_to_draw_page: list,
|
|
79
|
+
need_appearances: bool,
|
|
80
|
+
flatten: bool,
|
|
81
|
+
) -> bool:
|
|
82
|
+
"""Updates a single widget's value and handles its properties.
|
|
83
|
+
|
|
84
|
+
This function updates the value of a single PDF form widget based on its type. It also
|
|
85
|
+
handles widget flattening and prepares images or signatures for drawing if applicable.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
annot (DictionaryObject): The annotation object representing the widget in the PDF.
|
|
89
|
+
widget (WIDGET_TYPES): The widget middleware object containing the new value.
|
|
90
|
+
key (str): The unique key identifying the widget.
|
|
91
|
+
radio_button_tracker (Dict[str, int]): A tracker for radio button groups to manage their indices.
|
|
92
|
+
images_to_draw_page (list): A list to store image data for the current page.
|
|
93
|
+
need_appearances (bool): If True, skips updating appearance streams for certain fields.
|
|
94
|
+
flatten (bool): Whether to flatten the widget to prevent further editing.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
bool: True if an image or signature was prepared for drawing, False otherwise.
|
|
98
|
+
"""
|
|
99
|
+
if flatten:
|
|
100
|
+
flatten_field(annot, True)
|
|
101
|
+
if widget.value is None:
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
any_image_to_draw = False
|
|
105
|
+
if isinstance(widget, (Signature, Image)):
|
|
106
|
+
any_image_to_draw = signature_image_handler(annot, widget, images_to_draw_page)
|
|
107
|
+
elif type(widget) is Checkbox:
|
|
108
|
+
update_checkbox_value(annot, widget.value)
|
|
109
|
+
elif isinstance(widget, Radio):
|
|
110
|
+
if key not in radio_button_tracker:
|
|
111
|
+
radio_button_tracker[key] = 0
|
|
112
|
+
radio_button_tracker[key] += 1
|
|
113
|
+
if widget.value == radio_button_tracker[key] - 1:
|
|
114
|
+
update_radio_value(annot)
|
|
115
|
+
elif isinstance(widget, Dropdown):
|
|
116
|
+
update_dropdown_value(annot, widget, need_appearances)
|
|
117
|
+
elif isinstance(widget, Text):
|
|
118
|
+
update_text_value(annot, widget, need_appearances)
|
|
119
|
+
|
|
120
|
+
return any_image_to_draw
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def handle_image_drawing(
|
|
124
|
+
result: bytes,
|
|
125
|
+
images_to_draw: Dict[int, list],
|
|
126
|
+
) -> bytes:
|
|
127
|
+
"""Merges prepared images and signatures with the filled PDF.
|
|
128
|
+
|
|
129
|
+
This function takes the filled PDF and a dictionary of images to draw (from signatures
|
|
130
|
+
or image fields) and merges them into the PDF as watermarks.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
result (bytes): The filled PDF as bytes.
|
|
134
|
+
images_to_draw (Dict[int, list]): A dictionary mapping page numbers to lists of image data.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
bytes: The PDF with images and signatures merged.
|
|
138
|
+
"""
|
|
139
|
+
images = []
|
|
140
|
+
for page, elements in images_to_draw.items():
|
|
141
|
+
images.extend(
|
|
142
|
+
[{"page_number": page, "type": "image", **element} for element in elements]
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return merge_watermarks_with_pdf(result, create_watermarks_and_draw(result, images))
|
|
146
|
+
|
|
147
|
+
|
|
73
148
|
def fill(
|
|
74
149
|
template: bytes,
|
|
75
150
|
widgets: Dict[str, WIDGET_TYPES],
|
|
@@ -101,7 +176,6 @@ def fill(
|
|
|
101
176
|
The image drawn stream is only returned if there are any image or signature widgets
|
|
102
177
|
in the form.
|
|
103
178
|
"""
|
|
104
|
-
# pylint: disable=R0912
|
|
105
179
|
pdf = PdfReader(stream_to_io(template))
|
|
106
180
|
out = PdfWriter()
|
|
107
181
|
out.append(pdf)
|
|
@@ -120,28 +194,15 @@ def fill(
|
|
|
120
194
|
if widget is None:
|
|
121
195
|
continue
|
|
122
196
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)
|
|
133
|
-
elif type(widget) is Checkbox:
|
|
134
|
-
update_checkbox_value(annot, widget.value)
|
|
135
|
-
elif isinstance(widget, Radio):
|
|
136
|
-
if key not in radio_button_tracker:
|
|
137
|
-
radio_button_tracker[key] = 0
|
|
138
|
-
radio_button_tracker[key] += 1
|
|
139
|
-
if widget.value == radio_button_tracker[key] - 1:
|
|
140
|
-
update_radio_value(annot)
|
|
141
|
-
elif isinstance(widget, Dropdown):
|
|
142
|
-
update_dropdown_value(annot, widget, need_appearances)
|
|
143
|
-
elif isinstance(widget, Text):
|
|
144
|
-
update_text_value(annot, widget, need_appearances)
|
|
197
|
+
any_image_to_draw |= update_widget(
|
|
198
|
+
annot,
|
|
199
|
+
widget,
|
|
200
|
+
key,
|
|
201
|
+
radio_button_tracker,
|
|
202
|
+
images_to_draw[page_num + 1],
|
|
203
|
+
need_appearances,
|
|
204
|
+
flatten,
|
|
205
|
+
)
|
|
145
206
|
|
|
146
207
|
with BytesIO() as f:
|
|
147
208
|
out.write(f)
|
|
@@ -151,12 +212,4 @@ def fill(
|
|
|
151
212
|
if not any_image_to_draw:
|
|
152
213
|
return result, None
|
|
153
214
|
|
|
154
|
-
|
|
155
|
-
for page, elements in images_to_draw.items():
|
|
156
|
-
images.extend(
|
|
157
|
-
[{"page_number": page, "type": "image", **element} for element in elements]
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
return result, (
|
|
161
|
-
merge_watermarks_with_pdf(result, create_watermarks_and_draw(result, images))
|
|
162
|
-
)
|
|
215
|
+
return result, handle_image_drawing(result, images_to_draw)
|
|
@@ -27,6 +27,7 @@ from .constants import (DEFAULT_ASSUMED_GLYPH_WIDTH, DR, EM_TO_PDF_FACTOR,
|
|
|
27
27
|
MissingWidth, Resources, Subtype, TrueType, Type,
|
|
28
28
|
Widths, WinAnsiEncoding)
|
|
29
29
|
from .utils import stream_to_io
|
|
30
|
+
from .watermark import get_watermark_with_font
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
@lru_cache
|
|
@@ -144,7 +145,7 @@ def compute_font_glyph_widths(ttf_file: BytesIO, missing_width: float) -> list[f
|
|
|
144
145
|
|
|
145
146
|
|
|
146
147
|
def register_font_acroform(
|
|
147
|
-
pdf: bytes, ttf_stream: bytes, need_appearances: bool
|
|
148
|
+
pdf: bytes, font_name: str, ttf_stream: bytes, need_appearances: bool
|
|
148
149
|
) -> tuple:
|
|
149
150
|
"""
|
|
150
151
|
Registers a TrueType font within the PDF's AcroForm dictionary.
|
|
@@ -156,6 +157,7 @@ def register_font_acroform(
|
|
|
156
157
|
Args:
|
|
157
158
|
pdf (bytes): The PDF file data as bytes. This is the PDF document that
|
|
158
159
|
will be modified to include the new font.
|
|
160
|
+
font_name (str): The name of the font being registered.
|
|
159
161
|
ttf_stream (bytes): The font file data in TTF format as bytes. This is the
|
|
160
162
|
raw data of the TrueType font file.
|
|
161
163
|
need_appearances (bool): If True, attempts to retrieve existing font parameters
|
|
@@ -175,7 +177,7 @@ def register_font_acroform(
|
|
|
175
177
|
font_dict_params = {}
|
|
176
178
|
if need_appearances:
|
|
177
179
|
font_descriptor_params, font_dict_params = get_additional_font_params(
|
|
178
|
-
|
|
180
|
+
get_watermark_with_font(font_name), base_font_name
|
|
179
181
|
)
|
|
180
182
|
|
|
181
183
|
font_file_stream = StreamObject()
|
|
@@ -11,7 +11,7 @@ filling process, allowing for customization of the form's appearance and behavio
|
|
|
11
11
|
|
|
12
12
|
import sys
|
|
13
13
|
from io import BytesIO
|
|
14
|
-
from typing import TextIO,
|
|
14
|
+
from typing import TextIO, cast
|
|
15
15
|
|
|
16
16
|
from pypdf import PdfReader, PdfWriter
|
|
17
17
|
from pypdf.generic import (ArrayObject, DictionaryObject, FloatObject,
|
|
@@ -393,7 +393,7 @@ def update_field_hidden(annot: DictionaryObject, val: bool) -> None:
|
|
|
393
393
|
|
|
394
394
|
|
|
395
395
|
def _update_field_javascript(
|
|
396
|
-
annot: DictionaryObject, trigger_event: str, val:
|
|
396
|
+
annot: DictionaryObject, trigger_event: str, val: str | TextIO
|
|
397
397
|
) -> None:
|
|
398
398
|
"""
|
|
399
399
|
Updates a specific JavaScript action for a form field annotation.
|
|
@@ -405,7 +405,7 @@ def _update_field_javascript(
|
|
|
405
405
|
annot (DictionaryObject): The annotation dictionary for the form field.
|
|
406
406
|
trigger_event (str): The event that triggers the JavaScript action
|
|
407
407
|
(e.g., E for enter, X for exit, D for down, U for up, Fo for focus, Bl for blur).
|
|
408
|
-
val (
|
|
408
|
+
val (str | TextIO): The JavaScript code to execute. Can be a string
|
|
409
409
|
containing the code or a file-like object/path to a file containing the code.
|
|
410
410
|
"""
|
|
411
411
|
if AA not in annot:
|
|
@@ -424,7 +424,7 @@ def _update_field_javascript(
|
|
|
424
424
|
|
|
425
425
|
|
|
426
426
|
def update_field_on_hovered_over_javascript(
|
|
427
|
-
annot: DictionaryObject, val:
|
|
427
|
+
annot: DictionaryObject, val: str | TextIO
|
|
428
428
|
) -> None:
|
|
429
429
|
"""
|
|
430
430
|
Updates the JavaScript action triggered when the mouse enters the field area.
|
|
@@ -434,13 +434,13 @@ def update_field_on_hovered_over_javascript(
|
|
|
434
434
|
|
|
435
435
|
Args:
|
|
436
436
|
annot (DictionaryObject): The annotation dictionary for the form field.
|
|
437
|
-
val (
|
|
437
|
+
val (str | TextIO): The JavaScript code to execute.
|
|
438
438
|
"""
|
|
439
439
|
_update_field_javascript(annot, E, val)
|
|
440
440
|
|
|
441
441
|
|
|
442
442
|
def update_field_on_hovered_off_javascript(
|
|
443
|
-
annot: DictionaryObject, val:
|
|
443
|
+
annot: DictionaryObject, val: str | TextIO
|
|
444
444
|
) -> None:
|
|
445
445
|
"""
|
|
446
446
|
Updates the JavaScript action triggered when the mouse exits the field area.
|
|
@@ -450,13 +450,13 @@ def update_field_on_hovered_off_javascript(
|
|
|
450
450
|
|
|
451
451
|
Args:
|
|
452
452
|
annot (DictionaryObject): The annotation dictionary for the form field.
|
|
453
|
-
val (
|
|
453
|
+
val (str | TextIO): The JavaScript code to execute.
|
|
454
454
|
"""
|
|
455
455
|
_update_field_javascript(annot, X, val)
|
|
456
456
|
|
|
457
457
|
|
|
458
458
|
def update_field_on_mouse_pressed_javascript(
|
|
459
|
-
annot: DictionaryObject, val:
|
|
459
|
+
annot: DictionaryObject, val: str | TextIO
|
|
460
460
|
) -> None:
|
|
461
461
|
"""
|
|
462
462
|
Updates the JavaScript action triggered when the mouse button is pressed down inside the field.
|
|
@@ -466,13 +466,13 @@ def update_field_on_mouse_pressed_javascript(
|
|
|
466
466
|
|
|
467
467
|
Args:
|
|
468
468
|
annot (DictionaryObject): The annotation dictionary for the form field.
|
|
469
|
-
val (
|
|
469
|
+
val (str | TextIO): The JavaScript code to execute.
|
|
470
470
|
"""
|
|
471
471
|
_update_field_javascript(annot, D, val)
|
|
472
472
|
|
|
473
473
|
|
|
474
474
|
def update_field_on_mouse_released_javascript(
|
|
475
|
-
annot: DictionaryObject, val:
|
|
475
|
+
annot: DictionaryObject, val: str | TextIO
|
|
476
476
|
) -> None:
|
|
477
477
|
"""
|
|
478
478
|
Updates the JavaScript action triggered when the mouse button is released inside the field.
|
|
@@ -482,13 +482,13 @@ def update_field_on_mouse_released_javascript(
|
|
|
482
482
|
|
|
483
483
|
Args:
|
|
484
484
|
annot (DictionaryObject): The annotation dictionary for the form field.
|
|
485
|
-
val (
|
|
485
|
+
val (str | TextIO): The JavaScript code to execute.
|
|
486
486
|
"""
|
|
487
487
|
_update_field_javascript(annot, U, val)
|
|
488
488
|
|
|
489
489
|
|
|
490
490
|
def update_field_on_focused_javascript(
|
|
491
|
-
annot: DictionaryObject, val:
|
|
491
|
+
annot: DictionaryObject, val: str | TextIO
|
|
492
492
|
) -> None:
|
|
493
493
|
"""
|
|
494
494
|
Updates the JavaScript action triggered when the field receives input focus.
|
|
@@ -498,13 +498,13 @@ def update_field_on_focused_javascript(
|
|
|
498
498
|
|
|
499
499
|
Args:
|
|
500
500
|
annot (DictionaryObject): The annotation dictionary for the form field.
|
|
501
|
-
val (
|
|
501
|
+
val (str | TextIO): The JavaScript code to execute.
|
|
502
502
|
"""
|
|
503
503
|
_update_field_javascript(annot, Fo, val)
|
|
504
504
|
|
|
505
505
|
|
|
506
506
|
def update_field_on_blurred_javascript(
|
|
507
|
-
annot: DictionaryObject, val:
|
|
507
|
+
annot: DictionaryObject, val: str | TextIO
|
|
508
508
|
) -> None:
|
|
509
509
|
"""
|
|
510
510
|
Updates the JavaScript action triggered when the field loses input focus.
|
|
@@ -514,6 +514,6 @@ def update_field_on_blurred_javascript(
|
|
|
514
514
|
|
|
515
515
|
Args:
|
|
516
516
|
annot (DictionaryObject): The annotation dictionary for the form field.
|
|
517
|
-
val (
|
|
517
|
+
val (str | TextIO): The JavaScript code to execute.
|
|
518
518
|
"""
|
|
519
519
|
_update_field_javascript(annot, Bl, val)
|
|
@@ -8,14 +8,14 @@ account whether to preserve the aspect ratio.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from io import BytesIO
|
|
11
|
-
from typing import Tuple
|
|
11
|
+
from typing import Tuple
|
|
12
12
|
|
|
13
13
|
from PIL import Image
|
|
14
14
|
|
|
15
15
|
from .constants import Rect
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def rotate_image(image_stream: bytes, rotation:
|
|
18
|
+
def rotate_image(image_stream: bytes, rotation: float | int) -> bytes:
|
|
19
19
|
"""
|
|
20
20
|
Rotates an image by a specified angle in degrees.
|
|
21
21
|
|
|
@@ -26,7 +26,7 @@ def rotate_image(image_stream: bytes, rotation: Union[float, int]) -> bytes:
|
|
|
26
26
|
|
|
27
27
|
Args:
|
|
28
28
|
image_stream (bytes): The image data as bytes.
|
|
29
|
-
rotation (
|
|
29
|
+
rotation (float | int): The rotation angle in degrees. Positive values
|
|
30
30
|
rotate the image counterclockwise, while negative values rotate it clockwise.
|
|
31
31
|
|
|
32
32
|
Returns:
|
|
@@ -8,7 +8,7 @@ common attributes and methods for all form widgets, such as name, value,
|
|
|
8
8
|
and schema definition.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
from typing import Any, List,
|
|
11
|
+
from typing import Any, List, Optional, TextIO
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class Widget:
|
|
@@ -49,25 +49,25 @@ class Widget:
|
|
|
49
49
|
super().__init__()
|
|
50
50
|
self._name = name
|
|
51
51
|
self._value = value
|
|
52
|
-
self.tooltip: str = None
|
|
53
|
-
self.readonly: bool = None
|
|
54
|
-
self.required: bool = None
|
|
55
|
-
self.hidden: bool = None
|
|
52
|
+
self.tooltip: Optional[str] = None
|
|
53
|
+
self.readonly: Optional[bool] = None
|
|
54
|
+
self.required: Optional[bool] = None
|
|
55
|
+
self.hidden: Optional[bool] = None
|
|
56
56
|
self.hooks_to_trigger: list = []
|
|
57
57
|
|
|
58
58
|
# coordinate & dimension
|
|
59
|
-
self.x:
|
|
60
|
-
self.y:
|
|
61
|
-
self.width:
|
|
62
|
-
self.height:
|
|
59
|
+
self.x: Optional[float | List[float]] = None
|
|
60
|
+
self.y: Optional[float | List[float]] = None
|
|
61
|
+
self.width: Optional[float | List[float]] = None
|
|
62
|
+
self.height: Optional[float | List[float]] = None
|
|
63
63
|
|
|
64
64
|
# javascript
|
|
65
|
-
self.on_hovered_over_javascript:
|
|
66
|
-
self.on_hovered_off_javascript:
|
|
67
|
-
self.on_mouse_pressed_javascript:
|
|
68
|
-
self.on_mouse_released_javascript:
|
|
69
|
-
self.on_focused_javascript:
|
|
70
|
-
self.on_blurred_javascript:
|
|
65
|
+
self.on_hovered_over_javascript: Optional[str | TextIO] = None
|
|
66
|
+
self.on_hovered_off_javascript: Optional[str | TextIO] = None
|
|
67
|
+
self.on_mouse_pressed_javascript: Optional[str | TextIO] = None
|
|
68
|
+
self.on_mouse_released_javascript: Optional[str | TextIO] = None
|
|
69
|
+
self.on_focused_javascript: Optional[str | TextIO] = None
|
|
70
|
+
self.on_blurred_javascript: Optional[str | TextIO] = None
|
|
71
71
|
|
|
72
72
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
73
73
|
"""
|
|
@@ -6,7 +6,7 @@ This module defines the Checkbox class, which is a subclass of the
|
|
|
6
6
|
Widget class. It represents a checkbox form field in a PDF document.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from typing import Any,
|
|
9
|
+
from typing import Any, Optional
|
|
10
10
|
|
|
11
11
|
from .base import Widget
|
|
12
12
|
|
|
@@ -42,7 +42,7 @@ class Checkbox(Widget):
|
|
|
42
42
|
)
|
|
43
43
|
super().__init__(name, value)
|
|
44
44
|
|
|
45
|
-
self.size: float = None
|
|
45
|
+
self.size: Optional[float] = None
|
|
46
46
|
|
|
47
47
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
48
48
|
"""
|
|
@@ -75,7 +75,7 @@ class Checkbox(Widget):
|
|
|
75
75
|
return {"type": "boolean", **super().schema_definition}
|
|
76
76
|
|
|
77
77
|
@property
|
|
78
|
-
def sample_value(self) ->
|
|
78
|
+
def sample_value(self) -> bool | int:
|
|
79
79
|
"""
|
|
80
80
|
Returns a sample value for the checkbox.
|
|
81
81
|
|
|
@@ -83,6 +83,6 @@ class Checkbox(Widget):
|
|
|
83
83
|
checkbox field.
|
|
84
84
|
|
|
85
85
|
Returns:
|
|
86
|
-
|
|
86
|
+
bool | int: A sample value for the checkbox.
|
|
87
87
|
"""
|
|
88
88
|
return True
|
|
@@ -6,7 +6,7 @@ This module defines the Dropdown class, which is a subclass of the
|
|
|
6
6
|
Widget class. It represents a dropdown form field in a PDF document.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Optional
|
|
10
10
|
|
|
11
11
|
from .base import Widget
|
|
12
12
|
|
|
@@ -54,10 +54,10 @@ class Dropdown(Widget):
|
|
|
54
54
|
)
|
|
55
55
|
super().__init__(name, value)
|
|
56
56
|
|
|
57
|
-
self.font: str = None
|
|
58
|
-
self.font_size: float = None
|
|
59
|
-
self.font_color: tuple = None
|
|
60
|
-
self.choices:
|
|
57
|
+
self.font: Optional[str] = None
|
|
58
|
+
self.font_size: Optional[float] = None
|
|
59
|
+
self.font_color: Optional[tuple] = None
|
|
60
|
+
self.choices: Optional[tuple | list] = None
|
|
61
61
|
|
|
62
62
|
@property
|
|
63
63
|
def value(self) -> int:
|
|
@@ -70,7 +70,7 @@ class Dropdown(Widget):
|
|
|
70
70
|
return super().value
|
|
71
71
|
|
|
72
72
|
@value.setter
|
|
73
|
-
def value(self, value:
|
|
73
|
+
def value(self, value: str | int) -> None:
|
|
74
74
|
"""
|
|
75
75
|
Sets the value of the dropdown.
|
|
76
76
|
|
|
@@ -79,7 +79,7 @@ class Dropdown(Widget):
|
|
|
79
79
|
added to the choices, and its new index is used.
|
|
80
80
|
|
|
81
81
|
Args:
|
|
82
|
-
value (
|
|
82
|
+
value (str | int): The value to set. Can be a string
|
|
83
83
|
(option text) or an integer (index).
|
|
84
84
|
"""
|
|
85
85
|
if isinstance(value, str):
|
|
@@ -91,7 +91,7 @@ class Dropdown(Widget):
|
|
|
91
91
|
|
|
92
92
|
self._value = value
|
|
93
93
|
|
|
94
|
-
def _get_option_index(self, value: str) ->
|
|
94
|
+
def _get_option_index(self, value: str) -> int | None:
|
|
95
95
|
"""
|
|
96
96
|
Gets the index of a given option value in the dropdown's choices.
|
|
97
97
|
|
|
@@ -99,7 +99,7 @@ class Dropdown(Widget):
|
|
|
99
99
|
value (str): The option value to search for.
|
|
100
100
|
|
|
101
101
|
Returns:
|
|
102
|
-
|
|
102
|
+
int | None: The index of the option if found, otherwise None.
|
|
103
103
|
"""
|
|
104
104
|
for i, each in enumerate(self.choices):
|
|
105
105
|
if value == each:
|
|
@@ -8,7 +8,6 @@ allowing users to add their signature as an image.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from os.path import expanduser
|
|
11
|
-
from typing import Union
|
|
12
11
|
|
|
13
12
|
from ..adapter import fp_or_f_obj_or_stream_to_stream
|
|
14
13
|
from .base import Widget
|
|
@@ -55,7 +54,7 @@ class Signature(Widget):
|
|
|
55
54
|
return expanduser("~/Downloads/sample_image.jpg")
|
|
56
55
|
|
|
57
56
|
@property
|
|
58
|
-
def stream(self) ->
|
|
57
|
+
def stream(self) -> bytes | None:
|
|
59
58
|
"""
|
|
60
59
|
Returns the stream of the signature image.
|
|
61
60
|
|
|
@@ -64,7 +63,7 @@ class Signature(Widget):
|
|
|
64
63
|
as a stream of bytes.
|
|
65
64
|
|
|
66
65
|
Returns:
|
|
67
|
-
|
|
66
|
+
bytes | None: The stream of the signature image.
|
|
68
67
|
"""
|
|
69
68
|
return (
|
|
70
69
|
fp_or_f_obj_or_stream_to_stream(self.value)
|
|
@@ -7,6 +7,8 @@ Widget class. It represents a text field form field in a PDF document,
|
|
|
7
7
|
allowing users to enter text.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
10
12
|
from .base import Widget
|
|
11
13
|
|
|
12
14
|
|
|
@@ -56,13 +58,13 @@ class Text(Widget):
|
|
|
56
58
|
)
|
|
57
59
|
super().__init__(name, value)
|
|
58
60
|
|
|
59
|
-
self.font: str = None
|
|
60
|
-
self.font_size: float = None
|
|
61
|
-
self.font_color: tuple = None
|
|
62
|
-
self.comb: bool = None
|
|
63
|
-
self.alignment: int = None
|
|
64
|
-
self.multiline: bool = None
|
|
65
|
-
self.max_length: int = None
|
|
61
|
+
self.font: Optional[str] = None
|
|
62
|
+
self.font_size: Optional[float] = None
|
|
63
|
+
self.font_color: Optional[tuple] = None
|
|
64
|
+
self.comb: Optional[bool] = None
|
|
65
|
+
self.alignment: Optional[int] = None
|
|
66
|
+
self.multiline: Optional[bool] = None
|
|
67
|
+
self.max_length: Optional[int] = None
|
|
66
68
|
|
|
67
69
|
@property
|
|
68
70
|
def value(self) -> str:
|