PyPDFForm 1.4.37__tar.gz → 1.5.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 (48) hide show
  1. {pypdfform-1.4.37 → pypdfform-1.5.0}/PKG-INFO +1 -1
  2. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/__init__.py +1 -1
  3. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/constants.py +1 -0
  4. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/middleware/base.py +7 -1
  5. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/middleware/checkbox.py +1 -1
  6. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/middleware/dropdown.py +6 -1
  7. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/middleware/radio.py +5 -1
  8. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/middleware/text.py +1 -1
  9. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/patterns.py +3 -1
  10. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/template.py +38 -26
  11. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/wrapper.py +52 -23
  12. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm.egg-info/PKG-INFO +1 -1
  13. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_functional.py +3 -7
  14. {pypdfform-1.4.37 → pypdfform-1.5.0}/LICENSE +0 -0
  15. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/adapter.py +0 -0
  16. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/coordinate.py +0 -0
  17. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/filler.py +0 -0
  18. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/font.py +0 -0
  19. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/image.py +0 -0
  20. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/middleware/__init__.py +0 -0
  21. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/middleware/image.py +0 -0
  22. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/middleware/signature.py +0 -0
  23. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/utils.py +0 -0
  24. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/watermark.py +0 -0
  25. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/widgets/__init__.py +0 -0
  26. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/widgets/base.py +0 -0
  27. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/widgets/checkbox.py +0 -0
  28. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/widgets/dropdown.py +0 -0
  29. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm/widgets/text.py +0 -0
  30. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm.egg-info/SOURCES.txt +0 -0
  31. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  32. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm.egg-info/requires.txt +0 -0
  33. {pypdfform-1.4.37 → pypdfform-1.5.0}/PyPDFForm.egg-info/top_level.txt +0 -0
  34. {pypdfform-1.4.37 → pypdfform-1.5.0}/README.md +0 -0
  35. {pypdfform-1.4.37 → pypdfform-1.5.0}/setup.cfg +0 -0
  36. {pypdfform-1.4.37 → pypdfform-1.5.0}/setup.py +0 -0
  37. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_adobe_mode.py +0 -0
  38. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_create_widget.py +0 -0
  39. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_dropdown.py +0 -0
  40. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_dropdown_simple.py +0 -0
  41. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_fill_max_length_text_field.py +0 -0
  42. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_fill_max_length_text_field_simple.py +0 -0
  43. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_fill_method.py +0 -0
  44. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_functional_simple.py +0 -0
  45. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_paragraph.py +0 -0
  46. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_paragraph_simple.py +0 -0
  47. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_preview.py +0 -0
  48. {pypdfform-1.4.37 → pypdfform-1.5.0}/tests/test_signature.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyPDFForm
3
- Version: 1.4.37
3
+ Version: 1.5.0
4
4
  Summary: The Python library for PDF forms.
5
5
  Home-page: https://github.com/chinapandaman/PyPDFForm
6
6
  Author: Jinge Li
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Contains any object users might need."""
3
3
 
4
- __version__ = "1.4.37"
4
+ __version__ = "1.5.0"
5
5
 
6
6
  from .wrapper import FormWrapper, PdfWrapper
7
7
 
@@ -31,6 +31,7 @@ Annots = "/Annots"
31
31
  A = "/A"
32
32
  JS = "/JS"
33
33
  T = "/T"
34
+ TU = "/TU"
34
35
  Rect = "/Rect"
35
36
  FT = "/FT"
36
37
  Parent = "/Parent"
@@ -17,6 +17,7 @@ class Widget:
17
17
  super().__init__()
18
18
  self._name = name
19
19
  self._value = value
20
+ self.desc = None
20
21
 
21
22
  @property
22
23
  def name(self) -> str:
@@ -40,7 +41,12 @@ class Widget:
40
41
  def schema_definition(self) -> dict:
41
42
  """Json schema definition of the widget."""
42
43
 
43
- raise NotImplementedError
44
+ result = {}
45
+
46
+ if self.desc is not None:
47
+ result["description"] = self.desc
48
+
49
+ return result
44
50
 
45
51
  @property
46
52
  def sample_value(self) -> Any:
@@ -31,7 +31,7 @@ class Checkbox(Widget):
31
31
  def schema_definition(self) -> dict:
32
32
  """Json schema definition of the checkbox."""
33
33
 
34
- return {"type": "boolean"}
34
+ return {"type": "boolean", **super().schema_definition}
35
35
 
36
36
  @property
37
37
  def sample_value(self) -> Union[bool, int]:
@@ -17,12 +17,17 @@ class Dropdown(Widget):
17
17
  super().__init__(name, value)
18
18
 
19
19
  self.choices = []
20
+ self.desc = None
20
21
 
21
22
  @property
22
23
  def schema_definition(self) -> dict:
23
24
  """Json schema definition of the dropdown."""
24
25
 
25
- return {"type": "integer", "maximum": len(self.choices) - 1}
26
+ return {
27
+ "type": "integer",
28
+ "maximum": len(self.choices) - 1,
29
+ **super().schema_definition,
30
+ }
26
31
 
27
32
  @property
28
33
  def sample_value(self) -> int:
@@ -22,7 +22,11 @@ class Radio(Checkbox):
22
22
  def schema_definition(self) -> dict:
23
23
  """Json schema definition of the radiobutton."""
24
24
 
25
- return {"type": "integer", "maximum": self.number_of_options - 1}
25
+ return {
26
+ "maximum": self.number_of_options - 1,
27
+ **super().schema_definition,
28
+ "type": "integer",
29
+ }
26
30
 
27
31
  @property
28
32
  def sample_value(self) -> int:
@@ -53,7 +53,7 @@ class Text(Widget):
53
53
  if self.max_length is not None:
54
54
  result["maxLength"] = self.max_length
55
55
 
56
- return result
56
+ return {**result, **super().schema_definition}
57
57
 
58
58
  @property
59
59
  def sample_value(self) -> str:
@@ -5,7 +5,7 @@ from pypdf.generic import (DictionaryObject, NameObject, NumberObject,
5
5
  TextStringObject)
6
6
 
7
7
  from .constants import (AP, AS, CA, DA, DV, FT, IMAGE_FIELD_IDENTIFIER, JS, MK,
8
- MULTILINE, READ_ONLY, A, Btn, Ch, Ff, N, Off, Opt,
8
+ MULTILINE, READ_ONLY, TU, A, Btn, Ch, Ff, N, Off, Opt,
9
9
  Parent, Q, Sig, T, Tx, V, Yes)
10
10
  from .middleware.checkbox import Checkbox
11
11
  from .middleware.dropdown import Dropdown
@@ -68,6 +68,8 @@ WIDGET_KEY_PATTERNS = [
68
68
  {Parent: {T: True}},
69
69
  ]
70
70
 
71
+ WIDGET_DESCRIPTION_PATTERNS = [{TU: True}, {Parent: {TU: True}}]
72
+
71
73
  DROPDOWN_CHOICE_PATTERNS = [
72
74
  {Opt: True},
73
75
  {Parent: {Opt: True}},
@@ -21,8 +21,8 @@ from .middleware.radio import Radio
21
21
  from .middleware.text import Text
22
22
  from .patterns import (BUTTON_STYLE_PATTERNS, DROPDOWN_CHOICE_PATTERNS,
23
23
  TEXT_FIELD_FLAG_PATTERNS, WIDGET_ALIGNMENT_PATTERNS,
24
- WIDGET_KEY_PATTERNS, WIDGET_TYPE_PATTERNS,
25
- update_annotation_name)
24
+ WIDGET_DESCRIPTION_PATTERNS, WIDGET_KEY_PATTERNS,
25
+ WIDGET_TYPE_PATTERNS, update_annotation_name)
26
26
  from .utils import find_pattern_match, stream_to_io, traverse_pattern
27
27
  from .watermark import create_watermarks_and_draw
28
28
 
@@ -51,10 +51,9 @@ def build_widgets(pdf_stream: bytes) -> Dict[str, WIDGET_TYPES]:
51
51
  for widgets in get_widgets_by_page(pdf_stream).values():
52
52
  for widget in widgets:
53
53
  key = get_widget_key(widget)
54
-
55
54
  _widget = construct_widget(widget, key)
56
-
57
55
  if _widget is not None:
56
+ _widget.desc = get_widget_description(widget)
58
57
  if isinstance(_widget, Text):
59
58
  _widget.max_length = get_text_field_max_length(widget)
60
59
  if _widget.max_length is not None and is_text_field_comb(widget):
@@ -74,7 +73,6 @@ def build_widgets(pdf_stream: bytes) -> Dict[str, WIDGET_TYPES]:
74
73
  continue
75
74
 
76
75
  results[key] = _widget
77
-
78
76
  return results
79
77
 
80
78
 
@@ -225,6 +223,18 @@ def get_text_field_max_length(widget: dict) -> Union[int, None]:
225
223
  return int(widget[MaxLen]) or None if MaxLen in widget else None
226
224
 
227
225
 
226
+ def get_widget_description(widget: dict) -> Union[str, None]:
227
+ """Returns the description of the widget if presented or None."""
228
+
229
+ result = None
230
+ for pattern in WIDGET_DESCRIPTION_PATTERNS:
231
+ value = traverse_pattern(pattern, widget)
232
+ if value:
233
+ result = str(value)
234
+ break
235
+ return result
236
+
237
+
228
238
  def check_field_flag_bit(widget: dict, bit: int) -> bool:
229
239
  """Checks if a bit is set in a widget's field flag."""
230
240
 
@@ -408,39 +418,41 @@ def get_paragraph_auto_wrap_length(widget_middleware: Text) -> int:
408
418
  return result
409
419
 
410
420
 
411
- def update_widget_key(
421
+ def update_widget_keys(
412
422
  template: bytes,
413
423
  widgets: Dict[str, WIDGET_TYPES],
414
- old_key: str,
415
- new_key: str,
416
- index: int,
424
+ old_keys: List[str],
425
+ new_keys: List[str],
426
+ indices: List[int],
417
427
  ) -> bytes:
418
- """Updates the key of a widget."""
428
+ """Updates a list of keys of widgets."""
419
429
  # pylint: disable=R0801
420
430
 
421
431
  pdf = PdfReader(stream_to_io(template))
422
432
  out = PdfWriter()
423
433
  out.append(pdf)
424
434
 
425
- tracker = -1
426
-
427
- for page in out.pages:
428
- for annot in page.get(Annots, []): # noqa
429
- annot = cast(DictionaryObject, annot.get_object())
430
- key = get_widget_key(annot.get_object())
431
-
432
- widget = widgets.get(key)
433
- if widget is None:
434
- continue
435
+ for i, old_key in enumerate(old_keys):
436
+ index = indices[i]
437
+ new_key = new_keys[i]
438
+ tracker = -1
439
+ for page in out.pages:
440
+ for annot in page.get(Annots, []): # noqa
441
+ annot = cast(DictionaryObject, annot.get_object())
442
+ key = get_widget_key(annot.get_object())
443
+
444
+ widget = widgets.get(key)
445
+ if widget is None:
446
+ continue
435
447
 
436
- if old_key != key:
437
- continue
448
+ if old_key != key:
449
+ continue
438
450
 
439
- tracker += 1
440
- if not isinstance(widget, Radio) and tracker != index:
441
- continue
451
+ tracker += 1
452
+ if not isinstance(widget, Radio) and tracker != index:
453
+ continue
442
454
 
443
- update_annotation_name(annot, new_key)
455
+ update_annotation_name(annot, new_key)
444
456
 
445
457
  with BytesIO() as f:
446
458
  out.write(f)
@@ -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_key, widget_rect_watermarks)
21
+ update_widget_keys, widget_rect_watermarks)
22
22
  from .utils import (get_page_streams, merge_two_pdfs, preview_widget_to_draw,
23
23
  remove_all_widgets)
24
24
  from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
@@ -79,17 +79,35 @@ class PdfWrapper(FormWrapper):
79
79
  """Constructs all attributes for the object."""
80
80
 
81
81
  super().__init__(template)
82
- self.widgets = build_widgets(self.stream) if self.stream else {}
82
+ self.widgets = {}
83
+ self._keys_to_update = []
83
84
 
84
85
  self.global_font = kwargs.get("global_font")
85
86
  self.global_font_size = kwargs.get("global_font_size")
86
87
  self.global_font_color = kwargs.get("global_font_color")
87
88
 
88
- for each in self.widgets.values():
89
- if isinstance(each, Text):
90
- each.font = self.global_font
91
- each.font_size = self.global_font_size
92
- each.font_color = self.global_font_color
89
+ self._init_helper()
90
+
91
+ def _init_helper(self, key_to_refresh: str = None) -> None:
92
+ """Updates all attributes when the state of the PDF stream changes."""
93
+
94
+ refresh_not_needed = {}
95
+ new_widgets = build_widgets(self.read()) if self.read() else {}
96
+ for k, v in self.widgets.items():
97
+ if k in new_widgets:
98
+ new_widgets[k] = v
99
+ refresh_not_needed[k] = True
100
+ self.widgets = new_widgets
101
+
102
+ for key, value in self.widgets.items():
103
+ if (key_to_refresh and key == key_to_refresh) or (
104
+ key_to_refresh is None
105
+ and isinstance(value, Text)
106
+ and not refresh_not_needed.get(key)
107
+ ):
108
+ value.font = self.global_font
109
+ value.font_size = self.global_font_size
110
+ value.font_color = self.global_font_color
93
111
 
94
112
  @property
95
113
  def sample_data(self) -> dict:
@@ -223,31 +241,42 @@ class PdfWrapper(FormWrapper):
223
241
  self.stream, name, obj.non_acro_form_params
224
242
  )
225
243
 
226
- new_widgets = build_widgets(self.read())
227
- for k, v in self.widgets.items():
228
- if k in new_widgets:
229
- new_widgets[k] = v
230
- self.widgets = new_widgets
244
+ key_to_refresh = ""
231
245
  if widget_type in ("text", "dropdown"):
232
- self.widgets[name].font = self.global_font
233
- self.widgets[name].font_size = self.global_font_size
234
- self.widgets[name].font_color = self.global_font_color
246
+ key_to_refresh = name
247
+
248
+ self._init_helper(key_to_refresh)
235
249
 
236
250
  return self
237
251
 
238
252
  def update_widget_key(
239
- self, old_key: str, new_key: str, index: int = 0
253
+ self, old_key: str, new_key: str, index: int = 0, defer: bool = False
240
254
  ) -> PdfWrapper:
241
255
  """Updates the key of an existed widget on a PDF form."""
242
256
 
243
- self.__init__(
244
- template=update_widget_key(
245
- self.read(), self.widgets, old_key, new_key, index
246
- ),
247
- global_font=self.global_font,
248
- global_font_size=self.global_font_size,
249
- global_font_color=self.global_font_color,
257
+ if defer:
258
+ self._keys_to_update.append((old_key, new_key, index))
259
+ return self
260
+
261
+ self.stream = update_widget_keys(
262
+ self.read(), self.widgets, [old_key], [new_key], [index]
263
+ )
264
+ self._init_helper()
265
+
266
+ return self
267
+
268
+ def commit_widget_key_updates(self) -> PdfWrapper:
269
+ """Commits all deferred widget key updates on a PDF form."""
270
+
271
+ old_keys = [each[0] for each in self._keys_to_update]
272
+ new_keys = [each[1] for each in self._keys_to_update]
273
+ indices = [each[2] for each in self._keys_to_update]
274
+
275
+ self.stream = update_widget_keys(
276
+ self.read(), self.widgets, old_keys, new_keys, indices
250
277
  )
278
+ self._init_helper()
279
+ self._keys_to_update = []
251
280
 
252
281
  return self
253
282
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyPDFForm
3
- Version: 1.4.37
3
+ Version: 1.5.0
4
4
  Summary: The Python library for PDF forms.
5
5
  Home-page: https://github.com/chinapandaman/PyPDFForm
6
6
  Author: Jinge Li
@@ -10,11 +10,7 @@ from PyPDFForm.middleware.text import Text
10
10
 
11
11
 
12
12
  def test_base_schema_definition():
13
- try:
14
- assert Widget("foo").schema_definition
15
- raise AssertionError
16
- except NotImplementedError:
17
- pass
13
+ assert Widget("foo").schema_definition == {}
18
14
 
19
15
 
20
16
  def test_fill(template_stream, pdf_samples, data_dict, request):
@@ -635,7 +631,7 @@ def test_update_radio_key(template_with_radiobutton_stream, pdf_samples, request
635
631
  obj.update_widget_key("radio_3", "RADIO")
636
632
 
637
633
  request.config.results["expected_path"] = expected_path
638
- request.config.results["stream"] = obj.read()
634
+ request.config.results["stream"] = obj.preview
639
635
 
640
636
  expected = f.read()
641
637
 
@@ -653,7 +649,7 @@ def test_update_sejda_key(sejda_template, pdf_samples, request):
653
649
  obj.update_widget_key("buyer_signed_date", "BUYER_SIGNED_DATE")
654
650
 
655
651
  request.config.results["expected_path"] = expected_path
656
- request.config.results["stream"] = obj.read()
652
+ request.config.results["stream"] = obj.preview
657
653
 
658
654
  expected = f.read()
659
655
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes