PyPDFForm 2.0.1__py3-none-any.whl → 2.2.0__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 +6 -2
- PyPDFForm/adapter.py +37 -3
- PyPDFForm/constants.py +12 -1
- PyPDFForm/coordinate.py +218 -59
- PyPDFForm/filler.py +104 -9
- PyPDFForm/font.py +80 -16
- PyPDFForm/image.py +32 -3
- PyPDFForm/middleware/base.py +57 -8
- PyPDFForm/middleware/checkbox.py +49 -7
- PyPDFForm/middleware/dropdown.py +41 -5
- PyPDFForm/middleware/image.py +26 -2
- PyPDFForm/middleware/radio.py +41 -5
- PyPDFForm/middleware/signature.py +49 -6
- PyPDFForm/middleware/text.py +55 -7
- PyPDFForm/patterns.py +108 -10
- PyPDFForm/template.py +181 -29
- PyPDFForm/utils.py +108 -12
- PyPDFForm/watermark.py +163 -11
- PyPDFForm/widgets/base.py +65 -9
- PyPDFForm/widgets/bedrock.py +3 -0
- PyPDFForm/widgets/checkbox.py +22 -2
- PyPDFForm/widgets/dropdown.py +31 -3
- PyPDFForm/widgets/image.py +24 -0
- PyPDFForm/widgets/radio.py +78 -0
- PyPDFForm/widgets/signature.py +133 -0
- PyPDFForm/widgets/text.py +19 -2
- PyPDFForm/wrapper.py +351 -27
- {pypdfform-2.0.1.dist-info → pypdfform-2.2.0.dist-info}/METADATA +2 -2
- pypdfform-2.2.0.dist-info/RECORD +34 -0
- pypdfform-2.0.1.dist-info/RECORD +0 -30
- {pypdfform-2.0.1.dist-info → pypdfform-2.2.0.dist-info}/WHEEL +0 -0
- {pypdfform-2.0.1.dist-info → pypdfform-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {pypdfform-2.0.1.dist-info → pypdfform-2.2.0.dist-info}/top_level.txt +0 -0
PyPDFForm/watermark.py
CHANGED
|
@@ -1,18 +1,42 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
2
|
+
"""Provides watermark generation, annotation copying, and merging functionality for PDF forms.
|
|
3
|
+
|
|
4
|
+
This module handles:
|
|
5
|
+
- Drawing text, images, shapes, and lines onto PDF watermarks
|
|
6
|
+
- Managing watermark styles and properties
|
|
7
|
+
- Merging watermarks with PDF documents
|
|
8
|
+
- Copying annotation widgets (form fields) from watermark PDFs onto base PDFs
|
|
9
|
+
- Supporting various drawing operations needed for form filling
|
|
10
|
+
"""
|
|
3
11
|
|
|
4
12
|
from io import BytesIO
|
|
5
13
|
from typing import List
|
|
6
14
|
|
|
7
15
|
from pypdf import PdfReader, PdfWriter
|
|
16
|
+
from pypdf.generic import ArrayObject, NameObject
|
|
8
17
|
from reportlab.lib.utils import ImageReader
|
|
9
18
|
from reportlab.pdfgen.canvas import Canvas
|
|
10
19
|
|
|
11
|
-
from .
|
|
20
|
+
from .constants import Annots
|
|
21
|
+
from .patterns import WIDGET_KEY_PATTERNS
|
|
22
|
+
from .utils import extract_widget_property, stream_to_io
|
|
12
23
|
|
|
13
24
|
|
|
14
25
|
def draw_text(*args) -> None:
|
|
15
|
-
"""Draws a
|
|
26
|
+
"""Draws text onto a watermark canvas with proper formatting.
|
|
27
|
+
|
|
28
|
+
Handles:
|
|
29
|
+
- Comb fields (fixed character spacing)
|
|
30
|
+
- Multiline text with wrapping
|
|
31
|
+
- Font and color styling
|
|
32
|
+
- Text alignment
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
args[0]: Canvas object to draw on
|
|
36
|
+
args[1]: Text widget with content and properties
|
|
37
|
+
args[2]: X coordinate for drawing
|
|
38
|
+
args[3]: Y coordinate for drawing
|
|
39
|
+
"""
|
|
16
40
|
|
|
17
41
|
canvas = args[0]
|
|
18
42
|
widget = args[1]
|
|
@@ -73,7 +97,19 @@ def draw_text(*args) -> None:
|
|
|
73
97
|
|
|
74
98
|
|
|
75
99
|
def draw_rect(*args) -> None:
|
|
76
|
-
"""Draws a rectangle
|
|
100
|
+
"""Draws a rectangle onto a watermark canvas.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
args[0]: Canvas object to draw on
|
|
104
|
+
args[1]: X coordinate of bottom-left corner
|
|
105
|
+
args[2]: Y coordinate of bottom-left corner
|
|
106
|
+
args[3]: Width of rectangle
|
|
107
|
+
args[4]: Height of rectangle
|
|
108
|
+
args[5]: Border color
|
|
109
|
+
args[6]: Background color
|
|
110
|
+
args[7]: Border width
|
|
111
|
+
args[8]: Dash pattern for border
|
|
112
|
+
"""
|
|
77
113
|
|
|
78
114
|
canvas = args[0]
|
|
79
115
|
x = args[1]
|
|
@@ -88,7 +124,19 @@ def draw_rect(*args) -> None:
|
|
|
88
124
|
|
|
89
125
|
|
|
90
126
|
def draw_ellipse(*args) -> None:
|
|
91
|
-
"""Draws an ellipse
|
|
127
|
+
"""Draws an ellipse onto a watermark canvas.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
args[0]: Canvas object to draw on
|
|
131
|
+
args[1]: X coordinate of first bounding point
|
|
132
|
+
args[2]: Y coordinate of first bounding point
|
|
133
|
+
args[3]: X coordinate of second bounding point
|
|
134
|
+
args[4]: Y coordinate of second bounding point
|
|
135
|
+
args[5]: Border color
|
|
136
|
+
args[6]: Background color
|
|
137
|
+
args[7]: Border width
|
|
138
|
+
args[8]: Dash pattern for border
|
|
139
|
+
"""
|
|
92
140
|
|
|
93
141
|
canvas = args[0]
|
|
94
142
|
x1 = args[1]
|
|
@@ -103,7 +151,19 @@ def draw_ellipse(*args) -> None:
|
|
|
103
151
|
|
|
104
152
|
|
|
105
153
|
def draw_line(*args) -> None:
|
|
106
|
-
"""Draws a line
|
|
154
|
+
"""Draws a line onto a watermark canvas.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
args[0]: Canvas object to draw on
|
|
158
|
+
args[1]: X coordinate of start point
|
|
159
|
+
args[2]: Y coordinate of start point
|
|
160
|
+
args[3]: X coordinate of end point
|
|
161
|
+
args[4]: Y coordinate of end point
|
|
162
|
+
args[5]: Line color
|
|
163
|
+
args[6]: Unused (kept for consistency)
|
|
164
|
+
args[7]: Line width
|
|
165
|
+
args[8]: Dash pattern for line
|
|
166
|
+
"""
|
|
107
167
|
|
|
108
168
|
canvas = args[0]
|
|
109
169
|
src_x = args[1]
|
|
@@ -118,7 +178,18 @@ def draw_line(*args) -> None:
|
|
|
118
178
|
|
|
119
179
|
|
|
120
180
|
def set_border_and_background_styles(*args) -> tuple:
|
|
121
|
-
"""
|
|
181
|
+
"""Configures stroke and fill styles for drawing operations.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
args[0]: Canvas object to configure
|
|
185
|
+
args[5]: Border color
|
|
186
|
+
args[6]: Background color
|
|
187
|
+
args[7]: Border width
|
|
188
|
+
args[8]: Dash pattern for border
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
tuple: (stroke_flag, fill_flag) indicating which styles were set
|
|
192
|
+
"""
|
|
122
193
|
|
|
123
194
|
canvas = args[0]
|
|
124
195
|
border_color = args[5]
|
|
@@ -143,7 +214,16 @@ def set_border_and_background_styles(*args) -> tuple:
|
|
|
143
214
|
|
|
144
215
|
|
|
145
216
|
def draw_image(*args) -> None:
|
|
146
|
-
"""Draws an image
|
|
217
|
+
"""Draws an image onto a watermark canvas.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
args[0]: Canvas object to draw on
|
|
221
|
+
args[1]: Image data as bytes
|
|
222
|
+
args[2]: X coordinate for drawing
|
|
223
|
+
args[3]: Y coordinate for drawing
|
|
224
|
+
args[4]: Width of drawn image
|
|
225
|
+
args[5]: Height of drawn image
|
|
226
|
+
"""
|
|
147
227
|
|
|
148
228
|
canvas = args[0]
|
|
149
229
|
image_stream = args[1]
|
|
@@ -174,7 +254,17 @@ def create_watermarks_and_draw(
|
|
|
174
254
|
action_type: str,
|
|
175
255
|
actions: List[list],
|
|
176
256
|
) -> List[bytes]:
|
|
177
|
-
"""Creates
|
|
257
|
+
"""Creates watermarks for each page with specified drawing operations.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
pdf: PDF document as bytes
|
|
261
|
+
page_number: Page number to create watermark for (1-based)
|
|
262
|
+
action_type: Type of drawing operation ('text', 'image', 'line', etc.)
|
|
263
|
+
actions: List of drawing operations to perform
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
List[bytes]: Watermark data for each page (empty for non-target pages)
|
|
267
|
+
"""
|
|
178
268
|
|
|
179
269
|
pdf_file = PdfReader(stream_to_io(pdf))
|
|
180
270
|
buff = BytesIO()
|
|
@@ -216,9 +306,17 @@ def create_watermarks_and_draw(
|
|
|
216
306
|
|
|
217
307
|
def merge_watermarks_with_pdf(
|
|
218
308
|
pdf: bytes,
|
|
219
|
-
watermarks:
|
|
309
|
+
watermarks: List[bytes],
|
|
220
310
|
) -> bytes:
|
|
221
|
-
"""
|
|
311
|
+
"""Combines watermarks with their corresponding PDF pages.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
pdf: Original PDF document as bytes
|
|
315
|
+
watermarks: List of watermark data for each page
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
bytes: Merged PDF document with watermarks applied
|
|
319
|
+
"""
|
|
222
320
|
|
|
223
321
|
result = BytesIO()
|
|
224
322
|
pdf_file = PdfReader(stream_to_io(pdf))
|
|
@@ -234,3 +332,57 @@ def merge_watermarks_with_pdf(
|
|
|
234
332
|
output.write(result)
|
|
235
333
|
result.seek(0)
|
|
236
334
|
return result.read()
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def copy_watermark_widgets(
|
|
338
|
+
pdf: bytes, watermarks: List[bytes], keys: List[str]
|
|
339
|
+
) -> bytes:
|
|
340
|
+
"""
|
|
341
|
+
Copies annotation widgets (form fields) from watermark PDFs onto the corresponding pages of a base PDF,
|
|
342
|
+
including only those widgets whose key matches an entry in the provided keys list.
|
|
343
|
+
|
|
344
|
+
For each watermark in the provided list, any annotation widgets (such as form fields) are cloned
|
|
345
|
+
and appended to the annotations of the corresponding page in the base PDF, but only if their key
|
|
346
|
+
matches one of the specified keys.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
pdf: The original PDF document as bytes.
|
|
350
|
+
watermarks: List of watermark PDF data (as bytes), one per page. Empty or None entries are skipped.
|
|
351
|
+
keys: List of widget keys (str). Only widgets whose key is in this list will be copied.
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
bytes: The resulting PDF document with selected annotation widgets from watermarks copied onto their respective pages.
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
pdf_file = PdfReader(stream_to_io(pdf))
|
|
358
|
+
out = PdfWriter()
|
|
359
|
+
out.append(pdf_file)
|
|
360
|
+
|
|
361
|
+
widgets_to_copy = {}
|
|
362
|
+
|
|
363
|
+
for i, watermark in enumerate(watermarks):
|
|
364
|
+
if not watermark:
|
|
365
|
+
continue
|
|
366
|
+
|
|
367
|
+
widgets_to_copy[i] = []
|
|
368
|
+
watermark_file = PdfReader(stream_to_io(watermark))
|
|
369
|
+
for page in watermark_file.pages:
|
|
370
|
+
for annot in page.get(Annots, []): # noqa
|
|
371
|
+
key = extract_widget_property(
|
|
372
|
+
annot.get_object(), WIDGET_KEY_PATTERNS, None, str
|
|
373
|
+
)
|
|
374
|
+
if key in keys:
|
|
375
|
+
widgets_to_copy[i].append(annot.clone(out))
|
|
376
|
+
|
|
377
|
+
for i, page in enumerate(out.pages):
|
|
378
|
+
if i in widgets_to_copy:
|
|
379
|
+
page[NameObject(Annots)] = (
|
|
380
|
+
(page[NameObject(Annots)] + ArrayObject(widgets_to_copy[i])) # noqa
|
|
381
|
+
if Annots in page
|
|
382
|
+
else ArrayObject(widgets_to_copy[i])
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
with BytesIO() as f:
|
|
386
|
+
out.write(f)
|
|
387
|
+
f.seek(0)
|
|
388
|
+
return f.read()
|
PyPDFForm/widgets/base.py
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
2
|
+
"""Provides base widget class and utilities for PDF form field creation.
|
|
3
|
+
|
|
4
|
+
This module contains:
|
|
5
|
+
- Widget base class with core form field creation functionality
|
|
6
|
+
- Watermark generation for form fields
|
|
7
|
+
- Parameter handling for both AcroForm and non-AcroForm fields
|
|
8
|
+
"""
|
|
3
9
|
|
|
4
10
|
from io import BytesIO
|
|
5
|
-
from typing import List, cast
|
|
11
|
+
from typing import List, Union, cast
|
|
6
12
|
|
|
7
13
|
from pypdf import PdfReader, PdfWriter
|
|
8
14
|
from pypdf.generic import DictionaryObject
|
|
@@ -15,7 +21,21 @@ from ..utils import extract_widget_property, stream_to_io
|
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
class Widget:
|
|
18
|
-
"""
|
|
24
|
+
"""Abstract base class for all PDF form widget creators.
|
|
25
|
+
|
|
26
|
+
Provides common functionality for:
|
|
27
|
+
- Managing widget parameters
|
|
28
|
+
- Generating watermarks for form fields
|
|
29
|
+
- Handling both AcroForm and non-AcroForm parameters
|
|
30
|
+
- PDF page integration
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
USER_PARAMS: List of (user_name, pdf_param) mappings
|
|
34
|
+
COLOR_PARAMS: List of color parameters to convert
|
|
35
|
+
ALLOWED_NON_ACRO_FORM_PARAMS: Supported non-AcroForm parameters
|
|
36
|
+
NONE_DEFAULTS: Parameters that should default to None
|
|
37
|
+
ACRO_FORM_FUNC: Name of AcroForm function for widget creation
|
|
38
|
+
"""
|
|
19
39
|
|
|
20
40
|
USER_PARAMS = []
|
|
21
41
|
COLOR_PARAMS = []
|
|
@@ -27,11 +47,19 @@ class Widget:
|
|
|
27
47
|
self,
|
|
28
48
|
name: str,
|
|
29
49
|
page_number: int,
|
|
30
|
-
x: float,
|
|
31
|
-
y: float,
|
|
50
|
+
x: Union[float, List[float]],
|
|
51
|
+
y: Union[float, List[float]],
|
|
32
52
|
**kwargs,
|
|
33
53
|
) -> None:
|
|
34
|
-
"""
|
|
54
|
+
"""Initializes a new widget with position and parameters.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
name: Field name/key for the widget
|
|
58
|
+
page_number: Page number to place widget on (1-based)
|
|
59
|
+
x: X coordinate(s) for widget position
|
|
60
|
+
y: Y coordinate(s) for widget position
|
|
61
|
+
**kwargs: Additional widget-specific parameters
|
|
62
|
+
"""
|
|
35
63
|
|
|
36
64
|
super().__init__()
|
|
37
65
|
self.page_number = page_number
|
|
@@ -63,8 +91,27 @@ class Widget:
|
|
|
63
91
|
((type(self).__name__, each), kwargs.get(each))
|
|
64
92
|
)
|
|
65
93
|
|
|
94
|
+
def canvas_operations(self, canvas: Canvas) -> None:
|
|
95
|
+
"""Draws the widget on the provided PDF canvas using AcroForm.
|
|
96
|
+
|
|
97
|
+
Calls the appropriate AcroForm function on the canvas to create the widget
|
|
98
|
+
with the parameters specified in self.acro_form_params.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
canvas: The ReportLab Canvas object to draw the widget on.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
getattr(canvas.acroForm, self.ACRO_FORM_FUNC)(**self.acro_form_params)
|
|
105
|
+
|
|
66
106
|
def watermarks(self, stream: bytes) -> List[bytes]:
|
|
67
|
-
"""
|
|
107
|
+
"""Generates watermarks containing the widget for each page.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
stream: PDF document as bytes to add widget to
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List[bytes]: Watermark data for each page (empty for non-target pages)
|
|
114
|
+
"""
|
|
68
115
|
|
|
69
116
|
pdf = PdfReader(stream_to_io(stream))
|
|
70
117
|
page_count = len(pdf.pages)
|
|
@@ -78,7 +125,7 @@ class Widget:
|
|
|
78
125
|
),
|
|
79
126
|
)
|
|
80
127
|
|
|
81
|
-
|
|
128
|
+
self.canvas_operations(canvas)
|
|
82
129
|
|
|
83
130
|
canvas.showPage()
|
|
84
131
|
canvas.save()
|
|
@@ -91,7 +138,16 @@ class Widget:
|
|
|
91
138
|
|
|
92
139
|
|
|
93
140
|
def handle_non_acro_form_params(pdf: bytes, key: str, params: list) -> bytes:
|
|
94
|
-
"""
|
|
141
|
+
"""Processes non-AcroForm parameters for a widget.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
pdf: PDF document as bytes to modify
|
|
145
|
+
key: Field name/key of the widget to update
|
|
146
|
+
params: List of (parameter_name, value) tuples to set
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
bytes: Modified PDF with updated parameters
|
|
150
|
+
"""
|
|
95
151
|
|
|
96
152
|
pdf_file = PdfReader(stream_to_io(pdf))
|
|
97
153
|
out = PdfWriter()
|