PyPDFForm 1.5.6__tar.gz → 2.0.0__tar.gz

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.

Files changed (49) hide show
  1. {pypdfform-1.5.6 → pypdfform-2.0.0}/PKG-INFO +12 -2
  2. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/__init__.py +1 -1
  3. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/constants.py +8 -0
  4. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/coordinate.py +54 -9
  5. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/filler.py +75 -10
  6. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/font.py +30 -33
  7. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/middleware/base.py +6 -0
  8. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/middleware/checkbox.py +1 -1
  9. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/middleware/radio.py +12 -1
  10. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/patterns.py +18 -3
  11. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/template.py +61 -101
  12. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/utils.py +45 -5
  13. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/watermark.py +54 -11
  14. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/widgets/base.py +5 -4
  15. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/wrapper.py +29 -21
  16. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm.egg-info/PKG-INFO +12 -2
  17. {pypdfform-1.5.6 → pypdfform-2.0.0}/README.md +9 -0
  18. {pypdfform-1.5.6 → pypdfform-2.0.0}/LICENSE +0 -0
  19. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/adapter.py +0 -0
  20. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/image.py +0 -0
  21. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/middleware/__init__.py +0 -0
  22. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/middleware/dropdown.py +0 -0
  23. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/middleware/image.py +0 -0
  24. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/middleware/signature.py +0 -0
  25. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/middleware/text.py +0 -0
  26. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/widgets/__init__.py +0 -0
  27. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/widgets/checkbox.py +0 -0
  28. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/widgets/dropdown.py +0 -0
  29. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm/widgets/text.py +0 -0
  30. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm.egg-info/SOURCES.txt +0 -0
  31. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  32. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm.egg-info/requires.txt +0 -0
  33. {pypdfform-1.5.6 → pypdfform-2.0.0}/PyPDFForm.egg-info/top_level.txt +0 -0
  34. {pypdfform-1.5.6 → pypdfform-2.0.0}/setup.cfg +0 -0
  35. {pypdfform-1.5.6 → pypdfform-2.0.0}/setup.py +0 -0
  36. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_adobe_mode.py +0 -0
  37. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_create_widget.py +0 -0
  38. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_dropdown.py +0 -0
  39. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_dropdown_simple.py +0 -0
  40. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_fill_max_length_text_field.py +0 -0
  41. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_fill_max_length_text_field_simple.py +0 -0
  42. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_fill_method.py +0 -0
  43. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_functional.py +0 -0
  44. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_functional_simple.py +0 -0
  45. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_paragraph.py +0 -0
  46. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_paragraph_simple.py +0 -0
  47. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_preview.py +0 -0
  48. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_signature.py +0 -0
  49. {pypdfform-1.5.6 → pypdfform-2.0.0}/tests/test_use_full_widget_name.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 1.5.6
3
+ Version: 2.0.0
4
4
  Summary: The Python library for PDF forms.
5
5
  Home-page: https://github.com/chinapandaman/PyPDFForm
6
6
  Author: Jinge Li
@@ -29,6 +29,7 @@ Dynamic: classifier
29
29
  Dynamic: description
30
30
  Dynamic: description-content-type
31
31
  Dynamic: home-page
32
+ Dynamic: license-file
32
33
  Dynamic: requires-dist
33
34
  Dynamic: requires-python
34
35
  Dynamic: summary
@@ -43,6 +44,15 @@ Dynamic: summary
43
44
  <a href="https://pypistats.org/packages/pypdfform"><img src="https://img.shields.io/pypi/dm/pypdfform?logo=pypi&logoColor=white&label=downloads&labelColor=black&color=blue&style=for-the-badge"></a>
44
45
  </p>
45
46
 
47
+ ## Important Announcements
48
+
49
+ Hello fellow Python developers! With the release of v2.0.0, there are some important changes I'm making to the library:
50
+
51
+ * Since I started developing the library, versioning releases has been quite unorthodox, and there isn't any convention I followed. Starting with v2.0.0, PyPDFForm will version releases following the conventions defined by [Semantic Versioning](https://semver.org/).
52
+ * PyPDFForm now renders PDF form widgets! Ever since its ancestral stage, the library has only been able to render the data you filled into a PDF form. Now if you fill a PDF form using `PdfWrapper`, the result will render the whole widget instead of just the value that got filled. If you would like to disable this behavior, please refer to the docs [here](https://chinapandaman.github.io/PyPDFForm/fill/#disable-rendering-widgets).
53
+
54
+ Happy hacking!
55
+
46
56
  ## Introduction
47
57
 
48
58
  PyPDFForm is a free and open source pure-Python 3 library for PDF form processing. It contains the essential
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Contains any object users might need."""
3
3
 
4
- __version__ = "1.5.6"
4
+ __version__ = "2.0.0"
5
5
 
6
6
  from .wrapper import FormWrapper, PdfWrapper
7
7
 
@@ -50,6 +50,13 @@ Ch = "/Ch"
50
50
  Opt = "/Opt"
51
51
  MK = "/MK"
52
52
  CA = "/CA"
53
+ BC = "/BC"
54
+ BG = "/BG"
55
+ BS = "/BS"
56
+ W = "/W"
57
+ S = "/S"
58
+ D = "/D"
59
+ U = "/U"
53
60
  AS = "/AS"
54
61
  Yes = "/Yes"
55
62
  Off = "/Off"
@@ -66,6 +73,7 @@ COMB = 1 << 24
66
73
 
67
74
  FONT_SIZE_IDENTIFIER = "Tf"
68
75
  FONT_COLOR_IDENTIFIER = " rg"
76
+ DEFAULT_BORDER_WIDTH = 1
69
77
  DEFAULT_FONT = "Helvetica"
70
78
  DEFAULT_FONT_SIZE = 12
71
79
  DEFAULT_FONT_COLOR = (0, 0, 0)
@@ -10,21 +10,58 @@ from reportlab.pdfbase.pdfmetrics import stringWidth
10
10
  from .constants import (COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO, DEFAULT_FONT,
11
11
  Rect)
12
12
  from .middleware.text import Text
13
- from .template import (get_char_rect_width, get_widget_alignment,
14
- is_text_multiline)
15
- from .utils import stream_to_io
13
+ from .patterns import WIDGET_ALIGNMENT_PATTERNS
14
+ from .template import get_char_rect_width, is_text_multiline
15
+ from .utils import extract_widget_property, handle_color, stream_to_io
16
16
  from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
17
17
 
18
18
 
19
+ def get_draw_border_coordinates(widget: dict, shape: str) -> List[float]:
20
+ """Returns coordinates to draw each widget's border."""
21
+
22
+ result = [
23
+ float(widget[Rect][0]),
24
+ float(widget[Rect][1]),
25
+ abs(float(widget[Rect][0]) - float(widget[Rect][2])),
26
+ abs(float(widget[Rect][1]) - float(widget[Rect][3])),
27
+ ]
28
+
29
+ if shape == "ellipse":
30
+ width = abs(float(widget[Rect][0]) - float(widget[Rect][2]))
31
+ height = abs(float(widget[Rect][1]) - float(widget[Rect][3]))
32
+
33
+ width_mid = (float(widget[Rect][0]) + float(widget[Rect][2])) / 2
34
+ height_mid = (float(widget[Rect][1]) + float(widget[Rect][3])) / 2
35
+
36
+ less = min(width, height)
37
+
38
+ result = [
39
+ width_mid - less / 2,
40
+ height_mid - less / 2,
41
+ width_mid + less / 2,
42
+ height_mid + less / 2,
43
+ ]
44
+ elif shape == "line":
45
+ result = [
46
+ float(widget[Rect][0]),
47
+ float(widget[Rect][1]),
48
+ float(widget[Rect][2]),
49
+ float(widget[Rect][1]),
50
+ ]
51
+
52
+ return result
53
+
54
+
19
55
  def get_draw_checkbox_radio_coordinates(
20
56
  widget: dict,
21
57
  widget_middleware: Text,
58
+ border_width: int,
22
59
  ) -> Tuple[Union[float, int], Union[float, int]]:
23
60
  """Returns coordinates to draw at given a PDF form checkbox/radio widget."""
24
61
 
25
- string_height = widget_middleware.font_size * 96 / 72
62
+ string_height = widget_middleware.font_size * 72 / 96
26
63
  width_mid_point = (float(widget[Rect][0]) + float(widget[Rect][2])) / 2
27
- height_mid_point = (float(widget[Rect][1]) + float(widget[Rect][3])) / 2
64
+ half_widget_height = abs(float(widget[Rect][1]) - float(widget[Rect][3])) / 2
28
65
 
29
66
  return (
30
67
  width_mid_point
@@ -34,7 +71,9 @@ def get_draw_checkbox_radio_coordinates(
34
71
  widget_middleware.font_size,
35
72
  )
36
73
  / 2,
37
- (height_mid_point - string_height / 2 + height_mid_point) / 2,
74
+ float(widget[Rect][1])
75
+ + (half_widget_height - string_height / 2)
76
+ + border_width / 2,
38
77
  )
39
78
 
40
79
 
@@ -96,7 +135,9 @@ def get_draw_text_coordinates(
96
135
  else widget_middleware.character_paddings
97
136
  )
98
137
 
99
- alignment = get_widget_alignment(widget) or 0
138
+ alignment = (
139
+ extract_widget_property(widget, WIDGET_ALIGNMENT_PATTERNS, None, int) or 0
140
+ )
100
141
  x = float(widget[Rect][0])
101
142
 
102
143
  if int(alignment) != 0:
@@ -196,12 +237,16 @@ def generate_coordinate_grid(
196
237
 
197
238
  current = margin
198
239
  while current < width:
199
- lines_by_page[i + 1].append([current, 0, current, height, r, g, b])
240
+ lines_by_page[i + 1].append(
241
+ [current, 0, current, height, handle_color([r, g, b]), None, 1, None]
242
+ )
200
243
  current += margin
201
244
 
202
245
  current = margin
203
246
  while current < height:
204
- lines_by_page[i + 1].append([0, current, width, current, r, g, b])
247
+ lines_by_page[i + 1].append(
248
+ [0, current, width, current, handle_color([r, g, b]), None, 1, None]
249
+ )
205
250
  current += margin
206
251
 
207
252
  x = margin
@@ -7,8 +7,10 @@ from typing import Dict, Tuple, Union, cast
7
7
  from pypdf import PdfReader, PdfWriter
8
8
  from pypdf.generic import BooleanObject, DictionaryObject, NameObject
9
9
 
10
- from .constants import WIDGET_TYPES, AcroForm, Annots, NeedAppearances, Root
11
- from .coordinate import (get_draw_checkbox_radio_coordinates,
10
+ from .constants import (BUTTON_STYLES, DEFAULT_RADIO_STYLE, WIDGET_TYPES,
11
+ AcroForm, Annots, NeedAppearances, Root, U)
12
+ from .coordinate import (get_draw_border_coordinates,
13
+ get_draw_checkbox_radio_coordinates,
12
14
  get_draw_image_coordinates_resolutions,
13
15
  get_draw_text_coordinates,
14
16
  get_text_line_x_coordinates)
@@ -20,12 +22,13 @@ from .middleware.image import Image
20
22
  from .middleware.radio import Radio
21
23
  from .middleware.signature import Signature
22
24
  from .middleware.text import Text
23
- from .patterns import (simple_flatten_generic, simple_flatten_radio,
24
- simple_update_checkbox_value,
25
+ from .patterns import (WIDGET_KEY_PATTERNS, simple_flatten_generic,
26
+ simple_flatten_radio, simple_update_checkbox_value,
25
27
  simple_update_dropdown_value, simple_update_radio_value,
26
28
  simple_update_text_value)
27
- from .template import get_widget_key, get_widgets_by_page
28
- from .utils import checkbox_radio_to_draw, stream_to_io
29
+ from .template import get_widgets_by_page
30
+ from .utils import (checkbox_radio_to_draw, extract_widget_property,
31
+ stream_to_io)
29
32
  from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
30
33
 
31
34
 
@@ -38,7 +41,9 @@ def check_radio_handler(
38
41
  checkbox_radio_font_size(widget) if middleware.size is None else middleware.size
39
42
  )
40
43
  to_draw = checkbox_radio_to_draw(middleware, font_size)
41
- x, y = get_draw_checkbox_radio_coordinates(widget, to_draw)
44
+ x, y = get_draw_checkbox_radio_coordinates(
45
+ widget, to_draw, border_width=middleware.border_width
46
+ )
42
47
  text_needs_to_be_drawn = False
43
48
  if type(middleware) is Checkbox and middleware.value:
44
49
  text_needs_to_be_drawn = True
@@ -91,6 +96,45 @@ def text_handler(
91
96
  return to_draw, x, y, text_needs_to_be_drawn
92
97
 
93
98
 
99
+ def border_handler(
100
+ widget: dict,
101
+ middleware: WIDGET_TYPES,
102
+ rect_borders_to_draw: list,
103
+ ellipse_borders_to_draw: list,
104
+ line_borders_to_draw: list,
105
+ ) -> None:
106
+ """Handles draw parameters for each widget's border."""
107
+
108
+ if (
109
+ isinstance(middleware, Radio)
110
+ and BUTTON_STYLES.get(middleware.button_style) == DEFAULT_RADIO_STYLE
111
+ ):
112
+ list_to_append = ellipse_borders_to_draw
113
+ shape = "ellipse"
114
+ elif middleware.border_style == U:
115
+ list_to_append = line_borders_to_draw
116
+ shape = "line"
117
+ else:
118
+ list_to_append = rect_borders_to_draw
119
+ shape = "rect"
120
+
121
+ list_to_append.append(
122
+ get_draw_border_coordinates(widget, shape)
123
+ + [
124
+ middleware.border_color,
125
+ middleware.background_color,
126
+ middleware.border_width,
127
+ middleware.dash_array,
128
+ ]
129
+ )
130
+
131
+ if shape == "line":
132
+ rect_borders_to_draw.append(
133
+ get_draw_border_coordinates(widget, "rect")
134
+ + [None, middleware.background_color, 0, None]
135
+ )
136
+
137
+
94
138
  def get_drawn_stream(to_draw: dict, stream: bytes, action: str) -> bytes:
95
139
  """Generates a stream of an input PDF stream with stuff drawn on it."""
96
140
 
@@ -113,6 +157,9 @@ def fill(
113
157
 
114
158
  texts_to_draw = {}
115
159
  images_to_draw = {}
160
+ rect_borders_to_draw = {}
161
+ ellipse_borders_to_draw = {}
162
+ line_borders_to_draw = {}
116
163
  any_image_to_draw = False
117
164
 
118
165
  radio_button_tracker = {}
@@ -120,11 +167,23 @@ def fill(
120
167
  for page, widget_dicts in get_widgets_by_page(template_stream).items():
121
168
  texts_to_draw[page] = []
122
169
  images_to_draw[page] = []
170
+ rect_borders_to_draw[page] = []
171
+ ellipse_borders_to_draw[page] = []
172
+ line_borders_to_draw[page] = []
123
173
  for widget_dict in widget_dicts:
124
- key = get_widget_key(widget_dict)
174
+ key = extract_widget_property(widget_dict, WIDGET_KEY_PATTERNS, None, str)
125
175
  text_needs_to_be_drawn = False
126
176
  to_draw = x = y = None
127
177
 
178
+ if widgets[key].render_widget:
179
+ border_handler(
180
+ widget_dict,
181
+ widgets[key],
182
+ rect_borders_to_draw[page],
183
+ ellipse_borders_to_draw[page],
184
+ line_borders_to_draw[page],
185
+ )
186
+
128
187
  if isinstance(widgets[key], (Checkbox, Radio)):
129
188
  to_draw, x, y, text_needs_to_be_drawn = check_radio_handler(
130
189
  widget_dict, widgets[key], radio_button_tracker
@@ -154,7 +213,11 @@ def fill(
154
213
  ]
155
214
  )
156
215
 
157
- result = get_drawn_stream(texts_to_draw, template_stream, "text")
216
+ result = template_stream
217
+ result = get_drawn_stream(rect_borders_to_draw, result, "rect")
218
+ result = get_drawn_stream(ellipse_borders_to_draw, result, "ellipse")
219
+ result = get_drawn_stream(line_borders_to_draw, result, "line")
220
+ result = get_drawn_stream(texts_to_draw, result, "text")
158
221
 
159
222
  if any_image_to_draw:
160
223
  result = get_drawn_stream(images_to_draw, result, "image")
@@ -189,7 +252,9 @@ def simple_fill(
189
252
  for page in out.pages:
190
253
  for annot in page.get(Annots, []): # noqa
191
254
  annot = cast(DictionaryObject, annot.get_object())
192
- key = get_widget_key(annot.get_object())
255
+ key = extract_widget_property(
256
+ annot.get_object(), WIDGET_KEY_PATTERNS, None, str
257
+ )
193
258
 
194
259
  widget = widgets.get(key)
195
260
  if widget is None or widget.value is None:
@@ -16,7 +16,7 @@ from .constants import (DEFAULT_FONT, FONT_COLOR_IDENTIFIER,
16
16
  MARGIN_BETWEEN_LINES, Rect)
17
17
  from .middleware.text import Text
18
18
  from .patterns import TEXT_FIELD_APPEARANCE_PATTERNS
19
- from .utils import traverse_pattern
19
+ from .utils import extract_widget_property
20
20
 
21
21
 
22
22
  def register_font(font_name: str, ttf_stream: bytes) -> bool:
@@ -72,19 +72,14 @@ def extract_font_from_text_appearance(text_appearance: str) -> Union[str, None]:
72
72
  def auto_detect_font(widget: dict) -> str:
73
73
  """Returns the font of the text field if it is one of the standard fonts."""
74
74
 
75
- result = DEFAULT_FONT
76
-
77
- text_appearance = None
78
- for pattern in TEXT_FIELD_APPEARANCE_PATTERNS:
79
- text_appearance = traverse_pattern(pattern, widget)
80
-
81
- if text_appearance:
82
- break
75
+ text_appearance = extract_widget_property(
76
+ widget, TEXT_FIELD_APPEARANCE_PATTERNS, None, None
77
+ )
83
78
 
84
79
  if not text_appearance:
85
- return result
80
+ return DEFAULT_FONT
86
81
 
87
- return extract_font_from_text_appearance(text_appearance) or result
82
+ return extract_font_from_text_appearance(text_appearance) or DEFAULT_FONT
88
83
 
89
84
 
90
85
  def text_field_font_size(widget: dict) -> Union[float, int]:
@@ -115,13 +110,14 @@ def get_text_field_font_size(widget: dict) -> Union[float, int]:
115
110
  """Returns the font size of the text field if presented or zero."""
116
111
 
117
112
  result = 0
118
- for pattern in TEXT_FIELD_APPEARANCE_PATTERNS:
119
- text_appearance = traverse_pattern(pattern, widget)
120
- if text_appearance:
121
- properties = text_appearance.split(" ")
122
- for i, val in enumerate(properties):
123
- if val.startswith(FONT_SIZE_IDENTIFIER):
124
- return float(properties[i - 1])
113
+ text_appearance = extract_widget_property(
114
+ widget, TEXT_FIELD_APPEARANCE_PATTERNS, None, None
115
+ )
116
+ if text_appearance:
117
+ properties = text_appearance.split(" ")
118
+ for i, val in enumerate(properties):
119
+ if val.startswith(FONT_SIZE_IDENTIFIER):
120
+ return float(properties[i - 1])
125
121
 
126
122
  return result
127
123
 
@@ -132,21 +128,22 @@ def get_text_field_font_color(
132
128
  """Returns the font color tuple of the text field if presented or black."""
133
129
 
134
130
  result = (0, 0, 0)
135
- for pattern in TEXT_FIELD_APPEARANCE_PATTERNS:
136
- text_appearance = traverse_pattern(pattern, widget)
137
- if text_appearance:
138
- if FONT_COLOR_IDENTIFIER not in text_appearance:
139
- return result
140
-
141
- text_appearance = text_appearance.split(" ")
142
- for i, val in enumerate(text_appearance):
143
- if val.startswith(FONT_COLOR_IDENTIFIER.replace(" ", "")):
144
- result = (
145
- float(text_appearance[i - 3]),
146
- float(text_appearance[i - 2]),
147
- float(text_appearance[i - 1]),
148
- )
149
- break
131
+ text_appearance = extract_widget_property(
132
+ widget, TEXT_FIELD_APPEARANCE_PATTERNS, None, None
133
+ )
134
+ if text_appearance:
135
+ if FONT_COLOR_IDENTIFIER not in text_appearance:
136
+ return result
137
+
138
+ text_appearance = text_appearance.split(" ")
139
+ for i, val in enumerate(text_appearance):
140
+ if val.startswith(FONT_COLOR_IDENTIFIER.replace(" ", "")):
141
+ result = (
142
+ float(text_appearance[i - 3]),
143
+ float(text_appearance[i - 2]),
144
+ float(text_appearance[i - 1]),
145
+ )
146
+ break
150
147
 
151
148
  return result
152
149
 
@@ -19,6 +19,12 @@ class Widget:
19
19
  self.full_name = None
20
20
  self._value = value
21
21
  self.desc = None
22
+ self.border_color = None
23
+ self.background_color = None
24
+ self.border_width = None
25
+ self.border_style = None
26
+ self.dash_array = None
27
+ self.render_widget = None
22
28
 
23
29
  @property
24
30
  def name(self) -> str:
@@ -25,7 +25,7 @@ class Checkbox(Widget):
25
25
  super().__init__(name, value)
26
26
 
27
27
  self.size = None
28
- self._button_style = None
28
+ self._button_style = self.BUTTON_STYLE_MAPPING["check"]
29
29
 
30
30
  @property
31
31
  def schema_definition(self) -> dict:
@@ -7,7 +7,18 @@ from .checkbox import Checkbox
7
7
  class Radio(Checkbox):
8
8
  """A class to represent a radiobutton widget."""
9
9
 
10
- number_of_options = 0
10
+ def __init__(
11
+ self,
12
+ name: str,
13
+ value: int = None,
14
+ ) -> None:
15
+ """Constructs all attributes for the radiobutton."""
16
+
17
+ super().__init__(name, value)
18
+
19
+ self.size = None
20
+ self.number_of_options = 0
21
+ self._button_style = self.BUTTON_STYLE_MAPPING["circle"]
11
22
 
12
23
  @property
13
24
  def schema_definition(self) -> dict:
@@ -4,9 +4,10 @@
4
4
  from pypdf.generic import (DictionaryObject, NameObject, NumberObject,
5
5
  TextStringObject)
6
6
 
7
- from .constants import (AP, AS, CA, DA, DV, FT, IMAGE_FIELD_IDENTIFIER, JS, MK,
8
- MULTILINE, READ_ONLY, TU, A, Btn, Ch, Ff, N, Off, Opt,
9
- Parent, Q, Sig, T, Tx, V, Yes)
7
+ from .constants import (AP, AS, BC, BG, BS, CA, DA, DV, FT,
8
+ IMAGE_FIELD_IDENTIFIER, JS, MK, MULTILINE, READ_ONLY,
9
+ TU, A, Btn, Ch, D, Ff, N, Off, Opt, Parent, Q, S, Sig,
10
+ T, Tx, V, W, Yes)
10
11
  from .middleware.checkbox import Checkbox
11
12
  from .middleware.dropdown import Dropdown
12
13
  from .middleware.image import Image
@@ -94,6 +95,20 @@ BUTTON_STYLE_PATTERNS = [
94
95
  {MK: {CA: True}},
95
96
  ]
96
97
 
98
+ BORDER_COLOR_PATTERNS = [
99
+ {MK: {BC: True}},
100
+ ]
101
+
102
+ BACKGROUND_COLOR_PATTERNS = [
103
+ {MK: {BG: True}},
104
+ ]
105
+
106
+ BORDER_WIDTH_PATTERNS = [{BS: {W: True}}]
107
+
108
+ BORDER_STYLE_PATTERNS = [{BS: {S: True}}]
109
+
110
+ BORDER_DASH_ARRAY_PATTERNS = [{BS: {D: True}}]
111
+
97
112
 
98
113
  def simple_update_checkbox_value(annot: DictionaryObject, check: bool = False) -> None:
99
114
  """Patterns to update values for checkbox annotations."""
@@ -10,8 +10,9 @@ from pypdf import PdfReader, PdfWriter
10
10
  from pypdf.generic import DictionaryObject
11
11
  from reportlab.pdfbase.pdfmetrics import stringWidth
12
12
 
13
- from .constants import (COMB, DEFAULT_FONT_SIZE, MULTILINE, NEW_LINE_SYMBOL,
14
- WIDGET_TYPES, Annots, MaxLen, Parent, Rect, T)
13
+ from .constants import (COMB, DEFAULT_BORDER_WIDTH, DEFAULT_FONT_SIZE,
14
+ MULTILINE, NEW_LINE_SYMBOL, WIDGET_TYPES, Annots,
15
+ MaxLen, Parent, Rect, T)
15
16
  from .font import (adjust_paragraph_font_size, adjust_text_field_font_size,
16
17
  auto_detect_font, get_text_field_font_color,
17
18
  get_text_field_font_size, text_field_font_size)
@@ -19,12 +20,14 @@ from .middleware.checkbox import Checkbox
19
20
  from .middleware.dropdown import Dropdown
20
21
  from .middleware.radio import Radio
21
22
  from .middleware.text import Text
22
- from .patterns import (BUTTON_STYLE_PATTERNS, DROPDOWN_CHOICE_PATTERNS,
23
- TEXT_FIELD_FLAG_PATTERNS, WIDGET_ALIGNMENT_PATTERNS,
23
+ from .patterns import (BACKGROUND_COLOR_PATTERNS, BORDER_COLOR_PATTERNS,
24
+ BORDER_DASH_ARRAY_PATTERNS, BORDER_STYLE_PATTERNS,
25
+ BORDER_WIDTH_PATTERNS, BUTTON_STYLE_PATTERNS,
26
+ DROPDOWN_CHOICE_PATTERNS, TEXT_FIELD_FLAG_PATTERNS,
24
27
  WIDGET_DESCRIPTION_PATTERNS, WIDGET_KEY_PATTERNS,
25
28
  WIDGET_TYPE_PATTERNS, update_annotation_name)
26
- from .utils import find_pattern_match, stream_to_io, traverse_pattern
27
- from .watermark import create_watermarks_and_draw
29
+ from .utils import (extract_widget_property, find_pattern_match, handle_color,
30
+ stream_to_io)
28
31
 
29
32
 
30
33
  def set_character_x_paddings(
@@ -34,7 +37,7 @@ def set_character_x_paddings(
34
37
 
35
38
  for _widgets in get_widgets_by_page(pdf_stream).values():
36
39
  for widget in _widgets:
37
- key = get_widget_key(widget)
40
+ key = extract_widget_property(widget, WIDGET_KEY_PATTERNS, None, str)
38
41
  _widget = widgets[key]
39
42
 
40
43
  if isinstance(_widget, Text) and _widget.comb is True:
@@ -44,7 +47,9 @@ def set_character_x_paddings(
44
47
 
45
48
 
46
49
  def build_widgets(
47
- pdf_stream: bytes, use_full_widget_name: bool
50
+ pdf_stream: bytes,
51
+ use_full_widget_name: bool,
52
+ render_widgets: bool,
48
53
  ) -> Dict[str, WIDGET_TYPES]:
49
54
  """Builds a widget dict given a PDF form stream."""
50
55
 
@@ -52,18 +57,44 @@ def build_widgets(
52
57
 
53
58
  for widgets in get_widgets_by_page(pdf_stream).values():
54
59
  for widget in widgets:
55
- key = get_widget_key(widget)
60
+ key = extract_widget_property(widget, WIDGET_KEY_PATTERNS, None, str)
56
61
  _widget = construct_widget(widget, key)
57
62
  if _widget is not None:
58
- _widget.full_name = get_widget_full_key(widget)
59
- _widget.desc = get_widget_description(widget)
63
+ if use_full_widget_name:
64
+ _widget.full_name = get_widget_full_key(widget)
65
+
66
+ _widget.render_widget = render_widgets
67
+ _widget.desc = extract_widget_property(
68
+ widget, WIDGET_DESCRIPTION_PATTERNS, None, str
69
+ )
70
+ _widget.border_color = extract_widget_property(
71
+ widget, BORDER_COLOR_PATTERNS, None, handle_color
72
+ )
73
+ _widget.background_color = extract_widget_property(
74
+ widget, BACKGROUND_COLOR_PATTERNS, None, handle_color
75
+ )
76
+ _widget.border_width = extract_widget_property(
77
+ widget, BORDER_WIDTH_PATTERNS, DEFAULT_BORDER_WIDTH, float
78
+ )
79
+ _widget.border_style = extract_widget_property(
80
+ widget, BORDER_STYLE_PATTERNS, None, str
81
+ )
82
+ _widget.dash_array = extract_widget_property(
83
+ widget, BORDER_DASH_ARRAY_PATTERNS, None, list
84
+ )
85
+
60
86
  if isinstance(_widget, Text):
61
87
  _widget.max_length = get_text_field_max_length(widget)
62
88
  if _widget.max_length is not None and is_text_field_comb(widget):
63
89
  _widget.comb = True
64
90
 
65
91
  if isinstance(_widget, (Checkbox, Radio)):
66
- _widget.button_style = get_button_style(widget)
92
+ _widget.button_style = (
93
+ extract_widget_property(
94
+ widget, BUTTON_STYLE_PATTERNS, None, str
95
+ )
96
+ or _widget.button_style
97
+ )
67
98
 
68
99
  if isinstance(_widget, Dropdown):
69
100
  _widget.choices = get_dropdown_choices(widget)
@@ -81,32 +112,14 @@ def build_widgets(
81
112
  return results
82
113
 
83
114
 
84
- def widget_rect_watermarks(pdf: bytes) -> List[bytes]:
85
- """Draws the rectangular border of each widget and returns watermarks."""
86
-
87
- watermarks = []
88
-
89
- for page, widgets in get_widgets_by_page(pdf).items():
90
- to_draw = []
91
- for widget in widgets:
92
- rect = widget[Rect]
93
- x = rect[0]
94
- y = rect[1]
95
- width = abs(rect[0] - rect[2])
96
- height = abs(rect[1] - rect[3])
97
-
98
- to_draw.append([x, y, width, height])
99
- watermarks.append(
100
- create_watermarks_and_draw(pdf, page, "rect", to_draw)[page - 1]
101
- )
102
-
103
- return watermarks
104
-
105
-
106
115
  def dropdown_to_text(dropdown: Dropdown) -> Text:
107
116
  """Converts a dropdown widget to a text widget."""
108
117
 
109
118
  result = Text(dropdown.name)
119
+ result.border_color = dropdown.border_color
120
+ result.background_color = dropdown.background_color
121
+ result.border_width = dropdown.border_width
122
+ result.render_widget = dropdown.render_widget
110
123
 
111
124
  if dropdown.value is not None:
112
125
  result.value = (
@@ -126,7 +139,7 @@ def update_text_field_attributes(
126
139
 
127
140
  for _widgets in get_widgets_by_page(template_stream).values():
128
141
  for _widget in _widgets:
129
- key = get_widget_key(_widget)
142
+ key = extract_widget_property(_widget, WIDGET_KEY_PATTERNS, None, str)
130
143
 
131
144
  if isinstance(widgets[key], Text):
132
145
  should_adjust_font_size = False
@@ -183,48 +196,24 @@ def get_widgets_by_page(pdf: bytes) -> Dict[int, List[dict]]:
183
196
  return result
184
197
 
185
198
 
186
- def get_widget_key(widget: dict) -> Union[str, list, None]:
187
- """Finds a PDF widget's annotated key by pattern matching."""
188
-
189
- result = None
190
- for pattern in WIDGET_KEY_PATTERNS:
191
- value = traverse_pattern(pattern, widget)
192
- if value:
193
- result = value
194
- break
195
- return result
196
-
197
-
198
199
  def get_widget_full_key(widget: dict) -> Union[str, None]:
199
200
  """
200
201
  Returns a PDF widget's full annotated key by prepending its
201
202
  parent widget's key.
202
203
  """
203
204
 
204
- key = get_widget_key(widget)
205
+ key = extract_widget_property(widget, WIDGET_KEY_PATTERNS, None, str)
205
206
 
206
207
  if (
207
208
  Parent in widget
208
209
  and T in widget[Parent].get_object()
209
- and widget[Parent][T] != key
210
+ and widget[Parent].get_object()[T] != key
210
211
  ):
211
- return f"{widget[Parent][T]}.{key}"
212
+ return f"{widget[Parent].get_object()[T]}.{key}"
212
213
 
213
214
  return None
214
215
 
215
216
 
216
- def get_widget_alignment(widget: dict) -> Union[str, list, None]:
217
- """Finds a PDF widget's alignment by pattern matching."""
218
-
219
- result = None
220
- for pattern in WIDGET_ALIGNMENT_PATTERNS:
221
- value = traverse_pattern(pattern, widget)
222
- if value:
223
- result = value
224
- break
225
- return result
226
-
227
-
228
217
  def construct_widget(widget: dict, key: str) -> Union[WIDGET_TYPES, None]:
229
218
  """Finds a PDF widget's annotated type by pattern matching."""
230
219
 
@@ -246,26 +235,10 @@ def get_text_field_max_length(widget: dict) -> Union[int, None]:
246
235
  return int(widget[MaxLen]) or None if MaxLen in widget else None
247
236
 
248
237
 
249
- def get_widget_description(widget: dict) -> Union[str, None]:
250
- """Returns the description of the widget if presented or None."""
251
-
252
- result = None
253
- for pattern in WIDGET_DESCRIPTION_PATTERNS:
254
- value = traverse_pattern(pattern, widget)
255
- if value:
256
- result = str(value)
257
- break
258
- return result
259
-
260
-
261
238
  def check_field_flag_bit(widget: dict, bit: int) -> bool:
262
239
  """Checks if a bit is set in a widget's field flag."""
263
240
 
264
- field_flag = None
265
- for pattern in TEXT_FIELD_FLAG_PATTERNS:
266
- field_flag = traverse_pattern(pattern, widget)
267
- if field_flag is not None:
268
- break
241
+ field_flag = extract_widget_property(widget, TEXT_FIELD_FLAG_PATTERNS, None, int)
269
242
 
270
243
  if field_flag is None:
271
244
  return False
@@ -288,27 +261,12 @@ def is_text_multiline(widget: dict) -> bool:
288
261
  def get_dropdown_choices(widget: dict) -> Union[Tuple[str, ...], None]:
289
262
  """Returns string options of a dropdown field."""
290
263
 
291
- result = None
292
- for pattern in DROPDOWN_CHOICE_PATTERNS:
293
- choices = traverse_pattern(pattern, widget)
294
- if choices:
295
- result = tuple(
296
- (each if isinstance(each, str) else str(each[1])) for each in choices
297
- )
298
- break
299
-
300
- return result
301
-
302
-
303
- def get_button_style(widget: dict) -> Union[str, None]:
304
- """Returns the button style of a checkbox or radiobutton."""
305
-
306
- for pattern in BUTTON_STYLE_PATTERNS:
307
- style = traverse_pattern(pattern, widget)
308
- if style is not None:
309
- return str(style)
310
-
311
- return None
264
+ return tuple(
265
+ (each if isinstance(each, str) else str(each[1]))
266
+ for each in extract_widget_property(
267
+ widget, DROPDOWN_CHOICE_PATTERNS, None, None
268
+ )
269
+ )
312
270
 
313
271
 
314
272
  def get_char_rect_width(widget: dict, widget_middleware: Text) -> float:
@@ -462,7 +420,9 @@ def update_widget_keys(
462
420
  for page in out.pages:
463
421
  for annot in page.get(Annots, []): # noqa
464
422
  annot = cast(DictionaryObject, annot.get_object())
465
- key = get_widget_key(annot.get_object())
423
+ key = extract_widget_property(
424
+ annot.get_object(), WIDGET_KEY_PATTERNS, None, str
425
+ )
466
426
 
467
427
  widget = widgets.get(key)
468
428
  if widget is None:
@@ -1,13 +1,15 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Contains utility helpers."""
3
3
 
4
+ from collections.abc import Callable
4
5
  from io import BytesIO
5
6
  from secrets import choice
6
7
  from string import ascii_letters, digits, punctuation
7
- from typing import BinaryIO, List, Union
8
+ from typing import Any, BinaryIO, List, Union
8
9
 
9
10
  from pypdf import PdfReader, PdfWriter
10
- from pypdf.generic import DictionaryObject
11
+ from pypdf.generic import ArrayObject, DictionaryObject
12
+ from reportlab.lib.colors import CMYKColor, Color
11
13
 
12
14
  from .constants import (BUTTON_STYLES, DEFAULT_CHECKBOX_STYLE, DEFAULT_FONT,
13
15
  DEFAULT_FONT_COLOR, DEFAULT_FONT_SIZE,
@@ -28,6 +30,22 @@ def stream_to_io(stream: bytes) -> BinaryIO:
28
30
  return result
29
31
 
30
32
 
33
+ def handle_color(color: Union[list, ArrayObject]) -> Union[Color, CMYKColor, None]:
34
+ """Converts a color array to an RGB or CMYK color."""
35
+
36
+ result = None
37
+
38
+ if len(color) == 1:
39
+ result = CMYKColor(black=1 - color[0])
40
+ elif len(color) == 3:
41
+ result = Color(red=color[0], green=color[1], blue=color[2])
42
+ # write a test case for this
43
+ # elif len(color) == 4:
44
+ # result = CMYKColor(cyan=color[0], magenta=color[1], yellow=color[2], black=color[3])
45
+
46
+ return result
47
+
48
+
31
49
  def checkbox_radio_to_draw(
32
50
  widget: Union[Checkbox, Radio], font_size: Union[float, int]
33
51
  ) -> Text:
@@ -47,17 +65,20 @@ def checkbox_radio_to_draw(
47
65
  return new_widget
48
66
 
49
67
 
50
- def preview_widget_to_draw(widget: WIDGET_TYPES) -> Text:
68
+ def preview_widget_to_draw(widget: WIDGET_TYPES, with_preview_text: bool) -> Text:
51
69
  """Converts a widget to a preview text widget."""
52
70
 
53
71
  new_widget = Text(
54
72
  name=widget.name,
55
- value="{" + f" {widget.name} " + "}",
73
+ value="{" + f" {widget.name} " + "}" if with_preview_text else None,
56
74
  )
57
75
  new_widget.font = DEFAULT_FONT
58
76
  new_widget.font_size = DEFAULT_FONT_SIZE
59
77
  new_widget.font_color = PREVIEW_FONT_COLOR
60
- new_widget.preview = True
78
+ new_widget.preview = with_preview_text
79
+ new_widget.border_color = handle_color([0, 0, 0])
80
+ new_widget.border_width = 1
81
+ new_widget.render_widget = True
61
82
 
62
83
  return new_widget
63
84
 
@@ -155,6 +176,25 @@ def traverse_pattern(
155
176
  return None
156
177
 
157
178
 
179
+ def extract_widget_property(
180
+ widget: Union[dict, DictionaryObject],
181
+ patterns: list,
182
+ default_value: Any,
183
+ func_before_return: Union[Callable, None],
184
+ ) -> Any:
185
+ """Returns a property value given a PDF widget dict and a pattern."""
186
+
187
+ result = default_value
188
+
189
+ for pattern in patterns:
190
+ value = traverse_pattern(pattern, widget)
191
+ if value:
192
+ result = func_before_return(value) if func_before_return else value
193
+ break
194
+
195
+ return result
196
+
197
+
158
198
  def generate_unique_suffix() -> str:
159
199
  """Generates a unique suffix string for widgets during form merging."""
160
200
 
@@ -72,6 +72,36 @@ def draw_text(*args) -> None:
72
72
  canvas.restoreState()
73
73
 
74
74
 
75
+ def draw_rect(*args) -> None:
76
+ """Draws a rectangle on the watermark."""
77
+
78
+ canvas = args[0]
79
+ x = args[1]
80
+ y = args[2]
81
+ width = args[3]
82
+ height = args[4]
83
+
84
+ canvas.saveState()
85
+ stroke, fill = set_border_and_background_styles(*args)
86
+ canvas.rect(x, y, width, height, stroke=stroke, fill=fill)
87
+ canvas.restoreState()
88
+
89
+
90
+ def draw_ellipse(*args) -> None:
91
+ """Draws an ellipse on the watermark."""
92
+
93
+ canvas = args[0]
94
+ x1 = args[1]
95
+ y1 = args[2]
96
+ x2 = args[3]
97
+ y2 = args[4]
98
+
99
+ canvas.saveState()
100
+ stroke, fill = set_border_and_background_styles(*args)
101
+ canvas.ellipse(x1, y1, x2, y2, stroke=stroke, fill=fill)
102
+ canvas.restoreState()
103
+
104
+
75
105
  def draw_line(*args) -> None:
76
106
  """Draws a line on the watermark."""
77
107
 
@@ -80,26 +110,36 @@ def draw_line(*args) -> None:
80
110
  src_y = args[2]
81
111
  dest_x = args[3]
82
112
  dest_y = args[4]
83
- r = args[5]
84
- g = args[6]
85
- b = args[7]
86
113
 
87
114
  canvas.saveState()
88
- canvas.setStrokeColorRGB(r, g, b)
115
+ set_border_and_background_styles(*args)
89
116
  canvas.line(src_x, src_y, dest_x, dest_y)
90
117
  canvas.restoreState()
91
118
 
92
119
 
93
- def draw_rect(*args) -> None:
94
- """Draws a rectangle on the watermark."""
120
+ def set_border_and_background_styles(*args) -> tuple:
121
+ """Sets colors for both border and background before drawing."""
95
122
 
96
123
  canvas = args[0]
97
- x = args[1]
98
- y = args[2]
99
- width = args[3]
100
- height = args[4]
124
+ border_color = args[5]
125
+ background_color = args[6]
126
+ border_width = args[7]
127
+ dash_array = args[8]
101
128
 
102
- canvas.rect(x, y, width, height)
129
+ stroke = 0
130
+ fill = 0
131
+ if border_color is not None and border_width:
132
+ canvas.setStrokeColor(border_color)
133
+ canvas.setLineWidth(border_width)
134
+ stroke = 1
135
+ if background_color is not None:
136
+ canvas.setFillColor(background_color)
137
+ fill = 1
138
+
139
+ if dash_array is not None:
140
+ canvas.setDash(array=dash_array)
141
+
142
+ return stroke, fill
103
143
 
104
144
 
105
145
  def draw_image(*args) -> None:
@@ -159,6 +199,9 @@ def create_watermarks_and_draw(
159
199
  elif action_type == "rect":
160
200
  for each in actions:
161
201
  draw_rect(*([canvas, *each]))
202
+ elif action_type == "ellipse":
203
+ for each in actions:
204
+ draw_ellipse(*([canvas, *each]))
162
205
 
163
206
  canvas.save()
164
207
  buff.seek(0)
@@ -10,9 +10,8 @@ from reportlab.lib.colors import Color
10
10
  from reportlab.pdfgen.canvas import Canvas
11
11
 
12
12
  from ..constants import Annots
13
- from ..patterns import NON_ACRO_FORM_PARAM_TO_FUNC
14
- from ..template import get_widget_key
15
- from ..utils import stream_to_io
13
+ from ..patterns import NON_ACRO_FORM_PARAM_TO_FUNC, WIDGET_KEY_PATTERNS
14
+ from ..utils import extract_widget_property, stream_to_io
16
15
 
17
16
 
18
17
  class Widget:
@@ -101,7 +100,9 @@ def handle_non_acro_form_params(pdf: bytes, key: str, params: list) -> bytes:
101
100
  for page in out.pages:
102
101
  for annot in page.get(Annots, []): # noqa
103
102
  annot = cast(DictionaryObject, annot.get_object())
104
- _key = get_widget_key(annot.get_object())
103
+ _key = extract_widget_property(
104
+ annot.get_object(), WIDGET_KEY_PATTERNS, None, str
105
+ )
105
106
 
106
107
  if _key == key:
107
108
  for param in params:
@@ -18,7 +18,7 @@ from .middleware.dropdown import Dropdown
18
18
  from .middleware.text import Text
19
19
  from .template import (build_widgets, dropdown_to_text,
20
20
  set_character_x_paddings, update_text_field_attributes,
21
- update_widget_keys, widget_rect_watermarks)
21
+ update_widget_keys)
22
22
  from .utils import (generate_unique_suffix, get_page_streams, merge_two_pdfs,
23
23
  preview_widget_to_draw, remove_all_widgets)
24
24
  from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
@@ -52,7 +52,7 @@ class FormWrapper:
52
52
  ) -> FormWrapper:
53
53
  """Fills a PDF form."""
54
54
 
55
- widgets = build_widgets(self.stream, False) if self.stream else {}
55
+ widgets = build_widgets(self.stream, False, False) if self.stream else {}
56
56
 
57
57
  for key, value in data.items():
58
58
  if key in widgets:
@@ -72,10 +72,11 @@ class PdfWrapper(FormWrapper):
72
72
  """A class to represent a PDF form."""
73
73
 
74
74
  USER_PARAMS = [
75
- "global_font",
76
- "global_font_size",
77
- "global_font_color",
78
- "use_full_widget_name",
75
+ ("global_font", None),
76
+ ("global_font_size", None),
77
+ ("global_font_color", None),
78
+ ("use_full_widget_name", False),
79
+ ("render_widgets", True),
79
80
  ]
80
81
 
81
82
  def __init__(
@@ -89,8 +90,8 @@ class PdfWrapper(FormWrapper):
89
90
  self.widgets = {}
90
91
  self._keys_to_update = []
91
92
 
92
- for each in self.USER_PARAMS:
93
- setattr(self, each, kwargs.get(each))
93
+ for attr, default in self.USER_PARAMS:
94
+ setattr(self, attr, kwargs.get(attr, default))
94
95
 
95
96
  self._init_helper()
96
97
 
@@ -99,7 +100,11 @@ class PdfWrapper(FormWrapper):
99
100
 
100
101
  refresh_not_needed = {}
101
102
  new_widgets = (
102
- build_widgets(self.read(), getattr(self, "use_full_widget_name"))
103
+ build_widgets(
104
+ self.read(),
105
+ getattr(self, "use_full_widget_name"),
106
+ getattr(self, "render_widgets"),
107
+ )
103
108
  if self.read()
104
109
  else {}
105
110
  )
@@ -141,7 +146,7 @@ class PdfWrapper(FormWrapper):
141
146
 
142
147
  return [
143
148
  self.__class__(
144
- each, **{param: getattr(self, param) for param in self.USER_PARAMS}
149
+ each, **{param: getattr(self, param) for param, _ in self.USER_PARAMS}
145
150
  )
146
151
  for each in get_page_streams(self.stream)
147
152
  ]
@@ -180,15 +185,12 @@ class PdfWrapper(FormWrapper):
180
185
  """Inspects all supported widgets' names for the PDF form."""
181
186
 
182
187
  return remove_all_widgets(
183
- merge_watermarks_with_pdf(
184
- fill(
185
- self.stream,
186
- {
187
- key: preview_widget_to_draw(value)
188
- for key, value in self.widgets.items()
189
- },
190
- ),
191
- widget_rect_watermarks(self.read()),
188
+ fill(
189
+ self.stream,
190
+ {
191
+ key: preview_widget_to_draw(value, True)
192
+ for key, value in self.widgets.items()
193
+ },
192
194
  )
193
195
  )
194
196
 
@@ -198,8 +200,14 @@ class PdfWrapper(FormWrapper):
198
200
  """Inspects a coordinate grid of the PDF."""
199
201
 
200
202
  self.stream = generate_coordinate_grid(
201
- merge_watermarks_with_pdf(
202
- remove_all_widgets(self.read()), widget_rect_watermarks(self.read())
203
+ remove_all_widgets(
204
+ fill(
205
+ self.stream,
206
+ {
207
+ key: preview_widget_to_draw(value, False)
208
+ for key, value in self.widgets.items()
209
+ },
210
+ )
203
211
  ),
204
212
  color,
205
213
  margin,
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 1.5.6
3
+ Version: 2.0.0
4
4
  Summary: The Python library for PDF forms.
5
5
  Home-page: https://github.com/chinapandaman/PyPDFForm
6
6
  Author: Jinge Li
@@ -29,6 +29,7 @@ Dynamic: classifier
29
29
  Dynamic: description
30
30
  Dynamic: description-content-type
31
31
  Dynamic: home-page
32
+ Dynamic: license-file
32
33
  Dynamic: requires-dist
33
34
  Dynamic: requires-python
34
35
  Dynamic: summary
@@ -43,6 +44,15 @@ Dynamic: summary
43
44
  <a href="https://pypistats.org/packages/pypdfform"><img src="https://img.shields.io/pypi/dm/pypdfform?logo=pypi&logoColor=white&label=downloads&labelColor=black&color=blue&style=for-the-badge"></a>
44
45
  </p>
45
46
 
47
+ ## Important Announcements
48
+
49
+ Hello fellow Python developers! With the release of v2.0.0, there are some important changes I'm making to the library:
50
+
51
+ * Since I started developing the library, versioning releases has been quite unorthodox, and there isn't any convention I followed. Starting with v2.0.0, PyPDFForm will version releases following the conventions defined by [Semantic Versioning](https://semver.org/).
52
+ * PyPDFForm now renders PDF form widgets! Ever since its ancestral stage, the library has only been able to render the data you filled into a PDF form. Now if you fill a PDF form using `PdfWrapper`, the result will render the whole widget instead of just the value that got filled. If you would like to disable this behavior, please refer to the docs [here](https://chinapandaman.github.io/PyPDFForm/fill/#disable-rendering-widgets).
53
+
54
+ Happy hacking!
55
+
46
56
  ## Introduction
47
57
 
48
58
  PyPDFForm is a free and open source pure-Python 3 library for PDF form processing. It contains the essential
@@ -8,6 +8,15 @@
8
8
  <a href="https://pypistats.org/packages/pypdfform"><img src="https://img.shields.io/pypi/dm/pypdfform?logo=pypi&logoColor=white&label=downloads&labelColor=black&color=blue&style=for-the-badge"></a>
9
9
  </p>
10
10
 
11
+ ## Important Announcements
12
+
13
+ Hello fellow Python developers! With the release of v2.0.0, there are some important changes I'm making to the library:
14
+
15
+ * Since I started developing the library, versioning releases has been quite unorthodox, and there isn't any convention I followed. Starting with v2.0.0, PyPDFForm will version releases following the conventions defined by [Semantic Versioning](https://semver.org/).
16
+ * PyPDFForm now renders PDF form widgets! Ever since its ancestral stage, the library has only been able to render the data you filled into a PDF form. Now if you fill a PDF form using `PdfWrapper`, the result will render the whole widget instead of just the value that got filled. If you would like to disable this behavior, please refer to the docs [here](https://chinapandaman.github.io/PyPDFForm/fill/#disable-rendering-widgets).
17
+
18
+ Happy hacking!
19
+
11
20
  ## Introduction
12
21
 
13
22
  PyPDFForm is a free and open source pure-Python 3 library for PDF form processing. It contains the essential
File without changes
File without changes
File without changes
File without changes