PyPDFForm 1.3.1__tar.gz → 1.3.3__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 (32) hide show
  1. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PKG-INFO +2 -6
  2. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/__init__.py +1 -1
  3. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/core/constants.py +7 -0
  4. PyPDFForm-1.3.3/PyPDFForm/core/font.py +65 -0
  5. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/core/font_size.py +2 -2
  6. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/core/template.py +36 -5
  7. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/core/utils.py +24 -2
  8. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/middleware/checkbox.py +6 -0
  9. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/middleware/constants.py +0 -4
  10. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/middleware/dropdown.py +6 -0
  11. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/middleware/element.py +7 -1
  12. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/middleware/radio.py +6 -0
  13. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/middleware/template.py +0 -3
  14. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/middleware/text.py +7 -0
  15. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/wrapper.py +29 -8
  16. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm.egg-info/PKG-INFO +2 -6
  17. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/README.md +1 -5
  18. PyPDFForm-1.3.1/PyPDFForm/core/font.py +0 -24
  19. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/LICENSE +0 -0
  20. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/core/__init__.py +0 -0
  21. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/core/filler.py +0 -0
  22. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/core/image.py +0 -0
  23. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/core/patterns.py +0 -0
  24. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/core/watermark.py +0 -0
  25. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/middleware/__init__.py +0 -0
  26. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm/middleware/adapter.py +0 -0
  27. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm.egg-info/SOURCES.txt +0 -0
  28. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  29. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm.egg-info/requires.txt +0 -0
  30. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/PyPDFForm.egg-info/top_level.txt +0 -0
  31. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/setup.cfg +0 -0
  32. {PyPDFForm-1.3.1 → PyPDFForm-1.3.3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyPDFForm
3
- Version: 1.3.1
3
+ Version: 1.3.3
4
4
  Summary: The Python library for PDF forms.
5
5
  Home-page: https://github.com/chinapandaman/PyPDFForm
6
6
  Author: Jinge Li
@@ -81,10 +81,6 @@ and it should look like [this](https://github.com/chinapandaman/PyPDFForm/raw/ma
81
81
 
82
82
  ## How to Contribute
83
83
 
84
- If you wish to improve this library, there is one specific way you can contribute
85
- on top of the usual open source project norms such as issues and pull requests.
86
-
87
84
  It is difficult to make sure that the library supports all the PDF form creating tools out
88
- there. So if you run into a case where the library does not work for certain PDF forms created by
89
- certain tools, feel free to open an issue with the problematic PDF form attached. I will seek
85
+ there. So if you run into a case where the library does not work for certain PDF forms created by certain tools, feel free to open an issue with the problematic PDF form attached. I will seek
90
86
  to make the library support the attached PDF form as well as the tool used to create it.
@@ -5,4 +5,4 @@ from .wrapper import Wrapper
5
5
 
6
6
  PyPDFForm = Wrapper
7
7
 
8
- __version__ = "1.3.1"
8
+ __version__ = "1.3.3"
@@ -17,5 +17,12 @@ TEXT_FIELD_ALIGNMENT_IDENTIFIER = "/Q"
17
17
  CHOICE_FIELD_IDENTIFIER = "/Ch"
18
18
  CHOICES_IDENTIFIER = "/Opt"
19
19
 
20
+ FONT_SIZE_IDENTIFIER = "Tf"
21
+ FONT_COLOR_IDENTIFIER = " rg"
22
+ DEFAULT_FONT = "Helvetica"
23
+ DEFAULT_FONT_SIZE = 12
24
+ DEFAULT_FONT_COLOR = (0, 0, 0)
25
+ PREVIEW_FONT_COLOR = (1, 0, 0)
26
+
20
27
  CHECKBOX_TO_DRAW = "\u2713"
21
28
  RADIO_TO_DRAW = "\u25CF"
@@ -0,0 +1,65 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Contains helpers for font."""
3
+
4
+ import re
5
+ from io import BytesIO
6
+
7
+ import pdfrw
8
+ from reportlab.pdfbase import pdfmetrics
9
+ from reportlab.pdfbase.ttfonts import TTFError, TTFont
10
+
11
+ from . import constants
12
+ from .patterns import TEXT_FIELD_APPEARANCE_PATTERNS
13
+ from .template import traverse_pattern
14
+
15
+
16
+ def register_font(font_name: str, ttf_stream: bytes) -> bool:
17
+ """Registers a font from a ttf file stream."""
18
+
19
+ buff = BytesIO()
20
+ buff.write(ttf_stream)
21
+ buff.seek(0)
22
+
23
+ try:
24
+ pdfmetrics.registerFont(TTFont(name=font_name, filename=buff))
25
+ result = True
26
+ except TTFError:
27
+ result = False
28
+
29
+ buff.close()
30
+ return result
31
+
32
+
33
+ def auto_detect_font(element: pdfrw.PdfDict) -> str:
34
+ """Returns the font of the text field if it is one of the standard fonts."""
35
+
36
+ result = constants.DEFAULT_FONT
37
+
38
+ for pattern in TEXT_FIELD_APPEARANCE_PATTERNS:
39
+ text_appearance = traverse_pattern(pattern, element)
40
+
41
+ if text_appearance:
42
+ text_appearance = (
43
+ text_appearance.replace("(", "").replace(")", "").split(" ")
44
+ )
45
+
46
+ for each in text_appearance:
47
+ if each.startswith("/"):
48
+ text_segments = re.findall("[A-Z][^A-Z]*", each.replace("/", ""))
49
+
50
+ for font in pdfmetrics.standardFonts:
51
+ font_segments = re.findall(
52
+ "[A-Z][^A-Z]*", font.replace("-", "")
53
+ )
54
+ if len(font_segments) != len(text_segments):
55
+ continue
56
+
57
+ found = True
58
+ for i, val in enumerate(font_segments):
59
+ if not val.startswith(text_segments[i]):
60
+ found = False
61
+
62
+ if found:
63
+ return font
64
+
65
+ return result
@@ -6,8 +6,8 @@ from typing import Union
6
6
 
7
7
  import pdfrw
8
8
 
9
- from ..middleware.constants import GLOBAL_FONT_SIZE
10
9
  from . import constants, template
10
+ from .constants import DEFAULT_FONT_SIZE
11
11
 
12
12
 
13
13
  def text_field_font_size(element: pdfrw.PdfDict) -> Union[float, int]:
@@ -17,7 +17,7 @@ def text_field_font_size(element: pdfrw.PdfDict) -> Union[float, int]:
17
17
  """
18
18
 
19
19
  if template.is_text_multiline(element):
20
- return GLOBAL_FONT_SIZE
20
+ return DEFAULT_FONT_SIZE
21
21
 
22
22
  height = abs(
23
23
  float(element[constants.ANNOTATION_RECTANGLE_KEY][1])
@@ -148,13 +148,38 @@ def get_text_field_font_size(element: pdfrw.PdfDict) -> Union[float, int]:
148
148
  for pattern in TEXT_FIELD_APPEARANCE_PATTERNS:
149
149
  text_appearance = traverse_pattern(pattern, element)
150
150
  if text_appearance:
151
+ text_appearance = text_appearance.replace("(", "").replace(")", "")
151
152
  properties = text_appearance.split(" ")
152
- if len(properties) > 1:
153
- try:
154
- result = float(properties[1])
153
+ for i, val in enumerate(properties):
154
+ if val == constants.FONT_SIZE_IDENTIFIER:
155
+ return float(properties[i - 1])
156
+
157
+ return result
158
+
159
+
160
+ def get_text_field_font_color(
161
+ element: pdfrw.PdfDict,
162
+ ) -> Union[Tuple[float, float, float], None]:
163
+ """Returns the font color tuple of the text field if presented or black."""
164
+
165
+ result = (0, 0, 0)
166
+ for pattern in TEXT_FIELD_APPEARANCE_PATTERNS:
167
+ text_appearance = traverse_pattern(pattern, element)
168
+ if text_appearance:
169
+ if constants.FONT_COLOR_IDENTIFIER not in text_appearance:
170
+ return result
171
+
172
+ text_appearance = (
173
+ text_appearance.replace("(", "").replace(")", "").split(" ")
174
+ )
175
+ for i, val in enumerate(text_appearance):
176
+ if val == constants.FONT_COLOR_IDENTIFIER.replace(" ", ""):
177
+ result = (
178
+ float(text_appearance[i - 3]),
179
+ float(text_appearance[i - 2]),
180
+ float(text_appearance[i - 1]),
181
+ )
155
182
  break
156
- except ValueError:
157
- pass
158
183
 
159
184
  return result
160
185
 
@@ -251,6 +276,12 @@ def get_draw_text_coordinates(
251
276
  ) -> Tuple[Union[float, int], Union[float, int]]:
252
277
  """Returns coordinates to draw text at given a PDF form text element."""
253
278
 
279
+ if element_middleware.preview:
280
+ return (
281
+ float(element[constants.ANNOTATION_RECTANGLE_KEY][0]),
282
+ float(element[constants.ANNOTATION_RECTANGLE_KEY][3]) + 5,
283
+ )
284
+
254
285
  element_value = element_middleware.value or ""
255
286
  length = (
256
287
  min(len(element_value), element_middleware.max_length)
@@ -12,6 +12,7 @@ from ..middleware.constants import ELEMENT_TYPES
12
12
  from ..middleware.radio import Radio
13
13
  from ..middleware.text import Text
14
14
  from . import constants
15
+ from . import font as font_core
15
16
  from . import font_size as font_size_core
16
17
  from . import template
17
18
 
@@ -43,10 +44,16 @@ def update_text_field_attributes(
43
44
  key = template.get_element_key(_element)
44
45
 
45
46
  if isinstance(elements[key], Text):
47
+ if elements[key].font is None:
48
+ elements[key].font = font_core.auto_detect_font(_element)
46
49
  if elements[key].font_size is None:
47
50
  elements[key].font_size = template.get_text_field_font_size(
48
51
  _element
49
52
  ) or font_size_core.text_field_font_size(_element)
53
+ if elements[key].font_color is None:
54
+ elements[key].font_color = template.get_text_field_font_color(
55
+ _element
56
+ )
50
57
  if (
51
58
  template.is_text_multiline(_element)
52
59
  and elements[key].text_wrap_length is None
@@ -135,9 +142,9 @@ def checkbox_radio_to_draw(
135
142
  element_name=element.name,
136
143
  element_value="",
137
144
  )
138
- new_element.font = "Helvetica"
145
+ new_element.font = constants.DEFAULT_FONT
139
146
  new_element.font_size = font_size
140
- new_element.font_color = (0, 0, 0)
147
+ new_element.font_color = constants.DEFAULT_FONT_COLOR
141
148
 
142
149
  if isinstance(element, Checkbox):
143
150
  new_element.value = constants.CHECKBOX_TO_DRAW
@@ -147,6 +154,21 @@ def checkbox_radio_to_draw(
147
154
  return new_element
148
155
 
149
156
 
157
+ def preview_element_to_draw(element: ELEMENT_TYPES) -> Text:
158
+ """Converts an element to a preview text element."""
159
+
160
+ new_element = Text(
161
+ element_name=element.name,
162
+ element_value="{" + f" {element.name} " + "}",
163
+ )
164
+ new_element.font = constants.DEFAULT_FONT
165
+ new_element.font_size = constants.DEFAULT_FONT_SIZE
166
+ new_element.font_color = constants.PREVIEW_FONT_COLOR
167
+ new_element.preview = True
168
+
169
+ return new_element
170
+
171
+
150
172
  def remove_all_elements(pdf: bytes) -> bytes:
151
173
  """Removes all elements from a pdfrw parsed PDF form."""
152
174
 
@@ -21,3 +21,9 @@ class Checkbox(Element):
21
21
  """Json schema definition of the checkbox."""
22
22
 
23
23
  return {"type": "boolean"}
24
+
25
+ @property
26
+ def sample_value(self) -> bool:
27
+ """Sample value of the checkbox."""
28
+
29
+ return True
@@ -8,10 +8,6 @@ from .dropdown import Dropdown
8
8
  from .radio import Radio
9
9
  from .text import Text
10
10
 
11
- GLOBAL_FONT = "Helvetica"
12
- GLOBAL_FONT_SIZE = 12
13
- GLOBAL_FONT_COLOR = (0, 0, 0)
14
-
15
11
  VERSION_IDENTIFIERS = [
16
12
  b"%PDF-1.0",
17
13
  b"%PDF-1.1",
@@ -23,3 +23,9 @@ class Dropdown(Element):
23
23
  """Json schema definition of the dropdown."""
24
24
 
25
25
  return {"type": "integer", "maximum": len(self.choices) - 1}
26
+
27
+ @property
28
+ def sample_value(self) -> int:
29
+ """Sample value of the dropdown."""
30
+
31
+ return 0
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Contains element middleware."""
3
3
 
4
- from typing import Union
4
+ from typing import Any, Union
5
5
 
6
6
 
7
7
  class Element:
@@ -28,3 +28,9 @@ class Element:
28
28
  """Json schema definition of the element."""
29
29
 
30
30
  raise NotImplementedError
31
+
32
+ @property
33
+ def sample_value(self) -> Any:
34
+ """Sample value of the element."""
35
+
36
+ raise NotImplementedError
@@ -23,3 +23,9 @@ class Radio(Element):
23
23
  """Json schema definition of the radiobutton."""
24
24
 
25
25
  return {"type": "integer", "maximum": self.number_of_options - 1}
26
+
27
+ @property
28
+ def sample_value(self) -> int:
29
+ """Sample value of the radiobutton."""
30
+
31
+ return 0
@@ -67,9 +67,6 @@ def dropdown_to_text(dropdown: Dropdown) -> Text:
67
67
 
68
68
  result = Text(dropdown.name)
69
69
 
70
- result.font = constants.GLOBAL_FONT
71
- result.font_color = constants.GLOBAL_FONT_COLOR
72
-
73
70
  if dropdown.value is not None:
74
71
  result.value = (
75
72
  dropdown.choices[dropdown.value]
@@ -25,6 +25,7 @@ class Text(Element):
25
25
  self.character_paddings = None
26
26
  self.text_lines = None
27
27
  self.text_line_x_coordinates = None
28
+ self.preview = False
28
29
 
29
30
  @property
30
31
  def schema_definition(self) -> dict:
@@ -36,3 +37,9 @@ class Text(Element):
36
37
  result["maxLength"] = self.max_length
37
38
 
38
39
  return result
40
+
41
+ @property
42
+ def sample_value(self) -> str:
43
+ """Sample value of the text field."""
44
+
45
+ return self.name
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  from typing import BinaryIO, Dict, Union
7
7
 
8
+ from .core import constants as core_constants
8
9
  from .core import filler, font
9
10
  from .core import image as image_core
10
11
  from .core import utils
@@ -32,17 +33,21 @@ class Wrapper:
32
33
 
33
34
  for each in self.elements.values():
34
35
  if isinstance(each, Text):
35
- each.font = kwargs.get("global_font", constants.GLOBAL_FONT)
36
+ each.font = kwargs.get("global_font")
36
37
  each.font_size = kwargs.get("global_font_size")
37
- each.font_color = kwargs.get(
38
- "global_font_color", constants.GLOBAL_FONT_COLOR
39
- )
38
+ each.font_color = kwargs.get("global_font_color")
40
39
 
41
40
  def read(self) -> bytes:
42
41
  """Reads the file stream of a PDF form."""
43
42
 
44
43
  return self.stream
45
44
 
45
+ @property
46
+ def sample_data(self) -> dict:
47
+ """Returns a valid sample data that can be filled into the PDF form."""
48
+
49
+ return {key: value.sample_value for key, value in self.elements.items()}
50
+
46
51
  @property
47
52
  def version(self) -> Union[str, None]:
48
53
  """Gets the version of the PDF."""
@@ -78,11 +83,23 @@ class Wrapper:
78
83
 
79
84
  return new_obj
80
85
 
86
+ @property
87
+ def preview(self) -> bytes:
88
+ """Inspects all supported elements' names for the PDF form."""
89
+
90
+ return filler.fill(
91
+ self.stream,
92
+ {
93
+ key: utils.preview_element_to_draw(value)
94
+ for key, value in self.elements.items()
95
+ },
96
+ )
97
+
81
98
  def fill(
82
99
  self,
83
100
  data: Dict[str, Union[str, bool, int]],
84
101
  ) -> Wrapper:
85
- """Fill a PDF form."""
102
+ """Fills a PDF form."""
86
103
 
87
104
  for key, value in data.items():
88
105
  if key in self.elements:
@@ -114,9 +131,13 @@ class Wrapper:
114
131
 
115
132
  new_element = Text("new")
116
133
  new_element.value = text
117
- new_element.font = kwargs.get("font", constants.GLOBAL_FONT)
118
- new_element.font_size = kwargs.get("font_size", constants.GLOBAL_FONT_SIZE)
119
- new_element.font_color = kwargs.get("font_color", constants.GLOBAL_FONT_COLOR)
134
+ new_element.font = kwargs.get("font", core_constants.DEFAULT_FONT)
135
+ new_element.font_size = kwargs.get(
136
+ "font_size", core_constants.DEFAULT_FONT_SIZE
137
+ )
138
+ new_element.font_color = kwargs.get(
139
+ "font_color", core_constants.DEFAULT_FONT_COLOR
140
+ )
120
141
 
121
142
  watermarks = watermark_core.create_watermarks_and_draw(
122
143
  self.stream,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyPDFForm
3
- Version: 1.3.1
3
+ Version: 1.3.3
4
4
  Summary: The Python library for PDF forms.
5
5
  Home-page: https://github.com/chinapandaman/PyPDFForm
6
6
  Author: Jinge Li
@@ -81,10 +81,6 @@ and it should look like [this](https://github.com/chinapandaman/PyPDFForm/raw/ma
81
81
 
82
82
  ## How to Contribute
83
83
 
84
- If you wish to improve this library, there is one specific way you can contribute
85
- on top of the usual open source project norms such as issues and pull requests.
86
-
87
84
  It is difficult to make sure that the library supports all the PDF form creating tools out
88
- there. So if you run into a case where the library does not work for certain PDF forms created by
89
- certain tools, feel free to open an issue with the problematic PDF form attached. I will seek
85
+ there. So if you run into a case where the library does not work for certain PDF forms created by certain tools, feel free to open an issue with the problematic PDF form attached. I will seek
90
86
  to make the library support the attached PDF form as well as the tool used to create it.
@@ -68,10 +68,6 @@ and it should look like [this](https://github.com/chinapandaman/PyPDFForm/raw/ma
68
68
 
69
69
  ## How to Contribute
70
70
 
71
- If you wish to improve this library, there is one specific way you can contribute
72
- on top of the usual open source project norms such as issues and pull requests.
73
-
74
71
  It is difficult to make sure that the library supports all the PDF form creating tools out
75
- there. So if you run into a case where the library does not work for certain PDF forms created by
76
- certain tools, feel free to open an issue with the problematic PDF form attached. I will seek
72
+ there. So if you run into a case where the library does not work for certain PDF forms created by certain tools, feel free to open an issue with the problematic PDF form attached. I will seek
77
73
  to make the library support the attached PDF form as well as the tool used to create it.
@@ -1,24 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """Contains helpers for font."""
3
-
4
- from io import BytesIO
5
-
6
- from reportlab.pdfbase import pdfmetrics
7
- from reportlab.pdfbase.ttfonts import TTFError, TTFont
8
-
9
-
10
- def register_font(font_name: str, ttf_stream: bytes) -> bool:
11
- """Registers a font from a ttf file stream."""
12
-
13
- buff = BytesIO()
14
- buff.write(ttf_stream)
15
- buff.seek(0)
16
-
17
- try:
18
- pdfmetrics.registerFont(TTFont(name=font_name, filename=buff))
19
- result = True
20
- except TTFError:
21
- result = False
22
-
23
- buff.close()
24
- return result
File without changes
File without changes
File without changes