PyPDFForm 3.5.3__py3-none-any.whl → 4.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.
Files changed (44) hide show
  1. PyPDFForm/__init__.py +5 -3
  2. PyPDFForm/adapter.py +33 -1
  3. PyPDFForm/ap.py +99 -0
  4. PyPDFForm/assets/__init__.py +0 -0
  5. PyPDFForm/assets/blank.py +100 -0
  6. PyPDFForm/constants.py +20 -2
  7. PyPDFForm/coordinate.py +7 -11
  8. PyPDFForm/deprecation.py +30 -0
  9. PyPDFForm/filler.py +17 -36
  10. PyPDFForm/font.py +16 -16
  11. PyPDFForm/hooks.py +153 -30
  12. PyPDFForm/image.py +0 -3
  13. PyPDFForm/middleware/__init__.py +35 -0
  14. PyPDFForm/middleware/base.py +24 -5
  15. PyPDFForm/middleware/checkbox.py +18 -1
  16. PyPDFForm/middleware/signature.py +0 -1
  17. PyPDFForm/patterns.py +44 -13
  18. PyPDFForm/raw/__init__.py +37 -0
  19. PyPDFForm/raw/circle.py +65 -0
  20. PyPDFForm/raw/ellipse.py +69 -0
  21. PyPDFForm/raw/image.py +79 -0
  22. PyPDFForm/raw/line.py +65 -0
  23. PyPDFForm/raw/rect.py +70 -0
  24. PyPDFForm/raw/text.py +73 -0
  25. PyPDFForm/template.py +114 -12
  26. PyPDFForm/types.py +49 -0
  27. PyPDFForm/utils.py +31 -41
  28. PyPDFForm/watermark.py +153 -44
  29. PyPDFForm/widgets/__init__.py +1 -0
  30. PyPDFForm/widgets/base.py +79 -59
  31. PyPDFForm/widgets/checkbox.py +30 -30
  32. PyPDFForm/widgets/dropdown.py +42 -40
  33. PyPDFForm/widgets/image.py +17 -16
  34. PyPDFForm/widgets/radio.py +27 -28
  35. PyPDFForm/widgets/signature.py +96 -60
  36. PyPDFForm/widgets/text.py +40 -40
  37. PyPDFForm/wrapper.py +256 -240
  38. {pypdfform-3.5.3.dist-info → pypdfform-4.2.0.dist-info}/METADATA +33 -26
  39. pypdfform-4.2.0.dist-info/RECORD +47 -0
  40. {pypdfform-3.5.3.dist-info → pypdfform-4.2.0.dist-info}/licenses/LICENSE +1 -1
  41. pypdfform-3.5.3.dist-info/RECORD +0 -35
  42. /PyPDFForm/{widgets → assets}/bedrock.py +0 -0
  43. {pypdfform-3.5.3.dist-info → pypdfform-4.2.0.dist-info}/WHEEL +0 -0
  44. {pypdfform-3.5.3.dist-info → pypdfform-4.2.0.dist-info}/top_level.txt +0 -0
PyPDFForm/watermark.py CHANGED
@@ -7,14 +7,8 @@ It supports drawing text, lines, and images as watermarks.
7
7
  The module also includes functions to merge these watermarks with the original PDF content
8
8
  and to copy specific widgets from the watermarks to the original PDF.
9
9
  """
10
- # TODO: In `draw_image`, `ImageReader(image_buff)` is created for each image. If the same image is drawn multiple times, consider caching `ImageReader` objects or passing pre-processed image data to avoid redundant processing.
11
- # TODO: In `create_watermarks_and_draw`, `PdfReader(stream_to_io(pdf))` is called, which re-parses the PDF. If this function is called repeatedly for the same PDF, consider passing the `PdfReader` object directly to avoid redundant parsing.
12
- # TODO: In `create_watermarks_and_draw`, the function returns a list of watermarks where only one element is populated. This can be inefficient for memory if there are many pages but only one watermark is created. Consider returning only the created watermark and its page number, and let the caller handle placement.
13
- # TODO: In `merge_watermarks_with_pdf`, `PdfReader(stream_to_io(pdf))` and `PdfReader(stream_to_io(watermarks[i]))` are called in a loop. This leads to repeated parsing of the base PDF and each watermark. It would be more efficient to parse the base PDF once and then merge watermark pages directly into the existing `PdfWriter` object.
14
- # TODO: In `copy_watermark_widgets`, the function reads the PDF and watermarks multiple times. Similar to `merge_watermarks_with_pdf`, optimize by parsing the base PDF and watermarks once and then manipulating the `PdfWriter` object.
15
- # TODO: The `copy_watermark_widgets` function has a `TODO: refactor duplicate logic with merge_two_pdfs` comment. This indicates a potential for code duplication and inefficiency. Refactoring this to a shared helper function would improve maintainability and potentially performance.
16
- # TODO: In `copy_watermark_widgets`, the nested loops iterating through `watermarks`, `watermark_file.pages`, and `page.get(Annots, [])` can be very inefficient for large numbers of watermarks, pages, or annotations. Consider creating a lookup structure for annotations by key to avoid repeated linear scans.
17
10
 
11
+ from collections import defaultdict
18
12
  from io import BytesIO
19
13
  from typing import List, Union
20
14
 
@@ -84,6 +78,112 @@ def draw_line(canvas: Canvas, **kwargs) -> None:
84
78
  canvas.line(src_x, src_y, dest_x, dest_y)
85
79
 
86
80
 
81
+ def draw_rect(canvas: Canvas, **kwargs) -> None:
82
+ """
83
+ Draws a rectangle on the given canvas with the specified coordinates, dimensions, and color.
84
+
85
+ Args:
86
+ canvas (Canvas): The ReportLab Canvas object to draw on.
87
+ **kwargs: Keyword arguments containing the rectangle's properties and coordinates.
88
+ - x (float): The x-coordinate of the rectangle's bottom-left corner.
89
+ - y (float): The y-coordinate of the rectangle's bottom-left corner.
90
+ - width (float): The width of the rectangle.
91
+ - height (float): The height of the rectangle.
92
+ - color (tuple): A tuple representing the RGB color of the rectangle's outline.
93
+ - fill_color (tuple): A tuple representing the RGB color of the rectangle's fill.
94
+
95
+ Returns:
96
+ None
97
+ """
98
+ x = kwargs["x"]
99
+ y = kwargs["y"]
100
+ width = kwargs["width"]
101
+ height = kwargs["height"]
102
+ color = kwargs["color"]
103
+ fill_color = kwargs["fill_color"]
104
+
105
+ canvas.setStrokeColorRGB(*(color))
106
+
107
+ fill = 0
108
+ canvas.saveState()
109
+ if fill_color:
110
+ canvas.setFillColorRGB(*(fill_color))
111
+ fill = 1
112
+
113
+ canvas.rect(x, y, width, height, fill=fill)
114
+ canvas.restoreState()
115
+
116
+
117
+ def draw_circle(canvas: Canvas, **kwargs) -> None:
118
+ """
119
+ Draws a circle on the given canvas with the specified center coordinates, radius, and color.
120
+
121
+ Args:
122
+ canvas (Canvas): The ReportLab Canvas object to draw on.
123
+ **kwargs: Keyword arguments containing the circle's properties and coordinates.
124
+ - center_x (float): The x-coordinate of the circle's center.
125
+ - center_y (float): The y-coordinate of the circle's center.
126
+ - radius (float): The radius of the circle.
127
+ - color (tuple): A tuple representing the RGB color of the circle's outline.
128
+ - fill_color (tuple): A tuple representing the RGB color of the circle's fill.
129
+
130
+ Returns:
131
+ None
132
+ """
133
+ center_x = kwargs["center_x"]
134
+ center_y = kwargs["center_y"]
135
+ radius = kwargs["radius"]
136
+ color = kwargs["color"]
137
+ fill_color = kwargs["fill_color"]
138
+
139
+ canvas.setStrokeColorRGB(*(color))
140
+
141
+ fill = 0
142
+ canvas.saveState()
143
+ if fill_color:
144
+ canvas.setFillColorRGB(*(fill_color))
145
+ fill = 1
146
+
147
+ canvas.circle(center_x, center_y, radius, fill=fill)
148
+ canvas.restoreState()
149
+
150
+
151
+ def draw_ellipse(canvas: Canvas, **kwargs) -> None:
152
+ """
153
+ Draws an ellipse on the given canvas defined by its bounding box coordinates and color.
154
+
155
+ Args:
156
+ canvas (Canvas): The ReportLab Canvas object to draw on.
157
+ **kwargs: Keyword arguments containing the ellipse's properties and coordinates.
158
+ - x1 (float): The x-coordinate of the first corner of the bounding box.
159
+ - y1 (float): The y-coordinate of the first corner of the bounding box.
160
+ - x2 (float): The x-coordinate of the second corner of the bounding box.
161
+ - y2 (float): The y-coordinate of the second corner of the bounding box.
162
+ - color (tuple): A tuple representing the RGB color of the ellipse's outline.
163
+ - fill_color (tuple): A tuple representing the RGB color of the ellipse's fill.
164
+
165
+ Returns:
166
+ None
167
+ """
168
+ x1 = kwargs["x1"]
169
+ y1 = kwargs["y1"]
170
+ x2 = kwargs["x2"]
171
+ y2 = kwargs["y2"]
172
+ color = kwargs["color"]
173
+ fill_color = kwargs["fill_color"]
174
+
175
+ canvas.setStrokeColorRGB(*(color))
176
+
177
+ fill = 0
178
+ canvas.saveState()
179
+ if fill_color:
180
+ canvas.setFillColorRGB(*(fill_color))
181
+ fill = 1
182
+
183
+ canvas.ellipse(x1, y1, x2, y2, fill=fill)
184
+ canvas.restoreState()
185
+
186
+
87
187
  def draw_image(canvas: Canvas, **kwargs) -> None:
88
188
  """
89
189
  Draws an image on the given canvas, scaling it to fit within the specified width and height.
@@ -122,58 +222,67 @@ def draw_image(canvas: Canvas, **kwargs) -> None:
122
222
  image_buff.close()
123
223
 
124
224
 
125
- def create_watermarks_and_draw(
126
- pdf: bytes,
127
- page_number: int,
128
- action_type: str,
129
- actions: List[dict],
130
- ) -> List[bytes]:
225
+ def create_watermarks_and_draw(pdf: bytes, to_draw: List[dict]) -> List[bytes]:
131
226
  """
132
- Creates watermarks for a specific page in the PDF based on the provided actions and draws them.
227
+ Creates a watermark PDF for each page of the input PDF based on the drawing instructions.
133
228
 
134
- This function takes a PDF file, a page number, an action type, and a list of actions as input.
135
- It then creates a watermark for the specified page by drawing the specified actions on a Canvas object.
229
+ This function reads the input PDF to determine page sizes, then uses ReportLab
230
+ to create a separate, single-page PDF (a watermark) for each page that has
231
+ drawing instructions.
136
232
 
137
233
  Args:
138
- pdf (bytes): The PDF file as a byte stream.
139
- page_number (int): The page number to which the watermark should be applied (1-indexed).
140
- action_type (str): The type of action to perform when creating the watermark (e.g., "image", "text", "line").
141
- actions (List[dict]): A list of dictionaries, where each dictionary represents an action to be performed on the watermark.
234
+ pdf (bytes): The original PDF file as a byte stream.
235
+ to_draw (List[dict]): A list of drawing instructions, where each dictionary
236
+ must contain a "page_number" key (1-based) and a "type" key ("image", "text", or "line")
237
+ along with type-specific parameters.
142
238
 
143
239
  Returns:
144
- List[bytes]: A list of byte streams, where the element at index (page_number - 1) contains the watermark for the specified page,
145
- and all other elements are empty byte streams.
240
+ List[bytes]: A list of watermark PDF byte streams. An empty byte string (b"")
241
+ is used for pages without any drawing instructions.
146
242
  """
147
- pdf_file = PdfReader(stream_to_io(pdf))
148
- buff = BytesIO()
149
-
150
- canvas = Canvas(
151
- buff,
152
- pagesize=(
153
- float(pdf_file.pages[page_number - 1].mediabox[2]),
154
- float(pdf_file.pages[page_number - 1].mediabox[3]),
155
- ),
156
- )
157
-
158
- action_type_to_func = {
243
+ type_to_func = {
159
244
  "image": draw_image,
160
245
  "text": draw_text,
161
246
  "line": draw_line,
247
+ "rect": draw_rect,
248
+ "circle": draw_circle,
249
+ "ellipse": draw_ellipse,
162
250
  }
163
251
 
164
- if action_type_to_func.get(action_type):
165
- for each in actions:
166
- action_type_to_func[action_type](canvas, **each)
252
+ result = []
253
+
254
+ page_to_to_draw = defaultdict(list)
255
+ for each in to_draw:
256
+ page_to_to_draw[each["page_number"]].append(each)
257
+
258
+ pdf_file = PdfReader(stream_to_io(pdf))
259
+ buff = BytesIO()
260
+
261
+ for i, page in enumerate(pdf_file.pages):
262
+ elements = page_to_to_draw[i + 1]
263
+ if not elements:
264
+ result.append(b"")
265
+ continue
266
+
267
+ buff.seek(0)
268
+ buff.flush()
269
+
270
+ canvas = Canvas(
271
+ buff,
272
+ pagesize=(
273
+ float(page.mediabox[2]),
274
+ float(page.mediabox[3]),
275
+ ),
276
+ )
167
277
 
168
- canvas.save()
169
- buff.seek(0)
278
+ for element in elements:
279
+ type_to_func[element["type"]](canvas, **element)
170
280
 
171
- watermark = buff.read()
172
- buff.close()
281
+ canvas.save()
282
+ buff.seek(0)
283
+ result.append(buff.read())
173
284
 
174
- return [
175
- watermark if i == page_number - 1 else b"" for i in range(len(pdf_file.pages))
176
- ]
285
+ return result
177
286
 
178
287
 
179
288
  def merge_watermarks_with_pdf(
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  """
2
3
  The `widgets` package provides a collection of classes representing various types of PDF form fields (widgets).
3
4
 
PyPDFForm/widgets/base.py CHANGED
@@ -12,8 +12,7 @@ Classes:
12
12
  functionality for rendering and manipulation.
13
13
  """
14
14
 
15
- # TODO: In `watermarks`, `PdfReader(stream_to_io(stream))` is called, which re-parses the PDF for each widget. If multiple widgets are being processed, consider passing the `PdfReader` object directly to avoid redundant parsing.
16
- # TODO: In `watermarks`, the list comprehension `[watermark.read() if i == self.page_number - 1 else b"" for i in range(page_count)]` creates a new `BytesIO` object and reads from it for each widget. If many widgets are created, this could be optimized by creating the `BytesIO` object once and passing it around, or by directly returning the watermark bytes and its page number.
15
+ from __future__ import annotations
17
16
 
18
17
  from dataclasses import dataclass
19
18
  from inspect import signature
@@ -28,35 +27,6 @@ from ..constants import fieldFlags, required
28
27
  from ..utils import stream_to_io
29
28
 
30
29
 
31
- @dataclass
32
- class Field:
33
- """
34
- Base dataclass for all PDF form fields.
35
-
36
- This class defines the common properties that all types of form fields
37
- (e.g., text fields, checkboxes, radio buttons) share. Specific field types
38
- will extend this class to add their unique attributes.
39
-
40
- Attributes:
41
- name (str): The name of the form field. This is used to identify the
42
- field within the PDF document.
43
- page_number (int): The 1-based page number on which the field is located.
44
- x (float): The x-coordinate of the field's position on the page.
45
- y (float): The y-coordinate of the field's position on the page.
46
- required (Optional[bool]): Indicates whether the field is required to be
47
- filled by the user. Defaults to None, meaning not explicitly set.
48
- tooltip (Optional[str]): A tooltip message that appears when the user
49
- hovers over the field. Defaults to None.
50
- """
51
-
52
- name: str
53
- page_number: int
54
- x: float
55
- y: float
56
- required: Optional[bool] = None
57
- tooltip: Optional[str] = None
58
-
59
-
60
30
  class Widget:
61
31
  """
62
32
  Base class for all widgets in PyPDFForm.
@@ -106,6 +76,7 @@ class Widget:
106
76
  """
107
77
  super().__init__()
108
78
  self.page_number = page_number
79
+ self.name = name
109
80
  self.acro_form_params = {
110
81
  "name": name,
111
82
  "x": x,
@@ -181,44 +152,93 @@ class Widget:
181
152
  """
182
153
  getattr(canvas.acroForm, self.ACRO_FORM_FUNC)(**self.acro_form_params)
183
154
 
184
- def watermarks(self, stream: bytes) -> List[bytes]:
155
+ @staticmethod
156
+ def bulk_watermarks(widgets: List[Widget], stream: bytes) -> List[bytes]:
185
157
  """
186
- Generates watermarks for the widget.
158
+ Generates watermarks for multiple widgets in bulk.
187
159
 
188
- This method takes a PDF stream as input and generates watermarks for each
189
- page of the PDF. The watermark is created by drawing the widget on a
190
- ReportLab canvas and then embedding the canvas as a watermark on the
191
- specified page.
160
+ This static method processes a list of widgets and a PDF stream to create
161
+ a list of watermark streams, one for each page of the PDF. Widgets are
162
+ grouped by their page number, and all widgets for a given page are drawn
163
+ onto a single ReportLab canvas, which is then returned as the watermark
164
+ stream for that page. This is more efficient than generating watermarks
165
+ for each widget individually.
192
166
 
193
167
  Args:
194
- stream (bytes): PDF stream.
168
+ widgets (List[Widget]): A list of Widget objects to be watermarked.
169
+ stream (bytes): The PDF stream to be watermarked.
195
170
 
196
171
  Returns:
197
- List[bytes]: List of watermarks for each page. Each element in the list
198
- is a byte stream representing the watermark for that page.
199
- If a page does not need a watermark, the corresponding
200
- element will be an empty byte string.
172
+ List[bytes]: A list of watermark streams (bytes), where the index
173
+ corresponds to the 0-based page index of the original PDF.
174
+ Each element is a byte stream representing the combined
175
+ watermark for that page. Pages without any widgets will
176
+ have an empty byte string (b"").
201
177
  """
178
+ result = []
179
+
202
180
  pdf = PdfReader(stream_to_io(stream))
203
- page_count = len(pdf.pages)
204
181
  watermark = BytesIO()
205
182
 
206
- canvas = Canvas(
207
- watermark,
208
- pagesize=(
209
- float(pdf.pages[self.page_number - 1].mediabox[2]),
210
- float(pdf.pages[self.page_number - 1].mediabox[3]),
211
- ),
212
- )
183
+ widgets_by_page = {}
184
+ for widget in widgets:
185
+ if widget.page_number not in widgets_by_page:
186
+ widgets_by_page[widget.page_number] = []
187
+ widgets_by_page[widget.page_number].append(widget)
188
+
189
+ for i, page in enumerate(pdf.pages):
190
+ page_num = i + 1
191
+ if page_num not in widgets_by_page:
192
+ result.append(b"")
193
+ continue
213
194
 
214
- self._required_handler(canvas)
215
- self.canvas_operations(canvas)
195
+ watermark.seek(0)
196
+ watermark.flush()
216
197
 
217
- canvas.showPage()
218
- canvas.save()
219
- watermark.seek(0)
198
+ canvas = Canvas(
199
+ watermark,
200
+ pagesize=(
201
+ float(page.mediabox[2]),
202
+ float(page.mediabox[3]),
203
+ ),
204
+ )
220
205
 
221
- return [
222
- watermark.read() if i == self.page_number - 1 else b""
223
- for i in range(page_count)
224
- ]
206
+ for widget in widgets_by_page[page_num]:
207
+ getattr(widget, "_required_handler")(canvas)
208
+ widget.canvas_operations(canvas)
209
+
210
+ canvas.showPage()
211
+ canvas.save()
212
+ watermark.seek(0)
213
+ result.append(watermark.read())
214
+
215
+ return result
216
+
217
+
218
+ @dataclass
219
+ class Field:
220
+ """
221
+ Base dataclass for all PDF form fields.
222
+
223
+ This class defines the common properties that all types of form fields
224
+ (e.g., text fields, checkboxes, radio buttons) share. Specific field types
225
+ will extend this class to add their unique attributes.
226
+
227
+ Attributes:
228
+ name (str): The name of the form field. This is used to identify the
229
+ field within the PDF document.
230
+ page_number (int): The 1-based page number on which the field is located.
231
+ x (float): The x-coordinate of the field's position on the page.
232
+ y (float): The y-coordinate of the field's position on the page.
233
+ required (Optional[bool]): Indicates whether the field is required to be
234
+ filled by the user. Defaults to None, meaning not explicitly set.
235
+ tooltip (Optional[str]): A tooltip message that appears when the user
236
+ hovers over the field. Defaults to None.
237
+ """
238
+
239
+ name: str
240
+ page_number: int
241
+ x: float
242
+ y: float
243
+ required: Optional[bool] = None
244
+ tooltip: Optional[str] = None
@@ -11,40 +11,11 @@ functionality for interacting with checkbox form fields in PDFs.
11
11
  """
12
12
 
13
13
  from dataclasses import dataclass
14
- from typing import Optional, Tuple
14
+ from typing import Optional, Tuple, Type
15
15
 
16
16
  from .base import Field, Widget
17
17
 
18
18
 
19
- @dataclass
20
- class CheckBoxField(Field):
21
- """
22
- Represents a checkbox field in a PDF document.
23
-
24
- This dataclass extends the `Field` base class and defines the specific
25
- attributes that can be configured for a checkbox field.
26
-
27
- Attributes:
28
- _field_type (str): The type of the field, fixed as "checkbox".
29
- size (Optional[float]): The size of the checkbox.
30
- button_style (Optional[str]): The visual style of the checkbox button
31
- (e.g., "check", "circle", "cross").
32
- tick_color (Optional[Tuple[float, ...]]): The color of the checkmark or tick.
33
- bg_color (Optional[Tuple[float, ...]]): The background color of the checkbox.
34
- border_color (Optional[Tuple[float, ...]]): The color of the checkbox's border.
35
- border_width (Optional[float]): The width of the checkbox's border.
36
- """
37
-
38
- _field_type: str = "checkbox"
39
-
40
- size: Optional[float] = None
41
- button_style: Optional[str] = None
42
- tick_color: Optional[Tuple[float, ...]] = None
43
- bg_color: Optional[Tuple[float, ...]] = None
44
- border_color: Optional[Tuple[float, ...]] = None
45
- border_width: Optional[float] = None
46
-
47
-
48
19
  class CheckBoxWidget(Widget):
49
20
  """
50
21
  Represents a checkbox widget in a PDF form.
@@ -74,3 +45,32 @@ class CheckBoxWidget(Widget):
74
45
  COLOR_PARAMS = ["tick_color", "bg_color", "border_color"]
75
46
  ALLOWED_HOOK_PARAMS = ["size"]
76
47
  ACRO_FORM_FUNC = "checkbox"
48
+
49
+
50
+ @dataclass
51
+ class CheckBoxField(Field):
52
+ """
53
+ Represents a checkbox field in a PDF document.
54
+
55
+ This dataclass extends the `Field` base class and defines the specific
56
+ attributes that can be configured for a checkbox field.
57
+
58
+ Attributes:
59
+ _widget_class (Type[Widget]): The widget class associated with this field type.
60
+ size (Optional[float]): The size of the checkbox.
61
+ button_style (Optional[str]): The visual style of the checkbox button
62
+ (e.g., "check", "circle", "cross").
63
+ tick_color (Optional[Tuple[float, ...]]): The color of the checkmark or tick.
64
+ bg_color (Optional[Tuple[float, ...]]): The background color of the checkbox.
65
+ border_color (Optional[Tuple[float, ...]]): The color of the checkbox's border.
66
+ border_width (Optional[float]): The width of the checkbox's border.
67
+ """
68
+
69
+ _widget_class: Type[Widget] = CheckBoxWidget
70
+
71
+ size: Optional[float] = None
72
+ button_style: Optional[str] = None
73
+ tick_color: Optional[Tuple[float, ...]] = None
74
+ bg_color: Optional[Tuple[float, ...]] = None
75
+ border_color: Optional[Tuple[float, ...]] = None
76
+ border_width: Optional[float] = None
@@ -11,49 +11,12 @@ specific functionality for interacting with dropdown form fields in PDFs.
11
11
  """
12
12
 
13
13
  from dataclasses import dataclass
14
- from typing import List, Optional, Tuple, Union
14
+ from typing import List, Optional, Tuple, Type, Union
15
15
 
16
- from .base import Field
16
+ from .base import Field, Widget
17
17
  from .text import TextWidget
18
18
 
19
19
 
20
- @dataclass
21
- class DropdownField(Field):
22
- """
23
- Represents a dropdown field in a PDF document.
24
-
25
- This dataclass extends the `Field` base class and defines the specific
26
- attributes that can be configured for a dropdown selection field.
27
-
28
- Attributes:
29
- _field_type (str): The type of the field, fixed as "dropdown".
30
- options (Optional[List[Union[str, Tuple[str, str]]]]): A list of options
31
- available in the dropdown. Each option can be a string (display value)
32
- or a tuple of strings (display value, export value).
33
- width (Optional[float]): The width of the dropdown field.
34
- height (Optional[float]): The height of the dropdown field.
35
- font (Optional[str]): The font to use for the dropdown text.
36
- font_size (Optional[float]): The font size for the dropdown text.
37
- font_color (Optional[Tuple[float, ...]]): The color of the font as an RGB or RGBA tuple.
38
- bg_color (Optional[Tuple[float, ...]]): The background color of the dropdown field.
39
- border_color (Optional[Tuple[float, ...]]): The color of the dropdown's border.
40
- border_width (Optional[float]): The width of the dropdown's border.
41
- """
42
-
43
- _field_type: str = "dropdown"
44
-
45
- options: Optional[List[Union[str, Tuple[str, str]]]] = None
46
- width: Optional[float] = None
47
- height: Optional[float] = None
48
- # pylint: disable=R0801
49
- font: Optional[str] = None
50
- font_size: Optional[float] = None
51
- font_color: Optional[Tuple[float, ...]] = None
52
- bg_color: Optional[Tuple[float, ...]] = None
53
- border_color: Optional[Tuple[float, ...]] = None
54
- border_width: Optional[float] = None
55
-
56
-
57
20
  class DropdownWidget(TextWidget):
58
21
  """
59
22
  Represents a dropdown widget in a PDF form.
@@ -93,4 +56,43 @@ class DropdownWidget(TextWidget):
93
56
  ]
94
57
  super().__init__(name, page_number, x, y, **kwargs)
95
58
  self.acro_form_params["wkind"] = "choice"
96
- self.acro_form_params["value"] = self.acro_form_params["options"][0]
59
+ self.acro_form_params["value"] = (
60
+ self.acro_form_params["options"][0] or " " # reportlab bug
61
+ )
62
+
63
+
64
+ @dataclass
65
+ class DropdownField(Field):
66
+ """
67
+ Represents a dropdown field in a PDF document.
68
+
69
+ This dataclass extends the `Field` base class and defines the specific
70
+ attributes that can be configured for a dropdown selection field.
71
+
72
+ Attributes:
73
+ _widget_class (Type[Widget]): The widget class associated with this field type.
74
+ options (Optional[List[Union[str, Tuple[str, str]]]]): A list of options
75
+ available in the dropdown. Each option can be a string (display value)
76
+ or a tuple of strings (display value, export value).
77
+ width (Optional[float]): The width of the dropdown field.
78
+ height (Optional[float]): The height of the dropdown field.
79
+ font (Optional[str]): The font to use for the dropdown text.
80
+ font_size (Optional[float]): The font size for the dropdown text.
81
+ font_color (Optional[Tuple[float, ...]]): The color of the font as an RGB or RGBA tuple.
82
+ bg_color (Optional[Tuple[float, ...]]): The background color of the dropdown field.
83
+ border_color (Optional[Tuple[float, ...]]): The color of the dropdown's border.
84
+ border_width (Optional[float]): The width of the dropdown's border.
85
+ """
86
+
87
+ _widget_class: Type[Widget] = DropdownWidget
88
+
89
+ options: Optional[List[Union[str, Tuple[str, str]]]] = None
90
+ width: Optional[float] = None
91
+ height: Optional[float] = None
92
+ # pylint: disable=R0801
93
+ font: Optional[str] = None
94
+ font_size: Optional[float] = None
95
+ font_color: Optional[Tuple[float, ...]] = None
96
+ bg_color: Optional[Tuple[float, ...]] = None
97
+ border_color: Optional[Tuple[float, ...]] = None
98
+ border_width: Optional[float] = None
@@ -12,26 +12,11 @@ leveraging the existing infrastructure for positioning and rendering.
12
12
  """
13
13
 
14
14
  from dataclasses import dataclass
15
+ from typing import Type
15
16
 
16
17
  from .signature import SignatureField, SignatureWidget
17
18
 
18
19
 
19
- @dataclass
20
- class ImageField(SignatureField):
21
- """
22
- Represents an image field in a PDF document.
23
-
24
- This dataclass extends the `SignatureField` base class and defines the
25
- specific attributes for an image input field. It inherits `width` and
26
- `height` from `SignatureField` as images also have dimensions.
27
-
28
- Attributes:
29
- _field_type (str): The type of the field, fixed as "image".
30
- """
31
-
32
- _field_type: str = "image"
33
-
34
-
35
20
  class ImageWidget(SignatureWidget):
36
21
  """
37
22
  Represents an image widget in a PDF form.
@@ -47,3 +32,19 @@ class ImageWidget(SignatureWidget):
47
32
  """
48
33
 
49
34
  BEDROCK_WIDGET_TO_COPY = "image"
35
+
36
+
37
+ @dataclass
38
+ class ImageField(SignatureField):
39
+ """
40
+ Represents an image field in a PDF document.
41
+
42
+ This dataclass extends the `SignatureField` base class and defines the
43
+ specific attributes for an image input field. It inherits `width` and
44
+ `height` from `SignatureField` as images also have dimensions.
45
+
46
+ Attributes:
47
+ _widget_class (Type[ImageWidget]): The widget class associated with this field type.
48
+ """
49
+
50
+ _widget_class: Type[ImageWidget] = ImageWidget