PyPDFForm 3.5.1__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 +169 -31
  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 +71 -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 -10
  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.1.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.1.dist-info → pypdfform-4.2.0.dist-info}/licenses/LICENSE +1 -1
  41. pypdfform-3.5.1.dist-info/RECORD +0 -35
  42. /PyPDFForm/{widgets → assets}/bedrock.py +0 -0
  43. {pypdfform-3.5.1.dist-info → pypdfform-4.2.0.dist-info}/WHEEL +0 -0
  44. {pypdfform-3.5.1.dist-info → pypdfform-4.2.0.dist-info}/top_level.txt +0 -0
@@ -10,41 +10,15 @@ The `RadioWidget` class extends the base `CheckBoxWidget` class to provide
10
10
  specific functionality for interacting with radio button form fields in PDFs.
11
11
  """
12
12
 
13
- # TODO: In `canvas_operations`, `self.acro_form_params.copy()` creates a shallow copy of the dictionary in each iteration of the loop. For a large number of radio buttons, this repeated copying can be inefficient. Consider modifying the dictionary in place and then reverting changes if necessary, or restructuring the data to avoid repeated copying.
14
-
15
13
  from dataclasses import dataclass
16
- from typing import List, Optional
14
+ from typing import List, Optional, Type
17
15
 
18
16
  from reportlab.pdfgen.canvas import Canvas
19
17
 
18
+ from .base import Widget
20
19
  from .checkbox import CheckBoxField, CheckBoxWidget
21
20
 
22
21
 
23
- @dataclass
24
- class RadioGroup(CheckBoxField):
25
- """
26
- Represents a group of radio buttons in a PDF document.
27
-
28
- This dataclass extends the `CheckBoxField` base class and defines the specific
29
- attributes that can be configured for a radio button group. Unlike a single
30
- checkbox, a radio group allows for multiple positions (x, y coordinates)
31
- where individual radio buttons can be placed, but only one can be selected.
32
-
33
- Attributes:
34
- _field_type (str): The type of the field, fixed as "radio".
35
- x (List[float]): A list of x-coordinates for each radio button in the group.
36
- y (List[float]): A list of y-coordinates for each radio button in the group.
37
- shape (Optional[str]): The shape of the radio button. Valid values are
38
- "circle" or "square". Defaults to None, which typically means a default circle shape.
39
- """
40
-
41
- _field_type: str = "radio"
42
-
43
- x: List[float]
44
- y: List[float]
45
- shape: Optional[str] = None
46
-
47
-
48
22
  class RadioWidget(CheckBoxWidget):
49
23
  """
50
24
  Represents a radio button widget in a PDF form.
@@ -99,3 +73,28 @@ class RadioWidget(CheckBoxWidget):
99
73
  new_acro_form_params["y"] = y
100
74
  new_acro_form_params["value"] = str(i)
101
75
  getattr(canvas.acroForm, self.ACRO_FORM_FUNC)(**new_acro_form_params)
76
+
77
+
78
+ @dataclass
79
+ class RadioGroup(CheckBoxField):
80
+ """
81
+ Represents a group of radio buttons in a PDF document.
82
+
83
+ This dataclass extends the `CheckBoxField` base class and defines the specific
84
+ attributes that can be configured for a radio button group. Unlike a single
85
+ checkbox, a radio group allows for multiple positions (x, y coordinates)
86
+ where individual radio buttons can be placed, but only one can be selected.
87
+
88
+ Attributes:
89
+ _widget_class (Type[Widget]): The widget class associated with this field type.
90
+ x (List[float]): A list of x-coordinates for each radio button in the group.
91
+ y (List[float]): A list of y-coordinates for each radio button in the group.
92
+ shape (Optional[str]): The shape of the radio button. Valid values are
93
+ "circle" or "square". Defaults to None, which typically means a default circle shape.
94
+ """
95
+
96
+ _widget_class: Type[Widget] = RadioWidget
97
+
98
+ x: List[float]
99
+ y: List[float]
100
+ shape: Optional[str] = None
@@ -11,43 +11,23 @@ signature form fields in PDFs, including handling their creation, rendering, and
11
11
  integration into the document.
12
12
  """
13
13
 
14
- # TODO: In `watermarks`, `PdfReader(stream_to_io(BEDROCK_PDF))` is called every time the method is invoked. If `BEDROCK_PDF` is static, consider parsing it once and caching the `PdfReader` object to avoid redundant I/O and parsing.
15
- # TODO: In `watermarks`, the list comprehension `[f.read() if i == self.page_number - 1 else b"" for i in range(page_count)]` reads the entire `BytesIO` object `f` multiple times if `page_count` is large. Read `f` once into a variable and then use that variable in the list comprehension.
16
- # TODO: The `input_pdf` is created in `watermarks` but only its page count is used. If the `PdfReader` object is not needed for other operations, consider a lighter way to get the page count or pass the `PdfReader` object from the caller if it's already available.
14
+ from __future__ import annotations
17
15
 
16
+ from collections import defaultdict
18
17
  from dataclasses import dataclass
19
18
  from io import BytesIO
20
- from typing import List, Optional
19
+ from typing import List, Optional, Type
21
20
 
22
21
  from pypdf import PdfReader, PdfWriter
23
22
  from pypdf.generic import (ArrayObject, FloatObject, NameObject,
24
23
  TextStringObject)
24
+ from reportlab.pdfgen.canvas import Canvas
25
25
 
26
+ from ..assets.bedrock import BEDROCK_PDF
26
27
  from ..constants import Annots, Rect, T
27
28
  from ..template import get_widget_key
28
29
  from ..utils import stream_to_io
29
30
  from .base import Field
30
- from .bedrock import BEDROCK_PDF
31
-
32
-
33
- @dataclass
34
- class SignatureField(Field):
35
- """
36
- Represents a signature field in a PDF document.
37
-
38
- This dataclass extends the `Field` base class and defines the specific
39
- attributes that can be configured for a signature input field.
40
-
41
- Attributes:
42
- _field_type (str): The type of the field, fixed as "signature".
43
- width (Optional[float]): The width of the signature field.
44
- height (Optional[float]): The height of the signature field.
45
- """
46
-
47
- _field_type: str = "signature"
48
-
49
- width: Optional[float] = None
50
- height: Optional[float] = None
51
31
 
52
32
 
53
33
  class SignatureWidget:
@@ -106,52 +86,108 @@ class SignatureWidget:
106
86
  if each in kwargs:
107
87
  self.hook_params.append((each, kwargs.get(each)))
108
88
 
109
- def watermarks(self, stream: bytes) -> List[bytes]:
89
+ @staticmethod
90
+ def bulk_watermarks(widgets: List[SignatureWidget], stream: bytes) -> List[bytes]:
110
91
  """
111
- Generates watermarks for the signature widget.
92
+ Generates watermarks for multiple signature widgets in bulk.
112
93
 
113
- This method takes a PDF stream as input, reads a "bedrock" PDF, and
114
- creates a new PDF with the signature widget added as a watermark on the
115
- specified page. The signature's name and rectangle are then added to the
116
- new PDF.
94
+ This static method processes a list of SignatureWidget objects and a PDF stream
95
+ to create a list of watermark PDF streams, one for each page of the input PDF.
96
+ Each watermark PDF contains all the signature widgets that belong to that page.
97
+ This is more efficient than generating watermarks one by one.
117
98
 
118
99
  Args:
119
- stream (bytes): The PDF stream.
100
+ widgets (List[SignatureWidget]): A list of SignatureWidget objects to be
101
+ added as watermarks.
102
+ stream (bytes): The PDF stream of the document to be watermarked.
120
103
 
121
104
  Returns:
122
- List[bytes]: A list of watermarks for the signature widget. Each
123
- element in the list represents a page in the PDF. If the current
124
- page matches the signature's page number, the corresponding element
125
- will contain the watermark data. Otherwise, the element will be an
126
- empty byte string.
105
+ List[bytes]: A list of watermark PDF streams. Each element corresponds to
106
+ a page in the input PDF.
127
107
  """
128
- input_pdf = PdfReader(stream_to_io(stream))
129
- page_count = len(input_pdf.pages)
130
- pdf = PdfReader(stream_to_io(BEDROCK_PDF))
131
- out = PdfWriter()
132
- out.append(pdf)
108
+ result = []
133
109
 
134
- for page in out.pages:
135
- for annot in page.get(Annots, []):
136
- key = get_widget_key(annot.get_object(), False)
110
+ page_to_widgets = defaultdict(list)
111
+ for widget in widgets:
112
+ page_to_widgets[widget.page_number].append(widget)
137
113
 
138
- if key != self.BEDROCK_WIDGET_TO_COPY:
139
- continue
114
+ input_pdf = PdfReader(stream_to_io(stream))
140
115
 
141
- annot.get_object()[NameObject(T)] = TextStringObject(self.name)
142
- annot.get_object()[NameObject(Rect)] = ArrayObject(
116
+ bedrock = PdfReader(stream_to_io(BEDROCK_PDF))
117
+ page = bedrock.pages[0]
118
+ annot_type_to_annot = {}
119
+ for annot in page.get(Annots, []): # pylint: disable=E1101
120
+ key = get_widget_key(annot.get_object(), False)
121
+ annot_type_to_annot[key] = annot.get_object()
122
+
123
+ watermark = BytesIO()
124
+
125
+ for i, p in enumerate(input_pdf.pages):
126
+ # pylint: disable=R0801
127
+ watermark.seek(0)
128
+ watermark.flush()
129
+ canvas = Canvas(
130
+ watermark,
131
+ pagesize=(
132
+ float(p.mediabox[2]),
133
+ float(p.mediabox[3]),
134
+ ),
135
+ )
136
+ canvas.showPage()
137
+ canvas.save()
138
+ watermark.seek(0)
139
+
140
+ out = PdfWriter(watermark)
141
+
142
+ page_widgets = page_to_widgets.get(i + 1, [])
143
+
144
+ widgets_to_copy = []
145
+ for widget in page_widgets:
146
+ widget_to_copy = annot_type_to_annot[
147
+ widget.BEDROCK_WIDGET_TO_COPY
148
+ ].clone(out, force_duplicate=True)
149
+
150
+ widget_to_copy.get_object()[NameObject(T)] = TextStringObject(
151
+ widget.name
152
+ )
153
+ widget_to_copy.get_object()[NameObject(Rect)] = ArrayObject(
143
154
  [
144
- FloatObject(self.x),
145
- FloatObject(self.y),
146
- FloatObject(self.x + self.optional_params.get("width")),
147
- FloatObject(self.y + self.optional_params.get("height")),
155
+ FloatObject(widget.x),
156
+ FloatObject(widget.y),
157
+ FloatObject(widget.x + widget.optional_params.get("width")),
158
+ FloatObject(widget.y + widget.optional_params.get("height")),
148
159
  ]
149
160
  )
150
161
 
151
- with BytesIO() as f:
152
- out.write(f)
153
- f.seek(0)
154
- return [
155
- f.read() if i == self.page_number - 1 else b""
156
- for i in range(page_count)
157
- ]
162
+ widgets_to_copy.append(widget_to_copy)
163
+
164
+ out.pages[0][NameObject(Annots)] = ArrayObject( # pylint: disable=E1137
165
+ widgets_to_copy
166
+ )
167
+
168
+ with BytesIO() as f:
169
+ out.write(f)
170
+ f.seek(0)
171
+ result.append(f.read())
172
+
173
+ return result
174
+
175
+
176
+ @dataclass
177
+ class SignatureField(Field):
178
+ """
179
+ Represents a signature field in a PDF document.
180
+
181
+ This dataclass extends the `Field` base class and defines the specific
182
+ attributes that can be configured for a signature input field.
183
+
184
+ Attributes:
185
+ _widget_class (Type[SignatureWidget]): The widget class associated with this field type.
186
+ width (Optional[float]): The width of the signature field.
187
+ height (Optional[float]): The height of the signature field.
188
+ """
189
+
190
+ _widget_class: Type[SignatureWidget] = SignatureWidget
191
+
192
+ width: Optional[float] = None
193
+ height: Optional[float] = None
PyPDFForm/widgets/text.py CHANGED
@@ -11,11 +11,48 @@ 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
 
18
18
 
19
+ class TextWidget(Widget):
20
+ """
21
+ Represents a text widget in a PDF form.
22
+
23
+ This class inherits from the base Widget class and provides specific
24
+ parameters for text field styling, such as width, height, font size,
25
+ font color, background color, border color, border width, and maximum
26
+ length.
27
+
28
+ Attributes:
29
+ USER_PARAMS (list): A list of tuples, where each tuple contains the
30
+ user-facing parameter name and the corresponding AcroForm parameter name.
31
+ COLOR_PARAMS (list): A list of user-facing parameter names that represent colors.
32
+ ALLOWED_HOOK_PARAMS (list): A list of allowed hook parameters.
33
+ NONE_DEFAULTS (list): A list of parameters that default to None.
34
+ ACRO_FORM_FUNC (str): The name of the AcroForm function to use for
35
+ creating the text field.
36
+ """
37
+
38
+ USER_PARAMS = [
39
+ ("required", "required"),
40
+ ("tooltip", "tooltip"),
41
+ ("width", "width"),
42
+ ("height", "height"),
43
+ ("font_size", "fontSize"),
44
+ ("font_color", "textColor"),
45
+ ("bg_color", "fillColor"),
46
+ ("border_color", "borderColor"),
47
+ ("border_width", "borderWidth"),
48
+ ("max_length", "maxlen"),
49
+ ]
50
+ COLOR_PARAMS = ["font_color", "bg_color", "border_color"]
51
+ ALLOWED_HOOK_PARAMS = ["alignment", "multiline", "comb", "font"]
52
+ NONE_DEFAULTS = ["max_length"]
53
+ ACRO_FORM_FUNC = "textfield"
54
+
55
+
19
56
  @dataclass
20
57
  class TextField(Field):
21
58
  """
@@ -25,7 +62,7 @@ class TextField(Field):
25
62
  attributes that can be configured for a text input field.
26
63
 
27
64
  Attributes:
28
- _field_type (str): The type of the field, fixed as "text".
65
+ _widget_class (Type[Widget]): The widget class associated with this field type.
29
66
  width (Optional[float]): The width of the text field.
30
67
  height (Optional[float]): The height of the text field.
31
68
  max_length (Optional[int]): The maximum number of characters allowed in the text field.
@@ -41,7 +78,7 @@ class TextField(Field):
41
78
  multiline (Optional[bool]): If True, the text field can display multiple lines of text.
42
79
  """
43
80
 
44
- _field_type: str = "text"
81
+ _widget_class: Type[Widget] = TextWidget
45
82
 
46
83
  width: Optional[float] = None
47
84
  height: Optional[float] = None
@@ -55,40 +92,3 @@ class TextField(Field):
55
92
  border_width: Optional[float] = None
56
93
  alignment: Optional[int] = None
57
94
  multiline: Optional[bool] = None
58
-
59
-
60
- class TextWidget(Widget):
61
- """
62
- Represents a text widget in a PDF form.
63
-
64
- This class inherits from the base Widget class and provides specific
65
- parameters for text field styling, such as width, height, font size,
66
- font color, background color, border color, border width, and maximum
67
- length.
68
-
69
- Attributes:
70
- USER_PARAMS (list): A list of tuples, where each tuple contains the
71
- user-facing parameter name and the corresponding AcroForm parameter name.
72
- COLOR_PARAMS (list): A list of user-facing parameter names that represent colors.
73
- ALLOWED_HOOK_PARAMS (list): A list of allowed hook parameters.
74
- NONE_DEFAULTS (list): A list of parameters that default to None.
75
- ACRO_FORM_FUNC (str): The name of the AcroForm function to use for
76
- creating the text field.
77
- """
78
-
79
- USER_PARAMS = [
80
- ("required", "required"),
81
- ("tooltip", "tooltip"),
82
- ("width", "width"),
83
- ("height", "height"),
84
- ("font_size", "fontSize"),
85
- ("font_color", "textColor"),
86
- ("bg_color", "fillColor"),
87
- ("border_color", "borderColor"),
88
- ("border_width", "borderWidth"),
89
- ("max_length", "maxlen"),
90
- ]
91
- COLOR_PARAMS = ["font_color", "bg_color", "border_color"]
92
- ALLOWED_HOOK_PARAMS = ["alignment", "multiline", "comb", "font"]
93
- NONE_DEFAULTS = ["max_length"]
94
- ACRO_FORM_FUNC = "textfield"