PyPDFForm 2.4.0__py3-none-any.whl → 3.0.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 +22 -6
- PyPDFForm/adapter.py +28 -26
- PyPDFForm/constants.py +29 -34
- PyPDFForm/coordinate.py +23 -399
- PyPDFForm/filler.py +79 -303
- PyPDFForm/font.py +166 -164
- PyPDFForm/hooks.py +256 -0
- PyPDFForm/image.py +72 -22
- PyPDFForm/middleware/base.py +54 -48
- PyPDFForm/middleware/checkbox.py +29 -56
- PyPDFForm/middleware/dropdown.py +41 -30
- PyPDFForm/middleware/image.py +10 -22
- PyPDFForm/middleware/radio.py +30 -31
- PyPDFForm/middleware/signature.py +32 -47
- PyPDFForm/middleware/text.py +59 -48
- PyPDFForm/patterns.py +61 -141
- PyPDFForm/template.py +80 -427
- PyPDFForm/utils.py +142 -128
- PyPDFForm/watermark.py +77 -208
- PyPDFForm/widgets/base.py +57 -76
- PyPDFForm/widgets/checkbox.py +18 -21
- PyPDFForm/widgets/dropdown.py +18 -25
- PyPDFForm/widgets/image.py +11 -9
- PyPDFForm/widgets/radio.py +25 -35
- PyPDFForm/widgets/signature.py +29 -40
- PyPDFForm/widgets/text.py +18 -17
- PyPDFForm/wrapper.py +373 -437
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/METADATA +6 -7
- pypdfform-3.0.0.dist-info/RECORD +35 -0
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/WHEEL +1 -1
- pypdfform-2.4.0.dist-info/RECORD +0 -34
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/top_level.txt +0 -0
PyPDFForm/watermark.py
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- Supporting various drawing operations needed for form filling
|
|
2
|
+
"""
|
|
3
|
+
Module related to adding watermarks to a PDF.
|
|
4
|
+
|
|
5
|
+
This module provides functionalities to add watermarks to existing PDF documents.
|
|
6
|
+
It supports drawing text, lines, and images as watermarks.
|
|
7
|
+
The module also includes functions to merge these watermarks with the original PDF content
|
|
8
|
+
and to copy specific widgets from the watermarks to the original PDF.
|
|
10
9
|
"""
|
|
11
10
|
|
|
12
11
|
from io import BytesIO
|
|
@@ -23,208 +22,77 @@ from .utils import stream_to_io
|
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
def draw_text(canvas: Canvas, **kwargs) -> None:
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- Comb fields (fixed character spacing)
|
|
30
|
-
- Multiline text with wrapping
|
|
31
|
-
- Font and color styling
|
|
32
|
-
- Text alignment
|
|
25
|
+
"""
|
|
26
|
+
Draws a text string on the given canvas using the specified font, size, and color.
|
|
27
|
+
Supports multiline text by splitting the input string by newline characters.
|
|
33
28
|
|
|
34
29
|
Args:
|
|
35
|
-
canvas: Canvas object to draw on
|
|
36
|
-
**kwargs:
|
|
37
|
-
widget:
|
|
38
|
-
x:
|
|
39
|
-
y:
|
|
40
|
-
"""
|
|
30
|
+
canvas (Canvas): The ReportLab Canvas object to draw on.
|
|
31
|
+
**kwargs: Keyword arguments containing the text's properties and coordinates.
|
|
32
|
+
- widget: The widget object containing font, font_size, font_color, and value.
|
|
33
|
+
- x (float): The x-coordinate of the text's starting point.
|
|
34
|
+
- y (float): The y-coordinate of the text's starting point.
|
|
41
35
|
|
|
36
|
+
Returns:
|
|
37
|
+
None
|
|
38
|
+
"""
|
|
42
39
|
widget = kwargs["widget"]
|
|
43
40
|
coordinate_x = kwargs["x"]
|
|
44
41
|
coordinate_y = kwargs["y"]
|
|
45
42
|
|
|
46
43
|
text_to_draw = widget.value
|
|
47
|
-
|
|
48
|
-
if not text_to_draw:
|
|
49
|
-
text_to_draw = ""
|
|
50
|
-
|
|
51
|
-
if widget.max_length is not None:
|
|
52
|
-
text_to_draw = text_to_draw[: widget.max_length]
|
|
53
|
-
|
|
54
44
|
canvas.setFont(widget.font, widget.font_size)
|
|
55
45
|
canvas.setFillColorRGB(
|
|
56
46
|
widget.font_color[0], widget.font_color[1], widget.font_color[2]
|
|
57
47
|
)
|
|
48
|
+
text_obj = canvas.beginText(coordinate_x, coordinate_y)
|
|
49
|
+
for line in text_to_draw.split("\n"):
|
|
50
|
+
text_obj.textLine(line)
|
|
51
|
+
canvas.drawText(text_obj)
|
|
58
52
|
|
|
59
|
-
if widget.comb is True:
|
|
60
|
-
for i, char in enumerate(text_to_draw):
|
|
61
|
-
canvas.drawString(
|
|
62
|
-
coordinate_x + widget.character_paddings[i],
|
|
63
|
-
coordinate_y,
|
|
64
|
-
char,
|
|
65
|
-
)
|
|
66
|
-
elif (
|
|
67
|
-
widget.text_wrap_length is None or len(text_to_draw) < widget.text_wrap_length
|
|
68
|
-
) and widget.text_lines is None:
|
|
69
|
-
canvas.drawString(
|
|
70
|
-
coordinate_x,
|
|
71
|
-
coordinate_y,
|
|
72
|
-
text_to_draw,
|
|
73
|
-
)
|
|
74
|
-
else:
|
|
75
|
-
text_obj = canvas.beginText(0, 0)
|
|
76
|
-
for i, line in enumerate(widget.text_lines):
|
|
77
|
-
cursor_moved = False
|
|
78
|
-
if (
|
|
79
|
-
widget.text_line_x_coordinates is not None
|
|
80
|
-
and widget.text_line_x_coordinates[i] - coordinate_x != 0
|
|
81
|
-
):
|
|
82
|
-
text_obj.moveCursor(widget.text_line_x_coordinates[i] - coordinate_x, 0)
|
|
83
|
-
cursor_moved = True
|
|
84
|
-
text_obj.textLine(line)
|
|
85
|
-
if cursor_moved:
|
|
86
|
-
text_obj.moveCursor(
|
|
87
|
-
-1 * (widget.text_line_x_coordinates[i] - coordinate_x), 0
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
canvas.saveState()
|
|
91
|
-
canvas.translate(
|
|
92
|
-
coordinate_x,
|
|
93
|
-
coordinate_y,
|
|
94
|
-
)
|
|
95
|
-
canvas.drawText(text_obj)
|
|
96
|
-
canvas.restoreState()
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def draw_rect(canvas: Canvas, **kwargs) -> None:
|
|
100
|
-
"""Draws a rectangle onto a watermark canvas.
|
|
101
53
|
|
|
102
|
-
|
|
103
|
-
canvas: Canvas object to draw on
|
|
104
|
-
**kwargs: Additional arguments including:
|
|
105
|
-
x: X coordinate of bottom-left corner
|
|
106
|
-
y: Y coordinate of bottom-left corner
|
|
107
|
-
width: Width of rectangle
|
|
108
|
-
height: Height of rectangle
|
|
109
|
-
border_color: Border color
|
|
110
|
-
background_color: Background color
|
|
111
|
-
border_width: Border width
|
|
112
|
-
dash_array: Dash pattern for border
|
|
54
|
+
def draw_line(canvas: Canvas, **kwargs) -> None:
|
|
113
55
|
"""
|
|
114
|
-
|
|
115
|
-
x = kwargs["x"]
|
|
116
|
-
y = kwargs["y"]
|
|
117
|
-
width = kwargs["width"]
|
|
118
|
-
height = kwargs["height"]
|
|
119
|
-
|
|
120
|
-
canvas.saveState()
|
|
121
|
-
stroke, fill = set_border_and_background_styles(canvas, **kwargs)
|
|
122
|
-
canvas.rect(x, y, width, height, stroke=stroke, fill=fill)
|
|
123
|
-
canvas.restoreState()
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def draw_ellipse(canvas: Canvas, **kwargs) -> None:
|
|
127
|
-
"""Draws an ellipse onto a watermark canvas.
|
|
56
|
+
Draws a line on the given canvas with the specified source and destination coordinates, and color.
|
|
128
57
|
|
|
129
58
|
Args:
|
|
130
|
-
canvas: Canvas object to draw on
|
|
131
|
-
**kwargs:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
background_color: Background color
|
|
138
|
-
border_width: Border width
|
|
139
|
-
dash_array: Dash pattern for border
|
|
140
|
-
"""
|
|
59
|
+
canvas (Canvas): The ReportLab Canvas object to draw on.
|
|
60
|
+
**kwargs: Keyword arguments containing the line's properties and coordinates.
|
|
61
|
+
- src_x (float): The x-coordinate of the line's starting point.
|
|
62
|
+
- src_y (float): The y-coordinate of the line's starting point.
|
|
63
|
+
- dest_x (float): The x-coordinate of the line's ending point.
|
|
64
|
+
- dest_y (float): The y-coordinate of the line's ending point.
|
|
65
|
+
- color (tuple): A tuple representing the RGB color of the line.
|
|
141
66
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
x2 = kwargs["x2"]
|
|
145
|
-
y2 = kwargs["y2"]
|
|
146
|
-
|
|
147
|
-
canvas.saveState()
|
|
148
|
-
stroke, fill = set_border_and_background_styles(canvas, **kwargs)
|
|
149
|
-
canvas.ellipse(x1, y1, x2, y2, stroke=stroke, fill=fill)
|
|
150
|
-
canvas.restoreState()
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def draw_line(canvas: Canvas, **kwargs) -> None:
|
|
154
|
-
"""Draws a line onto a watermark canvas.
|
|
155
|
-
|
|
156
|
-
Args:
|
|
157
|
-
canvas: Canvas object to draw on
|
|
158
|
-
**kwargs: Additional arguments including:
|
|
159
|
-
src_x: X coordinate of start point
|
|
160
|
-
src_y: Y coordinate of start point
|
|
161
|
-
dest_x: X coordinate of end point
|
|
162
|
-
dest_y: Y coordinate of end point
|
|
163
|
-
border_color: Line color
|
|
164
|
-
border_width: Line width
|
|
165
|
-
dash_array: Dash pattern for line
|
|
67
|
+
Returns:
|
|
68
|
+
None
|
|
166
69
|
"""
|
|
167
|
-
|
|
168
70
|
src_x = kwargs["src_x"]
|
|
169
71
|
src_y = kwargs["src_y"]
|
|
170
72
|
dest_x = kwargs["dest_x"]
|
|
171
73
|
dest_y = kwargs["dest_y"]
|
|
74
|
+
color = kwargs["color"]
|
|
172
75
|
|
|
173
|
-
canvas.
|
|
174
|
-
set_border_and_background_styles(canvas, **kwargs)
|
|
76
|
+
canvas.setStrokeColorRGB(*(color))
|
|
175
77
|
canvas.line(src_x, src_y, dest_x, dest_y)
|
|
176
|
-
canvas.restoreState()
|
|
177
78
|
|
|
178
79
|
|
|
179
|
-
def
|
|
180
|
-
"""
|
|
80
|
+
def draw_image(canvas: Canvas, **kwargs) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Draws an image on the given canvas, scaling it to fit within the specified width and height.
|
|
181
83
|
|
|
182
84
|
Args:
|
|
183
|
-
canvas: Canvas object to
|
|
184
|
-
**kwargs:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
85
|
+
canvas (Canvas): The ReportLab Canvas object to draw on.
|
|
86
|
+
**kwargs: Keyword arguments containing the image's properties and coordinates.
|
|
87
|
+
- stream (bytes): The image data as a byte stream.
|
|
88
|
+
- x (float): The x-coordinate of the image's bottom-left corner.
|
|
89
|
+
- y (float): The y-coordinate of the image's bottom-left corner.
|
|
90
|
+
- width (float): The desired width of the image.
|
|
91
|
+
- height (float): The desired height of the image.
|
|
189
92
|
|
|
190
93
|
Returns:
|
|
191
|
-
|
|
94
|
+
None
|
|
192
95
|
"""
|
|
193
|
-
|
|
194
|
-
border_color = kwargs["border_color"]
|
|
195
|
-
background_color = kwargs["background_color"]
|
|
196
|
-
border_width = kwargs["border_width"]
|
|
197
|
-
dash_array = kwargs["dash_array"]
|
|
198
|
-
|
|
199
|
-
stroke = 0
|
|
200
|
-
fill = 0
|
|
201
|
-
if border_color is not None and border_width:
|
|
202
|
-
canvas.setStrokeColor(border_color)
|
|
203
|
-
canvas.setLineWidth(border_width)
|
|
204
|
-
stroke = 1
|
|
205
|
-
if background_color is not None:
|
|
206
|
-
canvas.setFillColor(background_color)
|
|
207
|
-
fill = 1
|
|
208
|
-
|
|
209
|
-
if dash_array is not None:
|
|
210
|
-
canvas.setDash(array=dash_array)
|
|
211
|
-
|
|
212
|
-
return stroke, fill
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def draw_image(canvas: Canvas, **kwargs) -> None:
|
|
216
|
-
"""Draws an image onto a watermark canvas.
|
|
217
|
-
|
|
218
|
-
Args:
|
|
219
|
-
canvas: Canvas object to draw on
|
|
220
|
-
**kwargs: Additional arguments including:
|
|
221
|
-
stream: Image data as bytes
|
|
222
|
-
x: X coordinate for drawing
|
|
223
|
-
y: Y coordinate for drawing
|
|
224
|
-
width: Width of drawn image
|
|
225
|
-
height: Height of drawn image
|
|
226
|
-
"""
|
|
227
|
-
|
|
228
96
|
image_stream = kwargs["stream"]
|
|
229
97
|
coordinate_x = kwargs["x"]
|
|
230
98
|
coordinate_y = kwargs["y"]
|
|
@@ -253,18 +121,22 @@ def create_watermarks_and_draw(
|
|
|
253
121
|
action_type: str,
|
|
254
122
|
actions: List[dict],
|
|
255
123
|
) -> List[bytes]:
|
|
256
|
-
"""
|
|
124
|
+
"""
|
|
125
|
+
Creates watermarks for a specific page in the PDF based on the provided actions and draws them.
|
|
126
|
+
|
|
127
|
+
This function takes a PDF file, a page number, an action type, and a list of actions as input.
|
|
128
|
+
It then creates a watermark for the specified page by drawing the specified actions on a Canvas object.
|
|
257
129
|
|
|
258
130
|
Args:
|
|
259
|
-
pdf: PDF
|
|
260
|
-
page_number:
|
|
261
|
-
action_type:
|
|
262
|
-
actions
|
|
131
|
+
pdf (bytes): The PDF file as a byte stream.
|
|
132
|
+
page_number (int): The page number to which the watermark should be applied (1-indexed).
|
|
133
|
+
action_type (str): The type of action to perform when creating the watermark (e.g., "image", "text", "line").
|
|
134
|
+
actions (List[dict]): A list of dictionaries, where each dictionary represents an action to be performed on the watermark.
|
|
263
135
|
|
|
264
136
|
Returns:
|
|
265
|
-
List[bytes]:
|
|
137
|
+
List[bytes]: A list of byte streams, where the element at index (page_number - 1) contains the watermark for the specified page,
|
|
138
|
+
and all other elements are empty byte streams.
|
|
266
139
|
"""
|
|
267
|
-
|
|
268
140
|
pdf_file = PdfReader(stream_to_io(pdf))
|
|
269
141
|
buff = BytesIO()
|
|
270
142
|
|
|
@@ -280,8 +152,6 @@ def create_watermarks_and_draw(
|
|
|
280
152
|
"image": draw_image,
|
|
281
153
|
"text": draw_text,
|
|
282
154
|
"line": draw_line,
|
|
283
|
-
"rect": draw_rect,
|
|
284
|
-
"ellipse": draw_ellipse,
|
|
285
155
|
}
|
|
286
156
|
|
|
287
157
|
if action_type_to_func.get(action_type):
|
|
@@ -303,16 +173,19 @@ def merge_watermarks_with_pdf(
|
|
|
303
173
|
pdf: bytes,
|
|
304
174
|
watermarks: List[bytes],
|
|
305
175
|
) -> bytes:
|
|
306
|
-
"""
|
|
176
|
+
"""
|
|
177
|
+
Merges the generated watermarks with the original PDF content.
|
|
178
|
+
|
|
179
|
+
This function takes a PDF file and a list of watermarks as input.
|
|
180
|
+
It then merges each watermark with its corresponding page in the PDF.
|
|
307
181
|
|
|
308
182
|
Args:
|
|
309
|
-
pdf:
|
|
310
|
-
watermarks
|
|
183
|
+
pdf (bytes): The PDF file as a byte stream.
|
|
184
|
+
watermarks (List[bytes]): A list of byte streams, where each element represents the watermark for a specific page.
|
|
311
185
|
|
|
312
186
|
Returns:
|
|
313
|
-
bytes:
|
|
187
|
+
bytes: A byte stream representing the merged PDF with watermarks applied.
|
|
314
188
|
"""
|
|
315
|
-
|
|
316
189
|
result = BytesIO()
|
|
317
190
|
pdf_file = PdfReader(stream_to_io(pdf))
|
|
318
191
|
output = PdfWriter()
|
|
@@ -335,32 +208,26 @@ def copy_watermark_widgets(
|
|
|
335
208
|
keys: Union[List[str], None],
|
|
336
209
|
page_num: Union[int, None],
|
|
337
210
|
) -> bytes:
|
|
338
|
-
"""
|
|
339
|
-
|
|
340
|
-
This function selectively copies annotation widgets (form fields) from watermark PDFs to a base PDF.
|
|
341
|
-
It allows specifying which widgets to copy based on their keys, and optionally restricts the operation
|
|
342
|
-
to a specific page number.
|
|
211
|
+
"""
|
|
212
|
+
Copies specific widgets from the watermarks to the original PDF.
|
|
343
213
|
|
|
344
|
-
|
|
345
|
-
|
|
214
|
+
This function allows you to selectively copy widgets (e.g., form fields) from the watermarks to the original PDF.
|
|
215
|
+
You can specify which widgets to copy by providing a list of keys.
|
|
346
216
|
|
|
347
217
|
Args:
|
|
348
|
-
pdf: The
|
|
349
|
-
watermarks:
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
If None, all widgets will be copied.
|
|
353
|
-
page_num: Optional page number (0-based) to restrict widget copying to. If None, widgets are copied
|
|
354
|
-
across all pages.
|
|
218
|
+
pdf (bytes): The PDF file as a byte stream.
|
|
219
|
+
watermarks (Union[List[bytes], bytes]): A list of byte streams, where each element represents the watermark for a specific page.
|
|
220
|
+
keys (Union[List[str], None]): A list of keys identifying the widgets to copy from the watermarks. If None, all widgets are copied.
|
|
221
|
+
page_num (Union[int, None]): The page number to copy the widgets from. If None, widgets are copied from all pages.
|
|
355
222
|
|
|
356
223
|
Returns:
|
|
357
|
-
bytes:
|
|
224
|
+
bytes: A byte stream representing the modified PDF with the specified widgets copied from the watermarks.
|
|
358
225
|
"""
|
|
359
|
-
|
|
360
226
|
pdf_file = PdfReader(stream_to_io(pdf))
|
|
361
227
|
out = PdfWriter()
|
|
362
228
|
out.append(pdf_file)
|
|
363
229
|
|
|
230
|
+
# TODO: refactor duplicate logic with merge_two_pdfs
|
|
364
231
|
widgets_to_copy_watermarks = {}
|
|
365
232
|
widgets_to_copy_pdf = {}
|
|
366
233
|
|
|
@@ -382,6 +249,8 @@ def copy_watermark_widgets(
|
|
|
382
249
|
widgets_to_copy_pdf[j] = []
|
|
383
250
|
for annot in page.get(Annots, []):
|
|
384
251
|
key = get_widget_key(annot.get_object(), False)
|
|
252
|
+
|
|
253
|
+
# cannot be watermarks when page_num not None
|
|
385
254
|
if (keys is None or key in keys) and (
|
|
386
255
|
page_num is None or page_num == j
|
|
387
256
|
):
|
PyPDFForm/widgets/base.py
CHANGED
|
@@ -1,46 +1,42 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
2
|
+
"""
|
|
3
|
+
This module defines the base class for all widgets in PyPDFForm.
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
It provides a common interface for interacting with different types of form fields,
|
|
6
|
+
such as text fields, checkboxes, and radio buttons. The Widget class handles
|
|
7
|
+
basic properties like name, page number, and coordinates, and provides methods
|
|
8
|
+
for rendering the widget on a PDF page.
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
from io import BytesIO
|
|
11
|
-
from typing import List, Union
|
|
12
|
+
from typing import List, Union
|
|
12
13
|
|
|
13
|
-
from pypdf import PdfReader
|
|
14
|
-
from pypdf.generic import DictionaryObject
|
|
14
|
+
from pypdf import PdfReader
|
|
15
15
|
from reportlab.lib.colors import Color
|
|
16
16
|
from reportlab.pdfgen.canvas import Canvas
|
|
17
17
|
|
|
18
|
-
from ..constants import Annots
|
|
19
|
-
from ..patterns import NON_ACRO_FORM_PARAM_TO_FUNC
|
|
20
|
-
from ..template import get_widget_key
|
|
21
18
|
from ..utils import stream_to_io
|
|
22
19
|
|
|
23
20
|
|
|
24
21
|
class Widget:
|
|
25
|
-
"""
|
|
22
|
+
"""
|
|
23
|
+
Base class for all widgets in PyPDFForm.
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- Handling both AcroForm and non-AcroForm parameters
|
|
31
|
-
- PDF page integration
|
|
25
|
+
This class provides a common interface for interacting with different types of
|
|
26
|
+
form fields. It handles basic properties like name, page number, and
|
|
27
|
+
coordinates, and provides methods for rendering the widget on a PDF page.
|
|
32
28
|
|
|
33
29
|
Attributes:
|
|
34
|
-
USER_PARAMS: List of
|
|
35
|
-
COLOR_PARAMS: List of color parameters
|
|
36
|
-
|
|
37
|
-
NONE_DEFAULTS:
|
|
38
|
-
ACRO_FORM_FUNC: Name of AcroForm function for widget
|
|
30
|
+
USER_PARAMS (list): List of user-defined parameters for the widget.
|
|
31
|
+
COLOR_PARAMS (list): List of color-related parameters for the widget.
|
|
32
|
+
ALLOWED_HOOK_PARAMS (list): List of allowed hook parameters for the widget.
|
|
33
|
+
NONE_DEFAULTS (list): List of parameters that default to None.
|
|
34
|
+
ACRO_FORM_FUNC (str): Name of the AcroForm function to use for rendering the widget.
|
|
39
35
|
"""
|
|
40
36
|
|
|
41
37
|
USER_PARAMS = []
|
|
42
38
|
COLOR_PARAMS = []
|
|
43
|
-
|
|
39
|
+
ALLOWED_HOOK_PARAMS = []
|
|
44
40
|
NONE_DEFAULTS = []
|
|
45
41
|
ACRO_FORM_FUNC = ""
|
|
46
42
|
|
|
@@ -52,16 +48,23 @@ class Widget:
|
|
|
52
48
|
y: Union[float, List[float]],
|
|
53
49
|
**kwargs,
|
|
54
50
|
) -> None:
|
|
55
|
-
"""
|
|
51
|
+
"""
|
|
52
|
+
Initializes a Widget object.
|
|
53
|
+
|
|
54
|
+
This method sets up the basic properties of the widget, such as its name,
|
|
55
|
+
page number, and coordinates. It also handles user-defined parameters,
|
|
56
|
+
color parameters, and hook parameters.
|
|
56
57
|
|
|
57
58
|
Args:
|
|
58
|
-
name:
|
|
59
|
-
page_number: Page number
|
|
60
|
-
x: X coordinate(s)
|
|
61
|
-
y: Y coordinate(s)
|
|
62
|
-
**kwargs: Additional widget
|
|
63
|
-
"""
|
|
59
|
+
name (str): Name of the widget.
|
|
60
|
+
page_number (int): Page number of the widget.
|
|
61
|
+
x (Union[float, List[float]]): X coordinate(s) of the widget. Can be a single float or a list of floats.
|
|
62
|
+
y (Union[float, List[float]]): Y coordinate(s) of the widget. Can be a single float or a list of floats.
|
|
63
|
+
**kwargs: Additional keyword arguments for customizing the widget.
|
|
64
64
|
|
|
65
|
+
Returns:
|
|
66
|
+
None
|
|
67
|
+
"""
|
|
65
68
|
super().__init__()
|
|
66
69
|
self.page_number = page_number
|
|
67
70
|
self.acro_form_params = {
|
|
@@ -69,7 +72,7 @@ class Widget:
|
|
|
69
72
|
"x": x,
|
|
70
73
|
"y": y,
|
|
71
74
|
}
|
|
72
|
-
self.
|
|
75
|
+
self.hook_params = []
|
|
73
76
|
|
|
74
77
|
for each in self.USER_PARAMS:
|
|
75
78
|
user_input, param = each
|
|
@@ -86,34 +89,44 @@ class Widget:
|
|
|
86
89
|
elif user_input in self.NONE_DEFAULTS:
|
|
87
90
|
self.acro_form_params[param] = None
|
|
88
91
|
|
|
89
|
-
for each in self.
|
|
92
|
+
for each in self.ALLOWED_HOOK_PARAMS:
|
|
90
93
|
if each in kwargs:
|
|
91
|
-
self.
|
|
92
|
-
((type(self).__name__, each), kwargs.get(each))
|
|
93
|
-
)
|
|
94
|
+
self.hook_params.append((each, kwargs.get(each)))
|
|
94
95
|
|
|
95
96
|
def canvas_operations(self, canvas: Canvas) -> None:
|
|
96
|
-
"""
|
|
97
|
+
"""
|
|
98
|
+
Performs canvas operations for the widget.
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
This method uses the ReportLab library to draw the widget on the PDF canvas.
|
|
101
|
+
It retrieves the appropriate AcroForm function from the canvas and calls it
|
|
102
|
+
with the widget's parameters.
|
|
100
103
|
|
|
101
104
|
Args:
|
|
102
|
-
canvas:
|
|
103
|
-
"""
|
|
105
|
+
canvas (Canvas): Canvas object to operate on.
|
|
104
106
|
|
|
107
|
+
Returns:
|
|
108
|
+
None
|
|
109
|
+
"""
|
|
105
110
|
getattr(canvas.acroForm, self.ACRO_FORM_FUNC)(**self.acro_form_params)
|
|
106
111
|
|
|
107
112
|
def watermarks(self, stream: bytes) -> List[bytes]:
|
|
108
|
-
"""
|
|
113
|
+
"""
|
|
114
|
+
Generates watermarks for the widget.
|
|
115
|
+
|
|
116
|
+
This method takes a PDF stream as input and generates watermarks for each
|
|
117
|
+
page of the PDF. The watermark is created by drawing the widget on a
|
|
118
|
+
ReportLab canvas and then embedding the canvas as a watermark on the
|
|
119
|
+
specified page.
|
|
109
120
|
|
|
110
121
|
Args:
|
|
111
|
-
stream: PDF
|
|
122
|
+
stream (bytes): PDF stream.
|
|
112
123
|
|
|
113
124
|
Returns:
|
|
114
|
-
List[bytes]:
|
|
125
|
+
List[bytes]: List of watermarks for each page. Each element in the list
|
|
126
|
+
is a byte stream representing the watermark for that page.
|
|
127
|
+
If a page does not need a watermark, the corresponding
|
|
128
|
+
element will be an empty byte string.
|
|
115
129
|
"""
|
|
116
|
-
|
|
117
130
|
pdf = PdfReader(stream_to_io(stream))
|
|
118
131
|
page_count = len(pdf.pages)
|
|
119
132
|
watermark = BytesIO()
|
|
@@ -136,35 +149,3 @@ class Widget:
|
|
|
136
149
|
watermark.read() if i == self.page_number - 1 else b""
|
|
137
150
|
for i in range(page_count)
|
|
138
151
|
]
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def handle_non_acro_form_params(pdf: bytes, key: str, params: list) -> bytes:
|
|
142
|
-
"""Processes non-AcroForm parameters for a widget.
|
|
143
|
-
|
|
144
|
-
Args:
|
|
145
|
-
pdf: PDF document as bytes to modify
|
|
146
|
-
key: Field name/key of the widget to update
|
|
147
|
-
params: List of (parameter_name, value) tuples to set
|
|
148
|
-
|
|
149
|
-
Returns:
|
|
150
|
-
bytes: Modified PDF with updated parameters
|
|
151
|
-
"""
|
|
152
|
-
|
|
153
|
-
pdf_file = PdfReader(stream_to_io(pdf))
|
|
154
|
-
out = PdfWriter()
|
|
155
|
-
out.append(pdf_file)
|
|
156
|
-
|
|
157
|
-
for page in out.pages:
|
|
158
|
-
for annot in page.get(Annots, []):
|
|
159
|
-
annot = cast(DictionaryObject, annot.get_object())
|
|
160
|
-
_key = get_widget_key(annot.get_object(), False)
|
|
161
|
-
|
|
162
|
-
if _key == key:
|
|
163
|
-
for param in params:
|
|
164
|
-
if param[0] in NON_ACRO_FORM_PARAM_TO_FUNC:
|
|
165
|
-
NON_ACRO_FORM_PARAM_TO_FUNC[param[0]](annot, param[1])
|
|
166
|
-
|
|
167
|
-
with BytesIO() as f:
|
|
168
|
-
out.write(f)
|
|
169
|
-
f.seek(0)
|
|
170
|
-
return f.read()
|
PyPDFForm/widgets/checkbox.py
CHANGED
|
@@ -1,34 +1,30 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
- Interactive checkbox fields with three states (checked, unchecked, read-only)
|
|
6
|
-
- Custom button styles (check, cross, circle)
|
|
7
|
-
- Color styling for tick, background and border
|
|
8
|
-
- Size adjustments
|
|
9
|
-
- PDF form field integration
|
|
10
|
-
|
|
11
|
-
Supports all standard PDF checkbox properties and integrates with both
|
|
12
|
-
AcroForm and non-AcroForm PDF documents.
|
|
2
|
+
"""
|
|
3
|
+
This module defines the CheckBoxWidget class, which is a subclass of the
|
|
4
|
+
Widget class. It represents a checkbox form field in a PDF document.
|
|
13
5
|
"""
|
|
14
6
|
|
|
15
7
|
from .base import Widget
|
|
16
8
|
|
|
17
9
|
|
|
18
10
|
class CheckBoxWidget(Widget):
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
11
|
+
"""
|
|
12
|
+
Represents a checkbox widget in a PDF form.
|
|
13
|
+
|
|
14
|
+
Inherits from the base Widget class and adds specific parameters for
|
|
15
|
+
checkbox styling, such as button style, tick color, background color,
|
|
16
|
+
border color, and border width.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
USER_PARAMS (list): A list of tuples, where each tuple contains the
|
|
20
|
+
user-facing parameter name and the corresponding AcroForm parameter name.
|
|
21
|
+
COLOR_PARAMS (list): A list of user-facing parameter names that represent colors.
|
|
22
|
+
ALLOWED_HOOK_PARAMS (list): A list of allowed hook parameters.
|
|
23
|
+
ACRO_FORM_FUNC (str): The name of the AcroForm function to use for
|
|
24
|
+
creating the checkbox.
|
|
28
25
|
"""
|
|
29
26
|
|
|
30
27
|
USER_PARAMS = [
|
|
31
|
-
("size", "size"),
|
|
32
28
|
("button_style", "buttonStyle"),
|
|
33
29
|
("tick_color", "textColor"),
|
|
34
30
|
("bg_color", "fillColor"),
|
|
@@ -36,4 +32,5 @@ class CheckBoxWidget(Widget):
|
|
|
36
32
|
("border_width", "borderWidth"),
|
|
37
33
|
]
|
|
38
34
|
COLOR_PARAMS = ["tick_color", "bg_color", "border_color"]
|
|
35
|
+
ALLOWED_HOOK_PARAMS = ["size"]
|
|
39
36
|
ACRO_FORM_FUNC = "checkbox"
|