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/watermark.py CHANGED
@@ -1,18 +1,42 @@
1
1
  # -*- coding: utf-8 -*-
2
- """Contains helpers for watermark."""
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 .utils import stream_to_io
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 text on the watermark."""
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 on the watermark."""
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 on the watermark."""
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 on the watermark."""
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
- """Sets colors for both border and background before drawing."""
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 on the watermark."""
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 a canvas watermark and draw some stuffs on it."""
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: list,
309
+ watermarks: List[bytes],
220
310
  ) -> bytes:
221
- """Merges watermarks with PDF."""
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
- """Contains base class for all widgets to create."""
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
- """Base class for all widgets to create."""
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
- """Sets acro form parameters."""
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
- """Returns a list of watermarks after creating the widget."""
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
- getattr(canvas.acroForm, self.ACRO_FORM_FUNC)(**self.acro_form_params)
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
- """Handles non acro form parameters when creating a widget."""
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()