PyPDFForm 2.0.0__py3-none-any.whl → 2.1.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,41 @@
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
 
20
+ from .constants import Annots
11
21
  from .utils import stream_to_io
12
22
 
13
23
 
14
24
  def draw_text(*args) -> None:
15
- """Draws a text on the watermark."""
25
+ """Draws text onto a watermark canvas with proper formatting.
26
+
27
+ Handles:
28
+ - Comb fields (fixed character spacing)
29
+ - Multiline text with wrapping
30
+ - Font and color styling
31
+ - Text alignment
32
+
33
+ Args:
34
+ args[0]: Canvas object to draw on
35
+ args[1]: Text widget with content and properties
36
+ args[2]: X coordinate for drawing
37
+ args[3]: Y coordinate for drawing
38
+ """
16
39
 
17
40
  canvas = args[0]
18
41
  widget = args[1]
@@ -73,7 +96,19 @@ def draw_text(*args) -> None:
73
96
 
74
97
 
75
98
  def draw_rect(*args) -> None:
76
- """Draws a rectangle on the watermark."""
99
+ """Draws a rectangle onto a watermark canvas.
100
+
101
+ Args:
102
+ args[0]: Canvas object to draw on
103
+ args[1]: X coordinate of bottom-left corner
104
+ args[2]: Y coordinate of bottom-left corner
105
+ args[3]: Width of rectangle
106
+ args[4]: Height of rectangle
107
+ args[5]: Border color
108
+ args[6]: Background color
109
+ args[7]: Border width
110
+ args[8]: Dash pattern for border
111
+ """
77
112
 
78
113
  canvas = args[0]
79
114
  x = args[1]
@@ -88,7 +123,19 @@ def draw_rect(*args) -> None:
88
123
 
89
124
 
90
125
  def draw_ellipse(*args) -> None:
91
- """Draws an ellipse on the watermark."""
126
+ """Draws an ellipse onto a watermark canvas.
127
+
128
+ Args:
129
+ args[0]: Canvas object to draw on
130
+ args[1]: X coordinate of first bounding point
131
+ args[2]: Y coordinate of first bounding point
132
+ args[3]: X coordinate of second bounding point
133
+ args[4]: Y coordinate of second bounding point
134
+ args[5]: Border color
135
+ args[6]: Background color
136
+ args[7]: Border width
137
+ args[8]: Dash pattern for border
138
+ """
92
139
 
93
140
  canvas = args[0]
94
141
  x1 = args[1]
@@ -103,7 +150,19 @@ def draw_ellipse(*args) -> None:
103
150
 
104
151
 
105
152
  def draw_line(*args) -> None:
106
- """Draws a line on the watermark."""
153
+ """Draws a line onto a watermark canvas.
154
+
155
+ Args:
156
+ args[0]: Canvas object to draw on
157
+ args[1]: X coordinate of start point
158
+ args[2]: Y coordinate of start point
159
+ args[3]: X coordinate of end point
160
+ args[4]: Y coordinate of end point
161
+ args[5]: Line color
162
+ args[6]: Unused (kept for consistency)
163
+ args[7]: Line width
164
+ args[8]: Dash pattern for line
165
+ """
107
166
 
108
167
  canvas = args[0]
109
168
  src_x = args[1]
@@ -118,7 +177,18 @@ def draw_line(*args) -> None:
118
177
 
119
178
 
120
179
  def set_border_and_background_styles(*args) -> tuple:
121
- """Sets colors for both border and background before drawing."""
180
+ """Configures stroke and fill styles for drawing operations.
181
+
182
+ Args:
183
+ args[0]: Canvas object to configure
184
+ args[5]: Border color
185
+ args[6]: Background color
186
+ args[7]: Border width
187
+ args[8]: Dash pattern for border
188
+
189
+ Returns:
190
+ tuple: (stroke_flag, fill_flag) indicating which styles were set
191
+ """
122
192
 
123
193
  canvas = args[0]
124
194
  border_color = args[5]
@@ -143,7 +213,16 @@ def set_border_and_background_styles(*args) -> tuple:
143
213
 
144
214
 
145
215
  def draw_image(*args) -> None:
146
- """Draws an image on the watermark."""
216
+ """Draws an image onto a watermark canvas.
217
+
218
+ Args:
219
+ args[0]: Canvas object to draw on
220
+ args[1]: Image data as bytes
221
+ args[2]: X coordinate for drawing
222
+ args[3]: Y coordinate for drawing
223
+ args[4]: Width of drawn image
224
+ args[5]: Height of drawn image
225
+ """
147
226
 
148
227
  canvas = args[0]
149
228
  image_stream = args[1]
@@ -174,7 +253,17 @@ def create_watermarks_and_draw(
174
253
  action_type: str,
175
254
  actions: List[list],
176
255
  ) -> List[bytes]:
177
- """Creates a canvas watermark and draw some stuffs on it."""
256
+ """Creates watermarks for each page with specified drawing operations.
257
+
258
+ Args:
259
+ pdf: PDF document as bytes
260
+ page_number: Page number to create watermark for (1-based)
261
+ action_type: Type of drawing operation ('text', 'image', 'line', etc.)
262
+ actions: List of drawing operations to perform
263
+
264
+ Returns:
265
+ List[bytes]: Watermark data for each page (empty for non-target pages)
266
+ """
178
267
 
179
268
  pdf_file = PdfReader(stream_to_io(pdf))
180
269
  buff = BytesIO()
@@ -218,7 +307,15 @@ def merge_watermarks_with_pdf(
218
307
  pdf: bytes,
219
308
  watermarks: list,
220
309
  ) -> bytes:
221
- """Merges watermarks with PDF."""
310
+ """Combines watermarks with their corresponding PDF pages.
311
+
312
+ Args:
313
+ pdf: Original PDF document as bytes
314
+ watermarks: List of watermark data for each page
315
+
316
+ Returns:
317
+ bytes: Merged PDF document with watermarks applied
318
+ """
222
319
 
223
320
  result = BytesIO()
224
321
  pdf_file = PdfReader(stream_to_io(pdf))
@@ -234,3 +331,48 @@ def merge_watermarks_with_pdf(
234
331
  output.write(result)
235
332
  result.seek(0)
236
333
  return result.read()
334
+
335
+
336
+ def copy_watermark_widgets(
337
+ pdf: bytes,
338
+ watermarks: list,
339
+ ) -> bytes:
340
+ """Copies annotation widgets from watermark PDFs onto the corresponding pages of a base PDF.
341
+
342
+ For each watermark in the provided list, any annotation widgets (such as form fields)
343
+ are cloned and appended to the annotations of the corresponding page in the base PDF.
344
+
345
+ Args:
346
+ pdf: The original PDF document as bytes.
347
+ watermarks: List of watermark PDF data (as bytes), one per page. Empty or None entries are skipped.
348
+
349
+ Returns:
350
+ bytes: The resulting PDF document with annotation widgets from watermarks copied onto their respective pages.
351
+ """
352
+
353
+ pdf_file = PdfReader(stream_to_io(pdf))
354
+ out = PdfWriter()
355
+ out.append(pdf_file)
356
+
357
+ widgets_to_copy = {}
358
+
359
+ for i, watermark in enumerate(watermarks):
360
+ if not watermark:
361
+ continue
362
+
363
+ widgets_to_copy[i] = []
364
+ watermark_file = PdfReader(stream_to_io(watermark))
365
+ for page in watermark_file.pages:
366
+ for annot in page.get(Annots, []): # noqa
367
+ widgets_to_copy[i].append(annot.clone(out))
368
+
369
+ for i, page in enumerate(out.pages):
370
+ if i in widgets_to_copy:
371
+ page[NameObject(Annots)] = page[NameObject(Annots)] + ArrayObject(
372
+ widgets_to_copy[i]
373
+ ) # noqa
374
+
375
+ with BytesIO() as f:
376
+ out.write(f)
377
+ f.seek(0)
378
+ 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()
@@ -1,11 +1,31 @@
1
1
  # -*- coding: utf-8 -*-
2
- """Contains checkbox widget to create."""
2
+ """Provides checkbox widget creation functionality for PDF forms.
3
+
4
+ This module contains the CheckBoxWidget class which handles creation of:
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.
13
+ """
3
14
 
4
15
  from .base import Widget
5
16
 
6
17
 
7
18
  class CheckBoxWidget(Widget):
8
- """Checkbox widget to create."""
19
+ """Creates and configures PDF checkbox widgets.
20
+
21
+ Supports all standard checkbox properties including:
22
+ - Button style customization (check, cross, circle)
23
+ - Tick, background and border colors
24
+ - Size adjustments
25
+ - PDF form field integration
26
+
27
+ Inherits from Widget base class adding checkbox-specific parameters.
28
+ """
9
29
 
10
30
  USER_PARAMS = [
11
31
  ("size", "size"),
@@ -1,11 +1,28 @@
1
1
  # -*- coding: utf-8 -*-
2
- """Contains dropdown widget to create."""
2
+ """Provides dropdown widget creation functionality for PDF forms.
3
+
4
+ This module contains the DropdownWidget class which handles creation of:
5
+ - Interactive dropdown/combobox fields
6
+ - Option list management
7
+ - Visual styling inheritance from text fields
8
+ - Form field integration
9
+ """
3
10
 
4
11
  from .text import TextWidget
5
12
 
6
13
 
7
14
  class DropdownWidget(TextWidget):
8
- """Dropdown widget to create."""
15
+ """Creates and configures PDF dropdown widgets.
16
+
17
+ Extends TextWidget to support dropdown/combobox functionality including:
18
+ - Option list management
19
+ - Initial value selection
20
+ - All inherited text field styling options
21
+
22
+ Class Attributes:
23
+ NONE_DEFAULTS: Empty list - dropdowns require options
24
+ ACRO_FORM_FUNC: Uses underlying text field creation function
25
+ """
9
26
 
10
27
  NONE_DEFAULTS = []
11
28
  ACRO_FORM_FUNC = "_textfield"
@@ -18,7 +35,18 @@ class DropdownWidget(TextWidget):
18
35
  y: float,
19
36
  **kwargs,
20
37
  ) -> None:
21
- """Sets acro form parameters."""
38
+ """Initializes a new dropdown widget with options.
39
+
40
+ Args:
41
+ name: Field name/key for the dropdown
42
+ page_number: Page number to place widget on (1-based)
43
+ x: X coordinate for widget position
44
+ y: Y coordinate for widget position
45
+ **kwargs: Additional widget parameters including:
46
+ width/height: Field dimensions
47
+ font/font_size: Text styling
48
+ options: List of dropdown choices
49
+ """
22
50
 
23
51
  self.USER_PARAMS = super().USER_PARAMS[:-1] + [
24
52
  ("options", "options"),
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Provides radio button widget creation functionality for PDF forms.
3
+
4
+ This module contains the RadioWidget class which handles creation of:
5
+ - Interactive radio button fields for mutually exclusive selections
6
+ - Custom button styles and color styling
7
+ - Size adjustments
8
+ - PDF form field integration
9
+
10
+ Supports all standard PDF radio button properties and integrates with both
11
+ AcroForm and non-AcroForm PDF documents.
12
+ """
13
+
14
+ from typing import List
15
+
16
+ from reportlab.pdfgen.canvas import Canvas
17
+
18
+ from .checkbox import CheckBoxWidget
19
+
20
+
21
+ class RadioWidget(CheckBoxWidget):
22
+ """Creates and configures PDF radio button widgets.
23
+
24
+ Supports all standard radio button properties including:
25
+ - Mutually exclusive selection handling
26
+ - Button style customization and color styling
27
+ - Size adjustments
28
+ - PDF form field integration
29
+
30
+ Inherits from CheckBoxWidget, adding radio-specific parameters and logic.
31
+ """
32
+
33
+ ACRO_FORM_FUNC = "radio"
34
+
35
+ def __init__(
36
+ self,
37
+ name: str,
38
+ page_number: int,
39
+ x: List[float],
40
+ y: List[float],
41
+ **kwargs,
42
+ ) -> None:
43
+ """Initializes a new radio button widget with options.
44
+
45
+ Args:
46
+ name: Field name/key for the radio group.
47
+ page_number: Page number to place widget on (1-based).
48
+ x: List of X coordinates for each radio button.
49
+ y: List of Y coordinates for each radio button.
50
+ **kwargs: Additional widget parameters including:
51
+ width/height: Field dimensions.
52
+ font/font_size: Text styling.
53
+ options: List of radio button choices.
54
+ shape: Button style (e.g., circle, check, cross, etc.).
55
+ color: Button color and border styling.
56
+ """
57
+
58
+ self.USER_PARAMS.append(("shape", "shape"))
59
+ super().__init__(name, page_number, x, y, **kwargs)
60
+
61
+ def canvas_operations(self, canvas: Canvas) -> None:
62
+ """Draws all radio button options on the provided PDF canvas.
63
+
64
+ Iterates over each (x, y) coordinate pair for the radio button group,
65
+ sets the value for each option, and calls the AcroForm radio function
66
+ to render each button on the canvas.
67
+
68
+ Args:
69
+ canvas: The ReportLab Canvas object to draw the radio buttons on.
70
+ """
71
+
72
+ for i, x in enumerate(self.acro_form_params["x"]):
73
+ y = self.acro_form_params["y"][i]
74
+ new_acro_form_params = self.acro_form_params.copy()
75
+ new_acro_form_params["x"] = x
76
+ new_acro_form_params["y"] = y
77
+ new_acro_form_params["value"] = str(i)
78
+ getattr(canvas.acroForm, self.ACRO_FORM_FUNC)(**new_acro_form_params)
PyPDFForm/widgets/text.py CHANGED
@@ -1,11 +1,28 @@
1
1
  # -*- coding: utf-8 -*-
2
- """Contains text field widget to create."""
2
+ """Provides text field widget creation functionality for PDF forms.
3
+
4
+ This module contains the TextWidget class which handles creation of:
5
+ - Standard text input fields
6
+ - Multiline text fields
7
+ - Font and color styling
8
+ - Field size and length constraints
9
+ """
3
10
 
4
11
  from .base import Widget
5
12
 
6
13
 
7
14
  class TextWidget(Widget):
8
- """Text field widget to create."""
15
+ """Creates and configures PDF text field widgets.
16
+
17
+ Supports all standard text field properties including:
18
+ - Font styling (family, size, color)
19
+ - Background and border colors
20
+ - Width/height dimensions
21
+ - Maximum length constraints
22
+ - Alignment and multiline options
23
+
24
+ Inherits from Widget base class adding text-specific parameters.
25
+ """
9
26
 
10
27
  USER_PARAMS = [
11
28
  ("width", "width"),