PyPDFForm 4.3.0__tar.gz → 4.4.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.
Files changed (73) hide show
  1. {pypdfform-4.3.0 → pypdfform-4.4.0}/PKG-INFO +1 -1
  2. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/__init__.py +1 -1
  3. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/template.py +39 -0
  4. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/wrapper.py +13 -2
  5. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm.egg-info/PKG-INFO +1 -1
  6. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm.egg-info/SOURCES.txt +0 -1
  7. pypdfform-4.4.0/tests/test_bulk_create_fields.py +146 -0
  8. pypdfform-4.4.0/tests/test_create_widget.py +652 -0
  9. {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_draw_elements.py +0 -143
  10. pypdfform-4.4.0/tests/test_dropdown.py +195 -0
  11. pypdfform-4.4.0/tests/test_fill_max_length_text_field.py +143 -0
  12. {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_functional.py +9 -414
  13. pypdfform-4.4.0/tests/test_paragraph.py +133 -0
  14. {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_signature.py +0 -44
  15. {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_widget_attr_trigger.py +0 -195
  16. pypdfform-4.3.0/tests/test_bulk_create_fields.py +0 -296
  17. pypdfform-4.3.0/tests/test_create_widget.py +0 -1421
  18. pypdfform-4.3.0/tests/test_dropdown.py +0 -467
  19. pypdfform-4.3.0/tests/test_fill_max_length_text_field.py +0 -446
  20. pypdfform-4.3.0/tests/test_fill_method.py +0 -141
  21. pypdfform-4.3.0/tests/test_paragraph.py +0 -416
  22. {pypdfform-4.3.0 → pypdfform-4.4.0}/LICENSE +0 -0
  23. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/adapter.py +0 -0
  24. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/ap.py +0 -0
  25. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/assets/__init__.py +0 -0
  26. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/assets/bedrock.py +0 -0
  27. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/assets/blank.py +0 -0
  28. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/constants.py +0 -0
  29. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/coordinate.py +0 -0
  30. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/deprecation.py +0 -0
  31. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/filler.py +0 -0
  32. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/font.py +0 -0
  33. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/hooks.py +0 -0
  34. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/image.py +0 -0
  35. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/__init__.py +0 -0
  36. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/base.py +0 -0
  37. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/checkbox.py +0 -0
  38. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/dropdown.py +0 -0
  39. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/image.py +0 -0
  40. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/radio.py +0 -0
  41. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/signature.py +0 -0
  42. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/middleware/text.py +0 -0
  43. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/patterns.py +0 -0
  44. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/__init__.py +0 -0
  45. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/circle.py +0 -0
  46. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/ellipse.py +0 -0
  47. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/image.py +0 -0
  48. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/line.py +0 -0
  49. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/rect.py +0 -0
  50. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/raw/text.py +0 -0
  51. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/types.py +0 -0
  52. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/utils.py +0 -0
  53. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/watermark.py +0 -0
  54. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/__init__.py +0 -0
  55. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/base.py +0 -0
  56. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/checkbox.py +0 -0
  57. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/dropdown.py +0 -0
  58. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/image.py +0 -0
  59. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/radio.py +0 -0
  60. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/signature.py +0 -0
  61. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm/widgets/text.py +0 -0
  62. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  63. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm.egg-info/requires.txt +0 -0
  64. {pypdfform-4.3.0 → pypdfform-4.4.0}/PyPDFForm.egg-info/top_level.txt +0 -0
  65. {pypdfform-4.3.0 → pypdfform-4.4.0}/README.md +0 -0
  66. {pypdfform-4.3.0 → pypdfform-4.4.0}/pyproject.toml +0 -0
  67. {pypdfform-4.3.0 → pypdfform-4.4.0}/setup.cfg +0 -0
  68. {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_extract_values.py +0 -0
  69. {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_font_widths.py +0 -0
  70. {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_generate_appearance_streams.py +0 -0
  71. {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_js.py +0 -0
  72. {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_need_appearances.py +0 -0
  73. {pypdfform-4.3.0 → pypdfform-4.4.0}/tests/test_use_full_widget_name.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 4.3.0
3
+ Version: 4.4.0
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -20,7 +20,7 @@ The library supports various PDF form features, including:
20
20
  PyPDFForm aims to simplify PDF form manipulation, making it accessible to developers of all skill levels.
21
21
  """
22
22
 
23
- __version__ = "4.3.0"
23
+ __version__ = "4.4.0"
24
24
 
25
25
  from .assets.blank import BlankPage
26
26
  from .middleware import Widgets
@@ -28,6 +28,45 @@ from .patterns import (DROPDOWN_CHOICE_PATTERNS, WIDGET_DESCRIPTION_PATTERNS,
28
28
  from .utils import extract_widget_property, find_pattern_match, stream_to_io
29
29
 
30
30
 
31
+ @lru_cache
32
+ def get_metadata(pdf: bytes) -> dict:
33
+ """
34
+ Retrieves the metadata of a PDF.
35
+
36
+ Args:
37
+ pdf (bytes): The PDF stream to extract metadata from.
38
+
39
+ Returns:
40
+ dict: A dictionary containing the PDF's metadata.
41
+ """
42
+ if not pdf:
43
+ return {}
44
+
45
+ reader = PdfReader(stream_to_io(pdf))
46
+ return reader.metadata or {}
47
+
48
+
49
+ def set_metadata(pdf: bytes, metadata: dict) -> bytes:
50
+ """
51
+ Sets the metadata of a PDF.
52
+
53
+ Args:
54
+ pdf (bytes): The PDF stream to set metadata for.
55
+ metadata (dict): A dictionary containing the metadata to be set.
56
+
57
+ Returns:
58
+ bytes: The updated PDF stream with the new metadata.
59
+ """
60
+ reader = PdfReader(stream_to_io(pdf))
61
+ writer = PdfWriter(clone_from=reader)
62
+ writer.add_metadata(metadata)
63
+
64
+ with BytesIO() as f:
65
+ writer.write(f)
66
+ f.seek(0)
67
+ return f.read()
68
+
69
+
31
70
  def build_widgets(
32
71
  pdf_stream: bytes,
33
72
  use_full_widget_name: bool,
@@ -38,7 +38,8 @@ from .middleware.dropdown import Dropdown
38
38
  from .middleware.signature import Signature
39
39
  from .middleware.text import Text
40
40
  from .raw import RawText, RawTypes
41
- from .template import build_widgets, update_widget_keys
41
+ from .template import (build_widgets, get_metadata, set_metadata,
42
+ update_widget_keys)
42
43
  from .types import PdfArray
43
44
  from .utils import (generate_unique_suffix, get_page_streams, merge_pdfs,
44
45
  remove_all_widgets)
@@ -68,6 +69,7 @@ class PdfWrapper:
68
69
  - `use_full_widget_name` (bool): Whether to use the full widget name when filling the form.
69
70
  - `need_appearances` (bool): Whether to set the `NeedAppearances` flag in the PDF's AcroForm dictionary.
70
71
  - `generate_appearance_streams` (bool): Whether to explicitly generate appearance streams for all form fields using pikepdf.
72
+ - `preserve_metadata` (bool): Whether to preserve the original metadata of the PDF.
71
73
  - `title` (str): The title of the PDF document.
72
74
 
73
75
  """
@@ -76,6 +78,7 @@ class PdfWrapper:
76
78
  ("use_full_widget_name", False),
77
79
  ("need_appearances", False),
78
80
  ("generate_appearance_streams", False),
81
+ ("preserve_metadata", False),
79
82
  ("title", None),
80
83
  ]
81
84
 
@@ -105,6 +108,9 @@ class PdfWrapper:
105
108
  self._stream = fp_or_f_obj_or_stream_to_stream(template)
106
109
  self.widgets = {}
107
110
  self.title: str = None
111
+ self._metadata = (
112
+ get_metadata(self._read()) if kwargs.get("preserve_metadata") else {}
113
+ )
108
114
  self._on_open_javascript = None
109
115
  self._available_fonts = {} # for setting /F1
110
116
  self._font_register_events = [] # for reregister
@@ -356,7 +362,8 @@ class PdfWrapper:
356
362
  2. If `need_appearances` is enabled, it handles appearance streams and the
357
363
  `/NeedAppearances` flag, which may include removing XFA and explicitly
358
364
  generating appearance streams.
359
- 3. If a title or on-open JavaScript is set, it updates the PDF properties
365
+ 3. If `preserve_metadata` is enabled, it preserves the original metadata of the PDF.
366
+ 4. If a title or on-open JavaScript is set, it updates the PDF properties
360
367
  accordingly.
361
368
 
362
369
  Returns:
@@ -369,6 +376,10 @@ class PdfWrapper:
369
376
  result, getattr(self, "generate_appearance_streams")
370
377
  ) # cached
371
378
 
379
+ if getattr(self, "preserve_metadata"):
380
+ # TODO: refactor with preserve_pdf_properties
381
+ result = set_metadata(result, self._metadata)
382
+
372
383
  if any([self.title, self.on_open_javascript]):
373
384
  result = preserve_pdf_properties(
374
385
  result,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 4.3.0
3
+ Version: 4.4.0
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -54,7 +54,6 @@ tests/test_draw_elements.py
54
54
  tests/test_dropdown.py
55
55
  tests/test_extract_values.py
56
56
  tests/test_fill_max_length_text_field.py
57
- tests/test_fill_method.py
58
57
  tests/test_font_widths.py
59
58
  tests/test_functional.py
60
59
  tests/test_generate_appearance_streams.py
@@ -0,0 +1,146 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import os
4
+
5
+ import pytest
6
+
7
+ from PyPDFForm import Fields, PdfWrapper
8
+
9
+
10
+ @pytest.mark.posix_only
11
+ def test_bulk_create_fields_stress_max(pdf_samples, request):
12
+ expected_path = os.path.join(
13
+ pdf_samples, "bulk_create_fields", "test_bulk_create_fields_stress_max.pdf"
14
+ )
15
+
16
+ fields = []
17
+ x = 0
18
+ y = 0
19
+ margin = 25
20
+ while x <= 575:
21
+ while y <= 625:
22
+ fields.append(
23
+ Fields.TextField(f"text_{x}_{y}", 1, x, y, width=margin, height=margin)
24
+ )
25
+ fields.append(Fields.CheckBoxField(f"check_{x}_{y}", 2, x, y, size=margin))
26
+ fields.append(
27
+ Fields.DropdownField(
28
+ f"dropdown_{x}_{y}",
29
+ 3,
30
+ x,
31
+ y,
32
+ width=margin,
33
+ height=margin,
34
+ options=["foo", "bar"],
35
+ )
36
+ )
37
+ fields.append(
38
+ Fields.RadioGroup(
39
+ f"radio_{x}_{y}", 4, [x, x + 12.5], [y, y + 12.5], size=12.5
40
+ )
41
+ )
42
+ fields.append(
43
+ Fields.ImageField(
44
+ f"image_{x}_{y}", 5, x, y, width=margin, height=margin
45
+ )
46
+ )
47
+ fields.append(
48
+ Fields.SignatureField(
49
+ f"signature_{x}_{y}", 6, x, y, width=margin, height=margin
50
+ )
51
+ )
52
+ y += margin
53
+ y = 0
54
+ x += margin
55
+
56
+ with open(expected_path, "rb+") as f:
57
+ obj = PdfWrapper()
58
+ for _ in range(6):
59
+ obj += PdfWrapper(os.path.join(pdf_samples, "dummy.pdf"))
60
+ obj.bulk_create_fields(fields)
61
+
62
+ request.config.results["expected_path"] = expected_path
63
+ request.config.results["stream"] = obj.read()
64
+
65
+ expected = f.read()
66
+
67
+ assert len(obj.read()) == len(expected)
68
+ assert obj.read() == expected
69
+
70
+
71
+ @pytest.mark.posix_only
72
+ def test_bulk_create_fields_stress_max_mixed(pdf_samples, request):
73
+ expected_path = os.path.join(
74
+ pdf_samples,
75
+ "bulk_create_fields",
76
+ "test_bulk_create_fields_stress_max_mixed.pdf",
77
+ )
78
+
79
+ fields = []
80
+ x = 0
81
+ y = 0
82
+ margin = 25
83
+ while x <= 450:
84
+ while y <= 625:
85
+ fields.append(
86
+ Fields.TextField(f"text_{x}_{y}", 1, x, y, width=margin, height=margin)
87
+ )
88
+ fields.append(
89
+ Fields.CheckBoxField(f"check_{x}_{y}", 1, x + margin, y, size=margin)
90
+ )
91
+ fields.append(
92
+ Fields.DropdownField(
93
+ f"dropdown_{x}_{y}",
94
+ 1,
95
+ x,
96
+ y + margin,
97
+ width=margin,
98
+ height=margin,
99
+ options=["foo", "bar"],
100
+ )
101
+ )
102
+ fields.append(
103
+ Fields.RadioGroup(
104
+ f"radio_{x}_{y}",
105
+ 1,
106
+ [x + margin, x + margin + 12.5],
107
+ [y + margin, y + margin + 12.5],
108
+ size=12.5,
109
+ )
110
+ )
111
+ fields.append(
112
+ Fields.ImageField(
113
+ f"image_{x}_{y}",
114
+ 1,
115
+ x + margin + margin,
116
+ y,
117
+ width=margin,
118
+ height=margin,
119
+ )
120
+ )
121
+ fields.append(
122
+ Fields.SignatureField(
123
+ f"signature_{x}_{y}",
124
+ 1,
125
+ x + margin + margin,
126
+ y + margin,
127
+ width=margin,
128
+ height=margin,
129
+ )
130
+ )
131
+ y += 50
132
+ y = 0
133
+ x += 75
134
+
135
+ with open(expected_path, "rb+") as f:
136
+ obj = PdfWrapper(os.path.join(pdf_samples, "dummy.pdf")).bulk_create_fields(
137
+ fields
138
+ )
139
+
140
+ request.config.results["expected_path"] = expected_path
141
+ request.config.results["stream"] = obj.read()
142
+
143
+ expected = f.read()
144
+
145
+ assert len(obj.read()) == len(expected)
146
+ assert obj.read() == expected