PyPDFForm 1.5.7__py3-none-any.whl → 2.0.1__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
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Contains any object users might need."""
3
3
 
4
- __version__ = "1.5.7"
4
+ __version__ = "2.0.1"
5
5
 
6
6
  from .wrapper import FormWrapper, PdfWrapper
7
7
 
PyPDFForm/constants.py CHANGED
@@ -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)
PyPDFForm/coordinate.py CHANGED
@@ -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
PyPDFForm/filler.py CHANGED
@@ -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:
PyPDFForm/font.py CHANGED
@@ -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:
PyPDFForm/patterns.py CHANGED
@@ -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
@@ -23,6 +24,10 @@ WIDGET_TYPE_PATTERNS = [
23
24
  ({FT: Sig},),
24
25
  Signature,
25
26
  ),
27
+ (
28
+ ({Parent: {FT: Sig}},),
29
+ Signature,
30
+ ),
26
31
  (
27
32
  ({FT: Tx},),
28
33
  Text,
@@ -94,6 +99,20 @@ BUTTON_STYLE_PATTERNS = [
94
99
  {MK: {CA: True}},
95
100
  ]
96
101
 
102
+ BORDER_COLOR_PATTERNS = [
103
+ {MK: {BC: True}},
104
+ ]
105
+
106
+ BACKGROUND_COLOR_PATTERNS = [
107
+ {MK: {BG: True}},
108
+ ]
109
+
110
+ BORDER_WIDTH_PATTERNS = [{BS: {W: True}}]
111
+
112
+ BORDER_STYLE_PATTERNS = [{BS: {S: True}}]
113
+
114
+ BORDER_DASH_ARRAY_PATTERNS = [{BS: {D: True}}]
115
+
97
116
 
98
117
  def simple_update_checkbox_value(annot: DictionaryObject, check: bool = False) -> None:
99
118
  """Patterns to update values for checkbox annotations."""
PyPDFForm/template.py CHANGED
@@ -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,19 +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
63
  if use_full_widget_name:
59
64
  _widget.full_name = get_widget_full_key(widget)
60
- _widget.desc = get_widget_description(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
+
61
86
  if isinstance(_widget, Text):
62
87
  _widget.max_length = get_text_field_max_length(widget)
63
88
  if _widget.max_length is not None and is_text_field_comb(widget):
64
89
  _widget.comb = True
65
90
 
66
91
  if isinstance(_widget, (Checkbox, Radio)):
67
- _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
+ )
68
98
 
69
99
  if isinstance(_widget, Dropdown):
70
100
  _widget.choices = get_dropdown_choices(widget)
@@ -82,32 +112,14 @@ def build_widgets(
82
112
  return results
83
113
 
84
114
 
85
- def widget_rect_watermarks(pdf: bytes) -> List[bytes]:
86
- """Draws the rectangular border of each widget and returns watermarks."""
87
-
88
- watermarks = []
89
-
90
- for page, widgets in get_widgets_by_page(pdf).items():
91
- to_draw = []
92
- for widget in widgets:
93
- rect = widget[Rect]
94
- x = rect[0]
95
- y = rect[1]
96
- width = abs(rect[0] - rect[2])
97
- height = abs(rect[1] - rect[3])
98
-
99
- to_draw.append([x, y, width, height])
100
- watermarks.append(
101
- create_watermarks_and_draw(pdf, page, "rect", to_draw)[page - 1]
102
- )
103
-
104
- return watermarks
105
-
106
-
107
115
  def dropdown_to_text(dropdown: Dropdown) -> Text:
108
116
  """Converts a dropdown widget to a text widget."""
109
117
 
110
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
111
123
 
112
124
  if dropdown.value is not None:
113
125
  result.value = (
@@ -127,7 +139,7 @@ def update_text_field_attributes(
127
139
 
128
140
  for _widgets in get_widgets_by_page(template_stream).values():
129
141
  for _widget in _widgets:
130
- key = get_widget_key(_widget)
142
+ key = extract_widget_property(_widget, WIDGET_KEY_PATTERNS, None, str)
131
143
 
132
144
  if isinstance(widgets[key], Text):
133
145
  should_adjust_font_size = False
@@ -184,25 +196,13 @@ def get_widgets_by_page(pdf: bytes) -> Dict[int, List[dict]]:
184
196
  return result
185
197
 
186
198
 
187
- def get_widget_key(widget: dict) -> Union[str, list, None]:
188
- """Finds a PDF widget's annotated key by pattern matching."""
189
-
190
- result = None
191
- for pattern in WIDGET_KEY_PATTERNS:
192
- value = traverse_pattern(pattern, widget)
193
- if value:
194
- result = value
195
- break
196
- return result
197
-
198
-
199
199
  def get_widget_full_key(widget: dict) -> Union[str, None]:
200
200
  """
201
201
  Returns a PDF widget's full annotated key by prepending its
202
202
  parent widget's key.
203
203
  """
204
204
 
205
- key = get_widget_key(widget)
205
+ key = extract_widget_property(widget, WIDGET_KEY_PATTERNS, None, str)
206
206
 
207
207
  if (
208
208
  Parent in widget
@@ -214,18 +214,6 @@ def get_widget_full_key(widget: dict) -> Union[str, None]:
214
214
  return None
215
215
 
216
216
 
217
- def get_widget_alignment(widget: dict) -> Union[str, list, None]:
218
- """Finds a PDF widget's alignment by pattern matching."""
219
-
220
- result = None
221
- for pattern in WIDGET_ALIGNMENT_PATTERNS:
222
- value = traverse_pattern(pattern, widget)
223
- if value:
224
- result = value
225
- break
226
- return result
227
-
228
-
229
217
  def construct_widget(widget: dict, key: str) -> Union[WIDGET_TYPES, None]:
230
218
  """Finds a PDF widget's annotated type by pattern matching."""
231
219
 
@@ -247,26 +235,10 @@ def get_text_field_max_length(widget: dict) -> Union[int, None]:
247
235
  return int(widget[MaxLen]) or None if MaxLen in widget else None
248
236
 
249
237
 
250
- def get_widget_description(widget: dict) -> Union[str, None]:
251
- """Returns the description of the widget if presented or None."""
252
-
253
- result = None
254
- for pattern in WIDGET_DESCRIPTION_PATTERNS:
255
- value = traverse_pattern(pattern, widget)
256
- if value:
257
- result = str(value)
258
- break
259
- return result
260
-
261
-
262
238
  def check_field_flag_bit(widget: dict, bit: int) -> bool:
263
239
  """Checks if a bit is set in a widget's field flag."""
264
240
 
265
- field_flag = None
266
- for pattern in TEXT_FIELD_FLAG_PATTERNS:
267
- field_flag = traverse_pattern(pattern, widget)
268
- if field_flag is not None:
269
- break
241
+ field_flag = extract_widget_property(widget, TEXT_FIELD_FLAG_PATTERNS, None, int)
270
242
 
271
243
  if field_flag is None:
272
244
  return False
@@ -289,27 +261,12 @@ def is_text_multiline(widget: dict) -> bool:
289
261
  def get_dropdown_choices(widget: dict) -> Union[Tuple[str, ...], None]:
290
262
  """Returns string options of a dropdown field."""
291
263
 
292
- result = None
293
- for pattern in DROPDOWN_CHOICE_PATTERNS:
294
- choices = traverse_pattern(pattern, widget)
295
- if choices:
296
- result = tuple(
297
- (each if isinstance(each, str) else str(each[1])) for each in choices
298
- )
299
- break
300
-
301
- return result
302
-
303
-
304
- def get_button_style(widget: dict) -> Union[str, None]:
305
- """Returns the button style of a checkbox or radiobutton."""
306
-
307
- for pattern in BUTTON_STYLE_PATTERNS:
308
- style = traverse_pattern(pattern, widget)
309
- if style is not None:
310
- return str(style)
311
-
312
- 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
+ )
313
270
 
314
271
 
315
272
  def get_char_rect_width(widget: dict, widget_middleware: Text) -> float:
@@ -463,7 +420,9 @@ def update_widget_keys(
463
420
  for page in out.pages:
464
421
  for annot in page.get(Annots, []): # noqa
465
422
  annot = cast(DictionaryObject, annot.get_object())
466
- key = get_widget_key(annot.get_object())
423
+ key = extract_widget_property(
424
+ annot.get_object(), WIDGET_KEY_PATTERNS, None, str
425
+ )
467
426
 
468
427
  widget = widgets.get(key)
469
428
  if widget is None:
PyPDFForm/utils.py CHANGED
@@ -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,23 @@ 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
+ elif len(color) == 4:
43
+ result = CMYKColor(
44
+ cyan=color[0], magenta=color[1], yellow=color[2], black=color[3]
45
+ )
46
+
47
+ return result
48
+
49
+
31
50
  def checkbox_radio_to_draw(
32
51
  widget: Union[Checkbox, Radio], font_size: Union[float, int]
33
52
  ) -> Text:
@@ -47,17 +66,20 @@ def checkbox_radio_to_draw(
47
66
  return new_widget
48
67
 
49
68
 
50
- def preview_widget_to_draw(widget: WIDGET_TYPES) -> Text:
69
+ def preview_widget_to_draw(widget: WIDGET_TYPES, with_preview_text: bool) -> Text:
51
70
  """Converts a widget to a preview text widget."""
52
71
 
53
72
  new_widget = Text(
54
73
  name=widget.name,
55
- value="{" + f" {widget.name} " + "}",
74
+ value="{" + f" {widget.name} " + "}" if with_preview_text else None,
56
75
  )
57
76
  new_widget.font = DEFAULT_FONT
58
77
  new_widget.font_size = DEFAULT_FONT_SIZE
59
78
  new_widget.font_color = PREVIEW_FONT_COLOR
60
- new_widget.preview = True
79
+ new_widget.preview = with_preview_text
80
+ new_widget.border_color = handle_color([0, 0, 0])
81
+ new_widget.border_width = 1
82
+ new_widget.render_widget = True
61
83
 
62
84
  return new_widget
63
85
 
@@ -155,6 +177,25 @@ def traverse_pattern(
155
177
  return None
156
178
 
157
179
 
180
+ def extract_widget_property(
181
+ widget: Union[dict, DictionaryObject],
182
+ patterns: list,
183
+ default_value: Any,
184
+ func_before_return: Union[Callable, None],
185
+ ) -> Any:
186
+ """Returns a property value given a PDF widget dict and a pattern."""
187
+
188
+ result = default_value
189
+
190
+ for pattern in patterns:
191
+ value = traverse_pattern(pattern, widget)
192
+ if value:
193
+ result = func_before_return(value) if func_before_return else value
194
+ break
195
+
196
+ return result
197
+
198
+
158
199
  def generate_unique_suffix() -> str:
159
200
  """Generates a unique suffix string for widgets during form merging."""
160
201
 
PyPDFForm/watermark.py CHANGED
@@ -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)
PyPDFForm/widgets/base.py CHANGED
@@ -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:
PyPDFForm/wrapper.py CHANGED
@@ -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.7
3
+ Version: 2.0.1
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
@@ -0,0 +1,30 @@
1
+ PyPDFForm/__init__.py,sha256=FOdEbs3j91EszZT-dpiG6soiBeSlS8Ue9ftcRxV6DTQ,178
2
+ PyPDFForm/adapter.py,sha256=qt0lqd7zRzaMHD2As47oiIhungO3AyWbAkxx1ZAZi9A,862
3
+ PyPDFForm/constants.py,sha256=2ti8lDF2K965jfom3l9jN_5cKxjEYyQNjr1VQlYvpNU,1869
4
+ PyPDFForm/coordinate.py,sha256=FtVkpmBYtBfwDLkr5o2VxifaWvEV8MKbRoTdLrcPHR0,9082
5
+ PyPDFForm/filler.py,sha256=oxSDZWsaXLI085W1ALEqBRuKS0tBbunoX1WzEztMYBk,9765
6
+ PyPDFForm/font.py,sha256=RHTZAYHfmIZedeNZwNlSg8aNUCyBsJA0t_hhHiV9GoQ,5566
7
+ PyPDFForm/image.py,sha256=IsmT2_LAvoGTxbsrelXnP8B4vUKetOVwp6oiE9MGnU8,835
8
+ PyPDFForm/patterns.py,sha256=7V3Xmpszt-mHaQ18MJKEW-uKm843foJnnQiKl31dG74,5772
9
+ PyPDFForm/template.py,sha256=IQDLhFt7sVWNaY0SRRosWIk_nvMVFDex7N7RGZsQcvE,15197
10
+ PyPDFForm/utils.py,sha256=MKNlPIUDtybmL2TPqJNVxvbKHXr_9LYSayZogGGcrAE,5998
11
+ PyPDFForm/watermark.py,sha256=m3PXsNGJdW_DWKegHtQmWjCcDEbFiDPw_wyL_GNyKLI,5926
12
+ PyPDFForm/wrapper.py,sha256=zzf2VkfkviA51dKiVSkCHgCUwBC4yxADOpvb_VRLbtI,11909
13
+ PyPDFForm/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ PyPDFForm/middleware/base.py,sha256=cfCzpbFkvCxQlFwmZ8BV-HiJ_8YhXvbBp45kUWEudQs,1351
15
+ PyPDFForm/middleware/checkbox.py,sha256=GQPaKlPo6IX14N4tLLNf2hyJZBZmERs_9cWiwhvLg0Q,1376
16
+ PyPDFForm/middleware/dropdown.py,sha256=_RXs9o3vWH_a402zhTOYE_mcWbtFfVU7bReYcEFAIyk,793
17
+ PyPDFForm/middleware/image.py,sha256=tQrLKZUKOz90jKxuUQpTP4Awuvtf0I92LeANH0k6mzQ,212
18
+ PyPDFForm/middleware/radio.py,sha256=4Twv2uOW_GhVV7PHR3RFm4SDyGBEKqvL1cqj7i_Zi7Q,891
19
+ PyPDFForm/middleware/signature.py,sha256=1ZOkBKectZ7kptJMQ-EuOA_xI7j8dlwseo2p2Z0FS2o,1138
20
+ PyPDFForm/middleware/text.py,sha256=7HFHy_lfBFfYbSUzgVKovSoShbNP6L7tiILIw-9_kpo,1549
21
+ PyPDFForm/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ PyPDFForm/widgets/base.py,sha256=2v0y3vb5Dqy8G4oLm-xHkqi6GNWV1nnL8GEIe8DEU0c,3427
23
+ PyPDFForm/widgets/checkbox.py,sha256=MGB6NGI-XMBz4j2erykgYOCd1pswvthuQ6UFowQOd4o,503
24
+ PyPDFForm/widgets/dropdown.py,sha256=2_R5xcLUWAL0G4raVgWWtE7TJdiWJITay-Gtl8YI2QI,705
25
+ PyPDFForm/widgets/text.py,sha256=sCB8CQAjh30QOGr8FTIDSk3X6dXSHJztOz6YkKEBDss,691
26
+ pypdfform-2.0.1.dist-info/licenses/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
27
+ pypdfform-2.0.1.dist-info/METADATA,sha256=rFgDjLUUyEIR7XDkE3xVRHwVgg58YNOwACC84jt1GVY,5265
28
+ pypdfform-2.0.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
29
+ pypdfform-2.0.1.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
30
+ pypdfform-2.0.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,30 +0,0 @@
1
- PyPDFForm/__init__.py,sha256=rnGnnQOhm4df5ycwLI_K3wFB0pxer6koep5nLg_bnQU,178
2
- PyPDFForm/adapter.py,sha256=qt0lqd7zRzaMHD2As47oiIhungO3AyWbAkxx1ZAZi9A,862
3
- PyPDFForm/constants.py,sha256=iHCwbnypwgMe5T_hMZIJJixySkOlXgtXuNlI2UUptJ8,1775
4
- PyPDFForm/coordinate.py,sha256=uZGGFiTUIrD9VX-bzgUpKlFAzK7VJppW1corGXvT-pY,7712
5
- PyPDFForm/filler.py,sha256=BWj3CpRWTrNCNBrMMptdxW9Nac6pnQCl3xOw5l0QPy8,7597
6
- PyPDFForm/font.py,sha256=TbFLY4G72d7cQ3VG2g4HaYKROzQPQUmh6-8ynN6hzXY,5713
7
- PyPDFForm/image.py,sha256=IsmT2_LAvoGTxbsrelXnP8B4vUKetOVwp6oiE9MGnU8,835
8
- PyPDFForm/patterns.py,sha256=Z548TjpQpaVepctvlRYe0-2i7Aws96fo-ncG82CUX14,5423
9
- PyPDFForm/template.py,sha256=vvXPUkHm8WvbMBpvi6H4V3ZgvCdPtpAlJ9rDCVQUxQg,15541
10
- PyPDFForm/utils.py,sha256=hMWGLyTZJSQG4gItlnQLq8RxnV4bM2sL6_-RmLqSwtE,4721
11
- PyPDFForm/watermark.py,sha256=eDGQKXc4j1uUhAAE1VeQXGHQ9FO4h9mSKExv3gnNTZg,4746
12
- PyPDFForm/wrapper.py,sha256=fxvwP-bGqq7yAFjUr4PtHBUVU_f0cdrQMtmamOPrk5s,11716
13
- PyPDFForm/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- PyPDFForm/middleware/base.py,sha256=Sjo8O9RrdBu73r4BCMPuLXnrl1hzjb1dlTOPeIgrhOU,1150
15
- PyPDFForm/middleware/checkbox.py,sha256=0EWFECXYdz9SNxgGe1QCcRrdxLVUIJpzRYwKfGRA9e4,1346
16
- PyPDFForm/middleware/dropdown.py,sha256=_RXs9o3vWH_a402zhTOYE_mcWbtFfVU7bReYcEFAIyk,793
17
- PyPDFForm/middleware/image.py,sha256=tQrLKZUKOz90jKxuUQpTP4Awuvtf0I92LeANH0k6mzQ,212
18
- PyPDFForm/middleware/radio.py,sha256=KfwOtlnMk5B2y8KbqD-ONN5AbkHa4TF5NzdcZlbNyDk,598
19
- PyPDFForm/middleware/signature.py,sha256=1ZOkBKectZ7kptJMQ-EuOA_xI7j8dlwseo2p2Z0FS2o,1138
20
- PyPDFForm/middleware/text.py,sha256=7HFHy_lfBFfYbSUzgVKovSoShbNP6L7tiILIw-9_kpo,1549
21
- PyPDFForm/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- PyPDFForm/widgets/base.py,sha256=nb6HEfRdWSjZc_Th1XI62PIlgREn_JiApGC-epb6jAg,3348
23
- PyPDFForm/widgets/checkbox.py,sha256=MGB6NGI-XMBz4j2erykgYOCd1pswvthuQ6UFowQOd4o,503
24
- PyPDFForm/widgets/dropdown.py,sha256=2_R5xcLUWAL0G4raVgWWtE7TJdiWJITay-Gtl8YI2QI,705
25
- PyPDFForm/widgets/text.py,sha256=sCB8CQAjh30QOGr8FTIDSk3X6dXSHJztOz6YkKEBDss,691
26
- pypdfform-1.5.7.dist-info/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
27
- pypdfform-1.5.7.dist-info/METADATA,sha256=E2qQcC1PGsUdD_M7rUSqxPimQAlR-tpDpakRe6iGSQQ,4376
28
- pypdfform-1.5.7.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
29
- pypdfform-1.5.7.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
30
- pypdfform-1.5.7.dist-info/RECORD,,