PyPDFForm 3.5.5__py3-none-any.whl → 3.6.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 CHANGED
@@ -20,7 +20,7 @@ The library supports various PDF form features, including:
20
20
  PyPDFForm aims to simplify PDF form manipulation, making it accessible to developers of all skill levels.
21
21
  """
22
22
 
23
- __version__ = "3.5.5"
23
+ __version__ = "3.6.0"
24
24
 
25
25
  from .middleware.text import Text # exposing for setting global font attrs
26
26
  from .widgets import Fields
@@ -21,6 +21,12 @@ from .text import TextField
21
21
  FieldTypes = Union[
22
22
  TextField, CheckBoxField, RadioGroup, DropdownField, SignatureField, ImageField
23
23
  ]
24
+ BulkFieldTypes = Union[
25
+ TextField,
26
+ CheckBoxField,
27
+ RadioGroup,
28
+ DropdownField,
29
+ ]
24
30
 
25
31
 
26
32
  @dataclass
PyPDFForm/widgets/base.py CHANGED
@@ -12,6 +12,8 @@ Classes:
12
12
  functionality for rendering and manipulation.
13
13
  """
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  from dataclasses import dataclass
16
18
  from inspect import signature
17
19
  from io import BytesIO
@@ -191,6 +193,68 @@ class Widget:
191
193
  for i in range(page_count)
192
194
  ]
193
195
 
196
+ @staticmethod
197
+ def bulk_watermarks(widgets: List[Widget], stream: bytes) -> List[bytes]:
198
+ """
199
+ Generates watermarks for multiple widgets in bulk.
200
+
201
+ This static method processes a list of widgets and a PDF stream to create
202
+ a list of watermark streams, one for each page of the PDF. Widgets are
203
+ grouped by their page number, and all widgets for a given page are drawn
204
+ onto a single ReportLab canvas, which is then returned as the watermark
205
+ stream for that page. This is more efficient than generating watermarks
206
+ for each widget individually.
207
+
208
+ Args:
209
+ widgets (List[Widget]): A list of Widget objects to be watermarked.
210
+ stream (bytes): The PDF stream to be watermarked.
211
+
212
+ Returns:
213
+ List[bytes]: A list of watermark streams (bytes), where the index
214
+ corresponds to the 0-based page index of the original PDF.
215
+ Each element is a byte stream representing the combined
216
+ watermark for that page. Pages without any widgets will
217
+ have an empty byte string (b"").
218
+ """
219
+ result = []
220
+
221
+ pdf = PdfReader(stream_to_io(stream))
222
+ watermark = BytesIO()
223
+
224
+ widgets_by_page = {}
225
+ for widget in widgets:
226
+ if widget.page_number not in widgets_by_page:
227
+ widgets_by_page[widget.page_number] = []
228
+ widgets_by_page[widget.page_number].append(widget)
229
+
230
+ for i, page in enumerate(pdf.pages):
231
+ page_num = i + 1
232
+ if page_num not in widgets_by_page:
233
+ result.append(b"")
234
+ continue
235
+
236
+ watermark.seek(0)
237
+ watermark.flush()
238
+
239
+ canvas = Canvas(
240
+ watermark,
241
+ pagesize=(
242
+ float(page.mediabox[2]),
243
+ float(page.mediabox[3]),
244
+ ),
245
+ )
246
+
247
+ for widget in widgets_by_page[page_num]:
248
+ getattr(widget, "_required_handler")(canvas)
249
+ widget.canvas_operations(canvas)
250
+
251
+ canvas.showPage()
252
+ canvas.save()
253
+ watermark.seek(0)
254
+ result.append(watermark.read())
255
+
256
+ return result
257
+
194
258
 
195
259
  @dataclass
196
260
  class Field:
@@ -11,7 +11,7 @@ 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
 
@@ -57,6 +57,7 @@ class CheckBoxField(Field):
57
57
 
58
58
  Attributes:
59
59
  _field_type (str): The type of the field, fixed as "checkbox".
60
+ _widget_class (Type[Widget]): The widget class associated with this field type.
60
61
  size (Optional[float]): The size of the checkbox.
61
62
  button_style (Optional[str]): The visual style of the checkbox button
62
63
  (e.g., "check", "circle", "cross").
@@ -67,6 +68,7 @@ class CheckBoxField(Field):
67
68
  """
68
69
 
69
70
  _field_type: str = "checkbox"
71
+ _widget_class: Type[Widget] = CheckBoxWidget
70
72
 
71
73
  size: Optional[float] = None
72
74
  button_style: Optional[str] = None
@@ -11,9 +11,9 @@ 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
 
@@ -69,6 +69,7 @@ class DropdownField(Field):
69
69
 
70
70
  Attributes:
71
71
  _field_type (str): The type of the field, fixed as "dropdown".
72
+ _widget_class (Type[Widget]): The widget class associated with this field type.
72
73
  options (Optional[List[Union[str, Tuple[str, str]]]]): A list of options
73
74
  available in the dropdown. Each option can be a string (display value)
74
75
  or a tuple of strings (display value, export value).
@@ -83,6 +84,7 @@ class DropdownField(Field):
83
84
  """
84
85
 
85
86
  _field_type: str = "dropdown"
87
+ _widget_class: Type[Widget] = DropdownWidget
86
88
 
87
89
  options: Optional[List[Union[str, Tuple[str, str]]]] = None
88
90
  width: Optional[float] = None
@@ -11,10 +11,11 @@ specific functionality for interacting with radio button form fields in PDFs.
11
11
  """
12
12
 
13
13
  from dataclasses import dataclass
14
- from typing import List, Optional
14
+ from typing import List, Optional, Type
15
15
 
16
16
  from reportlab.pdfgen.canvas import Canvas
17
17
 
18
+ from .base import Widget
18
19
  from .checkbox import CheckBoxField, CheckBoxWidget
19
20
 
20
21
 
@@ -86,6 +87,7 @@ class RadioGroup(CheckBoxField):
86
87
 
87
88
  Attributes:
88
89
  _field_type (str): The type of the field, fixed as "radio".
90
+ _widget_class (Type[Widget]): The widget class associated with this field type.
89
91
  x (List[float]): A list of x-coordinates for each radio button in the group.
90
92
  y (List[float]): A list of y-coordinates for each radio button in the group.
91
93
  shape (Optional[str]): The shape of the radio button. Valid values are
@@ -93,6 +95,7 @@ class RadioGroup(CheckBoxField):
93
95
  """
94
96
 
95
97
  _field_type: str = "radio"
98
+ _widget_class: Type[Widget] = RadioWidget
96
99
 
97
100
  x: List[float]
98
101
  y: List[float]
PyPDFForm/widgets/text.py CHANGED
@@ -11,7 +11,7 @@ functionality for interacting with text 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
 
@@ -63,6 +63,7 @@ class TextField(Field):
63
63
 
64
64
  Attributes:
65
65
  _field_type (str): The type of the field, fixed as "text".
66
+ _widget_class (Type[Widget]): The widget class associated with this field type.
66
67
  width (Optional[float]): The width of the text field.
67
68
  height (Optional[float]): The height of the text field.
68
69
  max_length (Optional[int]): The maximum number of characters allowed in the text field.
@@ -79,6 +80,7 @@ class TextField(Field):
79
80
  """
80
81
 
81
82
  _field_type: str = "text"
83
+ _widget_class: Type[Widget] = TextWidget
82
84
 
83
85
  width: Optional[float] = None
84
86
  height: Optional[float] = None
PyPDFForm/wrapper.py CHANGED
@@ -18,6 +18,7 @@ underlying PDF manipulation.
18
18
 
19
19
  from __future__ import annotations
20
20
 
21
+ from collections import defaultdict
21
22
  from dataclasses import asdict
22
23
  from functools import cached_property
23
24
  from typing import TYPE_CHECKING, BinaryIO, Dict, List, Sequence, Tuple, Union
@@ -41,6 +42,8 @@ from .utils import (enable_adobe_mode, generate_unique_suffix,
41
42
  get_page_streams, merge_two_pdfs, remove_all_widgets)
42
43
  from .watermark import (copy_watermark_widgets, create_watermarks_and_draw,
43
44
  merge_watermarks_with_pdf)
45
+ from .widgets import CheckBoxField
46
+ from .widgets.base import Widget
44
47
  from .widgets.checkbox import CheckBoxWidget
45
48
  from .widgets.dropdown import DropdownWidget
46
49
  from .widgets.image import ImageWidget
@@ -49,7 +52,7 @@ from .widgets.signature import SignatureWidget
49
52
  from .widgets.text import TextWidget
50
53
 
51
54
  if TYPE_CHECKING:
52
- from .widgets import FieldTypes
55
+ from .widgets import BulkFieldTypes, FieldTypes
53
56
 
54
57
 
55
58
  class PdfWrapper:
@@ -451,6 +454,91 @@ class PdfWrapper:
451
454
 
452
455
  return self
453
456
 
457
+ def bulk_create_fields(self, fields: List[BulkFieldTypes]) -> PdfWrapper:
458
+ """
459
+ Creates multiple new form fields (widgets) on the PDF in a single operation.
460
+
461
+ This method takes a list of field definition objects (`BulkFieldTypes`),
462
+ groups them by type (if necessary for specific widget handling, like CheckBoxField),
463
+ and then delegates the creation to the internal `_bulk_create_fields` method.
464
+ This is the preferred method for creating multiple fields as it minimizes
465
+ PDF manipulation overhead.
466
+
467
+ Args:
468
+ fields (List[BulkFieldTypes]): A list of field definition objects
469
+ (e.g., `TextField`, `CheckBoxField`, etc.) to be created.
470
+
471
+ Returns:
472
+ PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
473
+ """
474
+
475
+ needs_separate_creation = [CheckBoxField]
476
+ needs_separate_creation_dict = defaultdict(list)
477
+ general_creation = []
478
+
479
+ for each in fields:
480
+ if type(each) in needs_separate_creation:
481
+ needs_separate_creation_dict[type(each)].append(each)
482
+ else:
483
+ general_creation.append(each)
484
+
485
+ for each in needs_separate_creation_dict.values():
486
+ self._bulk_create_fields(each)
487
+ self._bulk_create_fields(general_creation)
488
+
489
+ return self
490
+
491
+ def _bulk_create_fields(self, fields: List[BulkFieldTypes]) -> PdfWrapper:
492
+ """
493
+ Internal method to create multiple new form fields (widgets) on the PDF in a single operation.
494
+
495
+ This method takes a list of field definition objects (`BulkFieldTypes`),
496
+ converts them into `Widget` objects, and efficiently draws them onto the
497
+ PDF using bulk watermarking. It is designed to be called by the public
498
+ `bulk_create_fields` method after fields have been grouped for creation.
499
+
500
+ Args:
501
+ fields (List[BulkFieldTypes]): A list of field definition objects
502
+ (e.g., `TextField`, `CheckBoxField`, etc.) to be created.
503
+
504
+ Returns:
505
+ PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
506
+ """
507
+
508
+ widgets = []
509
+ for field in fields:
510
+ field_dict = asdict(field)
511
+ widget_class = getattr(field, "_widget_class")
512
+ name = field_dict.pop("name")
513
+ page_number = field_dict.pop("page_number")
514
+ x = field_dict.pop("x")
515
+ y = field_dict.pop("y")
516
+ widgets.append(
517
+ widget_class(
518
+ name=name,
519
+ page_number=page_number,
520
+ x=x,
521
+ y=y,
522
+ **{k: v for k, v in field_dict.items() if v is not None},
523
+ )
524
+ )
525
+
526
+ watermarks = Widget.bulk_watermarks(widgets, self.read())
527
+ self._stream = copy_watermark_widgets(
528
+ self.read(),
529
+ watermarks,
530
+ [widget.acro_form_params["name"] for widget in widgets],
531
+ None,
532
+ )
533
+
534
+ self._init_helper()
535
+
536
+ for widget in widgets:
537
+ for k, v in widget.hook_params:
538
+ self.widgets[widget.acro_form_params["name"]].__setattr__(k, v)
539
+
540
+ return self
541
+
454
542
  def create_field(
455
543
  self,
456
544
  field: FieldTypes,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 3.5.5
3
+ Version: 3.6.0
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -49,7 +49,7 @@ Dynamic: license-file
49
49
  <a href="https://github.com/chinapandaman/PyPDFForm/actions/workflows/python-package.yml"><img src="https://img.shields.io/badge/coverage-100%25-green"></a>
50
50
  <a href="https://github.com/chinapandaman/PyPDFForm/raw/master/LICENSE"><img src="https://img.shields.io/github/license/chinapandaman/pypdfform?label=license&color=orange"></a>
51
51
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/pypi/pyversions/pypdfform?label=python&color=gold"></a>
52
- <a href="https://pypistats.org/packages/pypdfform"><img src="https://img.shields.io/pypi/dm/pypdfform?color=blue"></a>
52
+ <a href="https://pepy.tech/projects/pypdfform"><img src="https://static.pepy.tech/badge/pypdfform/month"></a>
53
53
  </p>
54
54
 
55
55
  ## Introduction
@@ -1,4 +1,4 @@
1
- PyPDFForm/__init__.py,sha256=NSu4wjk8XAx0SA5LJIMstL6UlHQa7AuvZnFXjjYYco4,963
1
+ PyPDFForm/__init__.py,sha256=CC5j1ui603ZNCwB7vhfBoSkfUothfptPDO9jrhWLbMs,963
2
2
  PyPDFForm/adapter.py,sha256=0OTZBzGYzOerfXzUPvNq2HNVcMmkRnxNHQ1-Ys2VjYA,2427
3
3
  PyPDFForm/constants.py,sha256=tFtomm4bSsghxRqKaMP6Tln7ug9W_72e-AAU3sEU_no,2917
4
4
  PyPDFForm/coordinate.py,sha256=VMVkqa-VAGJSGVvetZwOxeMzIgQtQdvtn_DI_qSecCE,3876
@@ -10,7 +10,7 @@ PyPDFForm/patterns.py,sha256=Xhdsef7pPEIVEYhQRyOzonld3z1wk6WZ3TTjnj-RvpU,9573
10
10
  PyPDFForm/template.py,sha256=GSNMH5meKk5B62sj_UXxiJgs4SOVDcQCNISvARmXJZ0,10004
11
11
  PyPDFForm/utils.py,sha256=hLSVUG6qnE0iTMB-yPNQQIhmm3R69X7fcnbCTDvSUQs,11001
12
12
  PyPDFForm/watermark.py,sha256=9p1tjaIqicXngTNai_iOEkCoXRYnR66azB4s7wNsZUw,9349
13
- PyPDFForm/wrapper.py,sha256=ayqL-p7Ftwg0txMpK8sNxQ0aIoUrPtyPwJEIgej_Ofo,27598
13
+ PyPDFForm/wrapper.py,sha256=8kYjgd18F_27QJXRJnxcZQ6RLyRmpXJHb0BAdtwUviw,30951
14
14
  PyPDFForm/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  PyPDFForm/middleware/base.py,sha256=YQNVhjoDV2E3WYSQrahHkD6_Zdfxpcnr9ONKeB7hcgM,3223
16
16
  PyPDFForm/middleware/checkbox.py,sha256=OCSZEFD8wQG_Y9qO7Os6VXTaxJCpkRYTxI4wDgG0GZc,1870
@@ -19,17 +19,17 @@ PyPDFForm/middleware/image.py,sha256=eKM7anU56jbaECnK6rq0jGsBRY3HW_fM86fgA3hq7xA
19
19
  PyPDFForm/middleware/radio.py,sha256=PuGDJ8RN1C-MkL9Jf14ABWYV67cN18R66dI4nR-03DU,2211
20
20
  PyPDFForm/middleware/signature.py,sha256=a2IfD36zpEWXWNNWRvtJ6nG6TszkF6Wil82Szsbjfns,2149
21
21
  PyPDFForm/middleware/text.py,sha256=WLK6Ae9zT3uUu1AzcWUhR-hs5rm0z9Ydz-fL8Qu-44o,3997
22
- PyPDFForm/widgets/__init__.py,sha256=TvMMWGtrTC4xRPRokr4Pl8ZtvfYXRPUDgfHd3pnbPE8,1304
23
- PyPDFForm/widgets/base.py,sha256=KoYSGkN35iU0RnWT1ZhGQ0I_q_NSFXBvvKculRPSmsc,7876
22
+ PyPDFForm/widgets/__init__.py,sha256=gVJzqdpj6NRltqXrvCeHJO28snlIwHydbftWCnLZnvI,1399
23
+ PyPDFForm/widgets/base.py,sha256=FCFMJPMeqE1z0zNe_IK6kEu8M_sYFy7Tu8SyAMDJ14M,10195
24
24
  PyPDFForm/widgets/bedrock.py,sha256=j6beU04kaQzpAIFZHI5VJLaDT5RVAAa6LzkU1luJpN8,137660
25
- PyPDFForm/widgets/checkbox.py,sha256=BhCKjjh2VP4s6OqW-VrUdfrIcUYhahxeZBYKeVe4MOA,2870
26
- PyPDFForm/widgets/dropdown.py,sha256=A55KfFR2Tqh2EvV0P2G1_KYYDeQ0KGMP8tGm4OHAKws,3528
25
+ PyPDFForm/widgets/checkbox.py,sha256=QQH9MURhyASDss83uWRvD8R7uyZ1D9fxtLnpNCVFc10,3013
26
+ PyPDFForm/widgets/dropdown.py,sha256=jIRefrCqUZy9MJxeMlAnreclnSeai6YflsNDuRzim-o,3679
27
27
  PyPDFForm/widgets/image.py,sha256=t-nHD38hF_wgHCAeBliqedbHX94k331L4HtKYhxkOaY,1628
28
- PyPDFForm/widgets/radio.py,sha256=za7vwYC-1JnUhnpro3xqOqRDzdBQfKuSOBvZyOoog54,3581
28
+ PyPDFForm/widgets/radio.py,sha256=ip1imdGQhbQ6GO57Z8HNrzQkQH7SEq6K-azS949lMkU,3746
29
29
  PyPDFForm/widgets/signature.py,sha256=-hc3bvU0zLPnla0CLzrnMAkJkPo_Nce_VO8UdeDUH90,5247
30
- PyPDFForm/widgets/text.py,sha256=n5kvI4u1wg1NWSyaSw450pkv4lR6-ZFg2of9OobZZpE,3836
31
- pypdfform-3.5.5.dist-info/licenses/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
32
- pypdfform-3.5.5.dist-info/METADATA,sha256=mKDGDNLtjyQZ2yFehUHdOLXcEYFTY_VpGTfUvQiWOeY,4278
33
- pypdfform-3.5.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- pypdfform-3.5.5.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
35
- pypdfform-3.5.5.dist-info/RECORD,,
30
+ PyPDFForm/widgets/text.py,sha256=RJXPLCopmuayKJSvVeDVaiMRvqOjFoFwqiEzgyydIhg,3975
31
+ pypdfform-3.6.0.dist-info/licenses/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
32
+ pypdfform-3.6.0.dist-info/METADATA,sha256=3w5_arGBmdkCiaWFjWYwLlDv-E3D8XFY5N2sh2_KtM4,4269
33
+ pypdfform-3.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ pypdfform-3.6.0.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
35
+ pypdfform-3.6.0.dist-info/RECORD,,