PyPDFForm 1.4.11__py3-none-any.whl → 1.4.13__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,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Contains any object users might need."""
3
3
 
4
- __version__ = "1.4.11"
4
+ __version__ = "1.4.13"
5
5
 
6
- from .wrapper import PdfWrapper, PyPDFForm
6
+ from .wrapper import FormWrapper, PdfWrapper, PyPDFForm
PyPDFForm/constants.py CHANGED
@@ -26,6 +26,7 @@ WIDGET_TYPES = Union[Text, Checkbox, Radio, Dropdown, Signature]
26
26
 
27
27
  DEPRECATION_NOTICE = "{} will be deprecated soon. Use {} instead."
28
28
 
29
+ ANNOTATION_KEY = "/Annots"
29
30
  ANNOTATION_FIELD_KEY = "/T"
30
31
  ANNOTATION_RECTANGLE_KEY = "/Rect"
31
32
  SUBTYPE_KEY = "/Subtype"
@@ -34,6 +35,8 @@ WIDGET_TYPE_KEY = "/FT"
34
35
  PARENT_KEY = "/Parent"
35
36
  FIELD_FLAG_KEY = "/Ff"
36
37
  TEXT_FIELD_IDENTIFIER = "/Tx"
38
+ TEXT_VALUE_IDENTIFIER = "/V"
39
+ TEXT_VALUE_SHOW_UP_IDENTIFIER = "/AP"
37
40
  SIGNATURE_FIELD_IDENTIFIER = "/Sig"
38
41
  TEXT_FIELD_APPEARANCE_IDENTIFIER = "/DA"
39
42
  SELECTABLE_IDENTIFIER = "/Btn"
@@ -43,6 +46,7 @@ CHOICE_FIELD_IDENTIFIER = "/Ch"
43
46
  CHOICES_IDENTIFIER = "/Opt"
44
47
  BUTTON_IDENTIFIER = "/MK"
45
48
  BUTTON_STYLE_IDENTIFIER = "/CA"
49
+ SELECTED_IDENTIFIER = "/AS"
46
50
 
47
51
  # Field flag bits
48
52
  MULTILINE = 1 << 12
@@ -60,9 +64,11 @@ NEW_LINE_SYMBOL = "\n"
60
64
  DEFAULT_CHECKBOX_STYLE = "\u2713"
61
65
  DEFAULT_RADIO_STYLE = "\u25CF"
62
66
  BUTTON_STYLES = {
63
- "4": "\u2713",
64
- "5": "\u00D7",
65
- "l": "\u25CF",
67
+ "4": "\u2713", # check
68
+ "5": "\u00D7", # cross
69
+ "l": "\u25CF", # circle
66
70
  }
67
71
 
72
+ CHECKBOX_SELECT = "/Yes"
73
+
68
74
  COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO = DEFAULT_FONT_SIZE / 100
PyPDFForm/filler.py CHANGED
@@ -1,9 +1,15 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Contains helpers for filling a PDF form."""
3
3
 
4
- from typing import Dict
4
+ from io import BytesIO
5
+ from typing import Dict, cast
5
6
 
6
- from .constants import WIDGET_TYPES
7
+ from pypdf import PdfReader, PdfWriter
8
+ from pypdf.generic import DictionaryObject, NameObject, TextStringObject
9
+
10
+ from .constants import (ANNOTATION_KEY, CHECKBOX_SELECT, SELECTED_IDENTIFIER,
11
+ TEXT_VALUE_IDENTIFIER, TEXT_VALUE_SHOW_UP_IDENTIFIER,
12
+ WIDGET_TYPES)
7
13
  from .coordinate import (get_draw_checkbox_radio_coordinates,
8
14
  get_draw_sig_coordinates_resolutions,
9
15
  get_draw_text_coordinates,
@@ -11,10 +17,12 @@ from .coordinate import (get_draw_checkbox_radio_coordinates,
11
17
  from .font import checkbox_radio_font_size
12
18
  from .image import any_image_to_jpg
13
19
  from .middleware.checkbox import Checkbox
20
+ from .middleware.dropdown import Dropdown
14
21
  from .middleware.radio import Radio
15
22
  from .middleware.signature import Signature
23
+ from .middleware.text import Text
16
24
  from .template import get_widget_key, get_widgets_by_page
17
- from .utils import checkbox_radio_to_draw
25
+ from .utils import checkbox_radio_to_draw, stream_to_io
18
26
  from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
19
27
 
20
28
 
@@ -44,10 +52,14 @@ def fill(
44
52
  _to_draw = x = y = None
45
53
 
46
54
  if isinstance(widgets[key], (Checkbox, Radio)):
47
- font_size = checkbox_radio_font_size(_widget)
55
+ font_size = (
56
+ checkbox_radio_font_size(_widget)
57
+ if widgets[key].size is None
58
+ else widgets[key].size
59
+ )
48
60
  _to_draw = checkbox_radio_to_draw(widgets[key], font_size)
49
61
  x, y = get_draw_checkbox_radio_coordinates(_widget, _to_draw)
50
- if isinstance(widgets[key], Checkbox) and widgets[key].value:
62
+ if type(widgets[key]) is Checkbox and widgets[key].value:
51
63
  text_needs_to_be_drawn = True
52
64
  elif isinstance(widgets[key], Radio):
53
65
  if key not in radio_button_tracker:
@@ -113,3 +125,55 @@ def fill(
113
125
  result = merge_watermarks_with_pdf(result, image_watermarks)
114
126
 
115
127
  return result
128
+
129
+
130
+ def simple_fill(
131
+ template: bytes,
132
+ widgets: Dict[str, WIDGET_TYPES],
133
+ ) -> bytes:
134
+ """Fills a PDF form in place."""
135
+
136
+ pdf = PdfReader(stream_to_io(template))
137
+ out = PdfWriter()
138
+ out.append(pdf)
139
+
140
+ radio_button_tracker = {}
141
+
142
+ for page in out.pages:
143
+ for annot in page.get(ANNOTATION_KEY, []): # noqa
144
+ annot = cast(DictionaryObject, annot.get_object())
145
+ key = get_widget_key(annot.get_object())
146
+
147
+ widget = widgets.get(key)
148
+ if widget is None:
149
+ continue
150
+
151
+ if isinstance(widget, Checkbox) and widget.value is True:
152
+ annot[NameObject(SELECTED_IDENTIFIER)] = NameObject(CHECKBOX_SELECT)
153
+ elif isinstance(widget, Radio):
154
+ if key not in radio_button_tracker:
155
+ radio_button_tracker[key] = 0
156
+ radio_button_tracker[key] += 1
157
+ if widget.value == radio_button_tracker[key] - 1:
158
+ annot[NameObject(SELECTED_IDENTIFIER)] = NameObject(
159
+ f"/{widget.value}"
160
+ )
161
+ elif isinstance(widget, Dropdown) and widget.value is not None:
162
+ annot[NameObject(TEXT_VALUE_IDENTIFIER)] = TextStringObject(
163
+ widget.choices[widget.value]
164
+ )
165
+ annot[NameObject(TEXT_VALUE_SHOW_UP_IDENTIFIER)] = TextStringObject(
166
+ widget.choices[widget.value]
167
+ )
168
+ elif isinstance(widget, Text) and widget.value:
169
+ annot[NameObject(TEXT_VALUE_IDENTIFIER)] = TextStringObject(
170
+ widget.value
171
+ )
172
+ annot[NameObject(TEXT_VALUE_SHOW_UP_IDENTIFIER)] = TextStringObject(
173
+ widget.value
174
+ )
175
+
176
+ with BytesIO() as f:
177
+ out.write(f)
178
+ f.seek(0)
179
+ return f.read()
@@ -1,12 +1,20 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Contains checkbox middleware."""
3
3
 
4
+ from typing import Union
5
+
4
6
  from .base import Widget
5
7
 
6
8
 
7
9
  class Checkbox(Widget):
8
10
  """A class to represent a checkbox widget."""
9
11
 
12
+ BUTTON_STYLE_MAPPING = {
13
+ "check": "4",
14
+ "cross": "5",
15
+ "circle": "l",
16
+ }
17
+
10
18
  def __init__(
11
19
  self,
12
20
  name: str,
@@ -16,7 +24,8 @@ class Checkbox(Widget):
16
24
 
17
25
  super().__init__(name, value)
18
26
 
19
- self.button_style = None
27
+ self.size = None
28
+ self._button_style = None
20
29
 
21
30
  @property
22
31
  def schema_definition(self) -> dict:
@@ -29,3 +38,18 @@ class Checkbox(Widget):
29
38
  """Sample value of the checkbox."""
30
39
 
31
40
  return True
41
+
42
+ @property
43
+ def button_style(self) -> Union[str, None]:
44
+ """Shape of the tick for the checkbox."""
45
+
46
+ return self._button_style
47
+
48
+ @button_style.setter
49
+ def button_style(self, value) -> None:
50
+ """Converts user specified button styles to acroform values."""
51
+
52
+ if value in self.BUTTON_STYLE_MAPPING:
53
+ self._button_style = self.BUTTON_STYLE_MAPPING[value]
54
+ elif value in self.BUTTON_STYLE_MAPPING.values():
55
+ self._button_style = value
@@ -1,10 +1,10 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Contains radio middleware."""
3
3
 
4
- from .base import Widget
4
+ from .checkbox import Checkbox
5
5
 
6
6
 
7
- class Radio(Widget):
7
+ class Radio(Checkbox):
8
8
  """A class to represent a radiobutton widget."""
9
9
 
10
10
  def __init__(
@@ -16,7 +16,6 @@ class Radio(Widget):
16
16
 
17
17
  super().__init__(name, value)
18
18
 
19
- self.button_style = None
20
19
  self.number_of_options = 0
21
20
 
22
21
  @property
PyPDFForm/utils.py CHANGED
@@ -38,7 +38,7 @@ def checkbox_radio_to_draw(
38
38
  new_widget.font_size = font_size
39
39
  new_widget.font_color = DEFAULT_FONT_COLOR
40
40
  new_widget.value = BUTTON_STYLES.get(widget.button_style) or (
41
- DEFAULT_CHECKBOX_STYLE if isinstance(widget, Checkbox) else DEFAULT_RADIO_STYLE
41
+ DEFAULT_CHECKBOX_STYLE if type(widget) is Checkbox else DEFAULT_RADIO_STYLE
42
42
  )
43
43
 
44
44
  return new_widget
PyPDFForm/wrapper.py CHANGED
@@ -11,7 +11,7 @@ from .constants import (DEFAULT_FONT, DEFAULT_FONT_COLOR, DEFAULT_FONT_SIZE,
11
11
  DEPRECATION_NOTICE, VERSION_IDENTIFIER_PREFIX,
12
12
  VERSION_IDENTIFIERS)
13
13
  from .coordinate import generate_coordinate_grid
14
- from .filler import fill
14
+ from .filler import fill, simple_fill
15
15
  from .font import register_font
16
16
  from .image import any_image_to_jpg, rotate_image
17
17
  from .middleware.dropdown import Dropdown
@@ -26,7 +26,40 @@ from .widgets.checkbox import CheckBoxWidget
26
26
  from .widgets.text import TextWidget
27
27
 
28
28
 
29
- class PdfWrapper:
29
+ class FormWrapper:
30
+ """A simple base wrapper for just filling a PDF form."""
31
+
32
+ def __init__(
33
+ self,
34
+ template: Union[bytes, str, BinaryIO] = b"",
35
+ ) -> None:
36
+ """Constructs all attributes for the object."""
37
+
38
+ self.stream = fp_or_f_obj_or_stream_to_stream(template)
39
+
40
+ def read(self) -> bytes:
41
+ """Reads the file stream of a PDF form."""
42
+
43
+ return self.stream
44
+
45
+ def fill(
46
+ self,
47
+ data: Dict[str, Union[str, bool, int]],
48
+ ) -> FormWrapper:
49
+ """Fills a PDF form."""
50
+
51
+ widgets = build_widgets(self.stream) if self.stream else {}
52
+
53
+ for key, value in data.items():
54
+ if key in widgets:
55
+ widgets[key].value = value
56
+
57
+ self.stream = simple_fill(self.read(), widgets)
58
+
59
+ return self
60
+
61
+
62
+ class PdfWrapper(FormWrapper):
30
63
  """A class to represent a PDF form."""
31
64
 
32
65
  def __init__(
@@ -36,7 +69,7 @@ class PdfWrapper:
36
69
  ) -> None:
37
70
  """Constructs all attributes for the object."""
38
71
 
39
- self.stream = fp_or_f_obj_or_stream_to_stream(template)
72
+ super().__init__(template)
40
73
  self.widgets = build_widgets(self.stream) if self.stream else {}
41
74
 
42
75
  self.global_font = kwargs.get("global_font")
@@ -49,11 +82,6 @@ class PdfWrapper:
49
82
  each.font_size = self.global_font_size
50
83
  each.font_color = self.global_font_color
51
84
 
52
- def read(self) -> bytes:
53
- """Reads the file stream of a PDF form."""
54
-
55
- return self.stream
56
-
57
85
  @property
58
86
  def elements(self) -> dict:
59
87
  """ToDo: deprecate this."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyPDFForm
3
- Version: 1.4.11
3
+ Version: 1.4.13
4
4
  Summary: The Python library for PDF forms.
5
5
  Home-page: https://github.com/chinapandaman/PyPDFForm
6
6
  Author: Jinge Li
@@ -1,28 +1,28 @@
1
- PyPDFForm/__init__.py,sha256=mtq3XW38VKYpCu0_gUwtNRdNTHOnh6tAjv3VkDnaW6k,136
1
+ PyPDFForm/__init__.py,sha256=KAGYHIP-vZnPpNmy2UPIQSULuxgByHXmJhj303FFmCg,149
2
2
  PyPDFForm/adapter.py,sha256=OgAIwcmukpBqHFBLymd3I7wA7wIdipRqgURZA2Y0Hgo,885
3
- PyPDFForm/constants.py,sha256=C-eTxxJRBqbahOXJvwuVl50kzTsghgslgCcbsM06dyY,1615
3
+ PyPDFForm/constants.py,sha256=qxC6FN_K5kWD5XmwypeTyGLBG778geIGf2kHnHGcXAQ,1791
4
4
  PyPDFForm/coordinate.py,sha256=EZxqeM3mkPSvhrsdOa-R2wPylmIECvUwMIlpigWRSbI,7854
5
- PyPDFForm/filler.py,sha256=uPHWoeY7g9kL3r82cLma3slj-SETaDU9bG50mYVVkaI,4308
5
+ PyPDFForm/filler.py,sha256=aVmzJZFW_XwX19KLbRWuDCBZ8ek2s8BapLydf44ZbxY,6707
6
6
  PyPDFForm/font.py,sha256=iXsih_EJ9qe_penqakbQWuP44yUdxhWlQzlpZlMl3uw,4347
7
7
  PyPDFForm/image.py,sha256=0GV8PFgY5oLJFHmhPD1fG-_5SBEO7jVLLtxCc_Uodfo,1143
8
8
  PyPDFForm/patterns.py,sha256=LQRwSk7jImTYZFfN_DC38mCWFrPrXDgkYtu-yQLI7PA,2340
9
9
  PyPDFForm/template.py,sha256=bvjrrIz6RZZl2h7MIjpI1g1FGD0mcabCjtseTvVUQTA,13176
10
- PyPDFForm/utils.py,sha256=rJ9c-HLQy1PiYA6EXNOXFBbq7N1NoyHFf4pAtnO8aXo,4144
10
+ PyPDFForm/utils.py,sha256=_gskLLNmwhfGF16gfMP00twVY6iYADYYAYWHMvKCDrg,4140
11
11
  PyPDFForm/watermark.py,sha256=clYdRqCuWEE25IL-BUHnSo4HgLkHPSOl7FUJLhbAfGg,4755
12
- PyPDFForm/wrapper.py,sha256=Yj56fK744JRtQ8yoJlxyilYlHoszOggZQtkm10N1EN0,9538
12
+ PyPDFForm/wrapper.py,sha256=pXLY84cXuL4X81hvGdmcatp_GV07XXeLfGMdjOT2sx4,10232
13
13
  PyPDFForm/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  PyPDFForm/middleware/base.py,sha256=TmoO8XpjFa5NerAumrAGm_kUt2eFP3DyyF-Vx0uXloM,724
15
- PyPDFForm/middleware/checkbox.py,sha256=noMj4gkwIb4icVUj-YIvbmvkU9bK58IpLqjglmXh5Z4,650
15
+ PyPDFForm/middleware/checkbox.py,sha256=Z92awlKjceMd_ryb9Fhrtyd4fLkU84Ii6fSum60rHcA,1305
16
16
  PyPDFForm/middleware/dropdown.py,sha256=BpRh2YPndhhaLY-s-3gqck64d5FoC719-ARgvfV3_N0,674
17
- PyPDFForm/middleware/radio.py,sha256=mmmZTsNIzhm469-qPPpPtaOvc0qWO1GEmzKPukkMLnU,725
17
+ PyPDFForm/middleware/radio.py,sha256=ehaJoehEzZqxDVb5A6fJkYludeBmtV2Z5GZUPPfUWng,700
18
18
  PyPDFForm/middleware/signature.py,sha256=m7tNvGZ7fQ0yuVKzSlLbNQ2IILo1GDnjzMDzMnYCKVM,1104
19
19
  PyPDFForm/middleware/text.py,sha256=-ETNQoLvgu5cLkpIQtNYHtq75Ou3P7ezYur-E1yb9-k,1057
20
20
  PyPDFForm/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  PyPDFForm/widgets/base.py,sha256=Stq-oQt-cfwQ6nilQ4NqpO5udFSKDNb1mAwryrWggZA,2076
22
22
  PyPDFForm/widgets/checkbox.py,sha256=issx3onojq9plTHbNQwEnkbnhTo5vJKA5XSAoxRzQCg,264
23
23
  PyPDFForm/widgets/text.py,sha256=lUVlfQ-ndemSelsKNzY6nS4Kuf_H4mDPxotFMQFV-so,484
24
- PyPDFForm-1.4.11.dist-info/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
25
- PyPDFForm-1.4.11.dist-info/METADATA,sha256=YDpFcJzOYMWtp5tUHfHEmLxwmItBNO14P6OTfPkx-dU,5178
26
- PyPDFForm-1.4.11.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
27
- PyPDFForm-1.4.11.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
28
- PyPDFForm-1.4.11.dist-info/RECORD,,
24
+ PyPDFForm-1.4.13.dist-info/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
25
+ PyPDFForm-1.4.13.dist-info/METADATA,sha256=1RfinIDEtIhuU7zhUfbvhAzp479KlQ0MH2GrLy7QxuA,5178
26
+ PyPDFForm-1.4.13.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
27
+ PyPDFForm-1.4.13.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
28
+ PyPDFForm-1.4.13.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5