PyPDFForm 2.2.5__tar.gz → 2.2.7__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 (53) hide show
  1. {pypdfform-2.2.5 → pypdfform-2.2.7}/PKG-INFO +1 -1
  2. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/__init__.py +1 -1
  3. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/constants.py +2 -0
  4. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/filler.py +30 -10
  5. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/patterns.py +14 -9
  6. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/template.py +4 -5
  7. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/utils.py +5 -2
  8. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/wrapper.py +7 -3
  9. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm.egg-info/PKG-INFO +1 -1
  10. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_adobe_mode.py +65 -4
  11. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_dropdown_simple.py +1 -1
  12. {pypdfform-2.2.5 → pypdfform-2.2.7}/LICENSE +0 -0
  13. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/adapter.py +0 -0
  14. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/coordinate.py +0 -0
  15. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/font.py +0 -0
  16. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/image.py +0 -0
  17. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/middleware/__init__.py +0 -0
  18. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/middleware/base.py +0 -0
  19. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/middleware/checkbox.py +0 -0
  20. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/middleware/dropdown.py +0 -0
  21. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/middleware/image.py +0 -0
  22. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/middleware/radio.py +0 -0
  23. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/middleware/signature.py +0 -0
  24. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/middleware/text.py +0 -0
  25. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/watermark.py +0 -0
  26. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/widgets/__init__.py +0 -0
  27. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/widgets/base.py +0 -0
  28. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/widgets/bedrock.py +0 -0
  29. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/widgets/checkbox.py +0 -0
  30. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/widgets/dropdown.py +0 -0
  31. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/widgets/image.py +0 -0
  32. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/widgets/radio.py +0 -0
  33. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/widgets/signature.py +0 -0
  34. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm/widgets/text.py +0 -0
  35. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm.egg-info/SOURCES.txt +0 -0
  36. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  37. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm.egg-info/requires.txt +0 -0
  38. {pypdfform-2.2.5 → pypdfform-2.2.7}/PyPDFForm.egg-info/top_level.txt +0 -0
  39. {pypdfform-2.2.5 → pypdfform-2.2.7}/README.md +0 -0
  40. {pypdfform-2.2.5 → pypdfform-2.2.7}/pyproject.toml +0 -0
  41. {pypdfform-2.2.5 → pypdfform-2.2.7}/setup.cfg +0 -0
  42. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_create_widget.py +0 -0
  43. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_dropdown.py +0 -0
  44. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_fill_max_length_text_field.py +0 -0
  45. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_fill_max_length_text_field_simple.py +0 -0
  46. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_fill_method.py +0 -0
  47. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_functional.py +0 -0
  48. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_functional_simple.py +0 -0
  49. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_paragraph.py +0 -0
  50. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_paragraph_simple.py +0 -0
  51. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_preview.py +0 -0
  52. {pypdfform-2.2.5 → pypdfform-2.2.7}/tests/test_signature.py +0 -0
  53. {pypdfform-2.2.5 → pypdfform-2.2.7}/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: 2.2.5
3
+ Version: 2.2.7
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -5,7 +5,7 @@ This package provides tools for filling PDF forms, drawing text and images,
5
5
  and manipulating PDF form elements programmatically.
6
6
  """
7
7
 
8
- __version__ = "2.2.5"
8
+ __version__ = "2.2.7"
9
9
 
10
10
  from .wrapper import FormWrapper, PdfWrapper
11
11
 
@@ -50,6 +50,7 @@ Ff = "/Ff"
50
50
  Tx = "/Tx"
51
51
  V = "/V"
52
52
  AP = "/AP"
53
+ I = "/I" # noqa: E741
53
54
  N = "/N"
54
55
  Sig = "/Sig"
55
56
  DA = "/DA"
@@ -75,6 +76,7 @@ Off = "/Off"
75
76
  # For Adobe Acrobat
76
77
  AcroForm = "/AcroForm"
77
78
  Root = "/Root"
79
+ Fields = "/Fields"
78
80
  NeedAppearances = "/NeedAppearances"
79
81
 
80
82
  # Field flag bits
@@ -16,10 +16,11 @@ from io import BytesIO
16
16
  from typing import Dict, Tuple, Union, cast
17
17
 
18
18
  from pypdf import PdfReader, PdfWriter
19
- from pypdf.generic import BooleanObject, DictionaryObject, NameObject
19
+ from pypdf.generic import (ArrayObject, BooleanObject, DictionaryObject,
20
+ IndirectObject, NameObject)
20
21
 
21
22
  from .constants import (BUTTON_STYLES, DEFAULT_RADIO_STYLE, WIDGET_TYPES,
22
- AcroForm, Annots, NeedAppearances, Root, U)
23
+ AcroForm, Annots, Fields, NeedAppearances, Root, U)
23
24
  from .coordinate import (get_draw_border_coordinates,
24
25
  get_draw_checkbox_radio_coordinates,
25
26
  get_draw_image_coordinates_resolutions,
@@ -37,7 +38,7 @@ from .patterns import (WIDGET_KEY_PATTERNS, simple_flatten_generic,
37
38
  simple_flatten_radio, simple_update_checkbox_value,
38
39
  simple_update_dropdown_value, simple_update_radio_value,
39
40
  simple_update_text_value)
40
- from .template import get_widgets_by_page
41
+ from .template import get_widget_full_key, get_widgets_by_page
41
42
  from .utils import (checkbox_radio_to_draw, extract_widget_property,
42
43
  stream_to_io)
43
44
  from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
@@ -219,6 +220,7 @@ def get_drawn_stream(to_draw: dict, stream: bytes, action: str) -> bytes:
219
220
  def fill(
220
221
  template_stream: bytes,
221
222
  widgets: Dict[str, WIDGET_TYPES],
223
+ use_full_widget_name: bool,
222
224
  ) -> bytes:
223
225
  """Fills a PDF form using watermark technique for complex rendering.
224
226
 
@@ -230,6 +232,7 @@ def fill(
230
232
  Args:
231
233
  template_stream: Input PDF form as bytes
232
234
  widgets: Dictionary mapping field names to widget middleware
235
+ use_full_widget_name: If True, uses the full widget name as the key in the widgets dictionary
233
236
 
234
237
  Returns:
235
238
  bytes: Filled PDF form as bytes
@@ -252,6 +255,8 @@ def fill(
252
255
  line_borders_to_draw[page] = []
253
256
  for widget_dict in widget_dicts:
254
257
  key = extract_widget_property(widget_dict, WIDGET_KEY_PATTERNS, None, str)
258
+ if use_full_widget_name:
259
+ key = get_widget_full_key(widget_dict)
255
260
  text_needs_to_be_drawn = False
256
261
  to_draw = x = y = None
257
262
 
@@ -305,19 +310,34 @@ def fill(
305
310
  return result
306
311
 
307
312
 
308
- def enable_adobe_mode(pdf: PdfReader, adobe_mode: bool) -> None:
309
- """Configures PDF for Adobe Acrobat compatibility.
313
+ def enable_adobe_mode(reader: PdfReader, writer: PdfWriter, adobe_mode: bool) -> None:
314
+ """Configures the PDF for Adobe Acrobat compatibility by setting the NeedAppearances flag
315
+ and ensuring the AcroForm structure is properly initialized.
310
316
 
311
317
  Args:
312
- pdf: PdfReader instance of the PDF
313
- adobe_mode: If True, sets NeedAppearances flag for Acrobat
318
+ reader: PdfReader instance of the PDF
319
+ writer: PdfWriter instance to configure
320
+ adobe_mode: If True, enables Adobe Acrobat compatibility mode
314
321
  """
315
322
 
316
- if adobe_mode and AcroForm in pdf.trailer[Root]:
317
- pdf.trailer[Root][AcroForm].update(
323
+ if not adobe_mode:
324
+ return
325
+
326
+ # https://stackoverflow.com/questions/47288578/pdf-form-filled-with-pypdf2-does-not-show-in-print
327
+ if AcroForm in reader.trailer[Root]:
328
+ reader.trailer[Root][AcroForm].update(
318
329
  {NameObject(NeedAppearances): BooleanObject(True)}
319
330
  )
320
331
 
332
+ if AcroForm not in writer.root_object:
333
+ writer.root_object.update(
334
+ {NameObject(AcroForm): IndirectObject(len(writer.root_object), 0, writer)}
335
+ )
336
+ writer.root_object[AcroForm][NameObject(NeedAppearances)] = BooleanObject( # noqa
337
+ True
338
+ )
339
+ writer.root_object[AcroForm][NameObject(Fields)] = ArrayObject() # noqa
340
+
321
341
 
322
342
  def simple_fill(
323
343
  template: bytes,
@@ -343,8 +363,8 @@ def simple_fill(
343
363
  """
344
364
 
345
365
  pdf = PdfReader(stream_to_io(template))
346
- enable_adobe_mode(pdf, adobe_mode)
347
366
  out = PdfWriter()
367
+ enable_adobe_mode(pdf, out, adobe_mode)
348
368
  out.append(pdf)
349
369
 
350
370
  radio_button_tracker = {}
@@ -15,13 +15,13 @@ The module also contains utility functions for common PDF form operations
15
15
  like updating field values and flattening form fields.
16
16
  """
17
17
 
18
- from pypdf.generic import (DictionaryObject, NameObject, NumberObject,
19
- TextStringObject)
18
+ from pypdf.generic import (ArrayObject, DictionaryObject, NameObject,
19
+ NumberObject, TextStringObject)
20
20
 
21
21
  from .constants import (AP, AS, BC, BG, BS, CA, DA, DV, FT,
22
22
  IMAGE_FIELD_IDENTIFIER, JS, MK, MULTILINE, READ_ONLY,
23
- TU, A, Btn, Ch, D, Ff, N, Off, Opt, Parent, Q, S, Sig,
24
- T, Tx, V, W, Yes)
23
+ TU, A, Btn, Ch, D, Ff, I, N, Off, Opt, Parent, Q, S,
24
+ Sig, T, Tx, V, W, Yes)
25
25
  from .middleware.checkbox import Checkbox
26
26
  from .middleware.dropdown import Dropdown
27
27
  from .middleware.image import Image
@@ -160,13 +160,17 @@ def simple_update_radio_value(annot: DictionaryObject) -> None:
160
160
  """Update radio button annotation values to selected state.
161
161
 
162
162
  Modifies the appearance state (AS) of a radio button annotation and updates
163
- the parent's value (V) to reflect the selected state. Uses the annotation's
164
- appearance dictionary (AP/N) to determine valid states.
163
+ the parent's value (V) to reflect the selected state. Removes 'Opt' entry
164
+ from parent dictionary if present. Uses the annotation's appearance
165
+ dictionary (AP/N) to determine valid states.
165
166
 
166
167
  Args:
167
168
  annot: PDF radio button annotation dictionary to modify
168
169
  """
169
170
 
171
+ if Opt in annot[Parent]:
172
+ del annot[Parent][Opt] # noqa
173
+
170
174
  for each in annot[AP][N]: # noqa
171
175
  if str(each) != Off:
172
176
  annot[NameObject(AS)] = NameObject(each)
@@ -177,9 +181,9 @@ def simple_update_radio_value(annot: DictionaryObject) -> None:
177
181
  def simple_update_dropdown_value(annot: DictionaryObject, widget: Dropdown) -> None:
178
182
  """Update dropdown annotation values based on widget selection.
179
183
 
180
- Modifies the value (V) and appearance (AP) of a dropdown annotation to
181
- reflect the currently selected choice from the widget. Handles both
182
- standalone dropdowns and those with parent annotations.
184
+ Modifies the value (V), appearance (AP), and index (I) of a dropdown
185
+ annotation to reflect the currently selected choice from the widget.
186
+ Handles both standalone dropdowns and those with parent annotations.
183
187
 
184
188
  Args:
185
189
  annot: PDF dropdown annotation dictionary to modify
@@ -194,6 +198,7 @@ def simple_update_dropdown_value(annot: DictionaryObject, widget: Dropdown) -> N
194
198
  else:
195
199
  annot[NameObject(V)] = TextStringObject(widget.choices[widget.value])
196
200
  annot[NameObject(AP)] = TextStringObject(widget.choices[widget.value])
201
+ annot[NameObject(I)] = ArrayObject([NumberObject(widget.value)])
197
202
 
198
203
 
199
204
  def simple_update_text_value(annot: DictionaryObject, widget: Text) -> None:
@@ -246,15 +246,14 @@ def get_widgets_by_page(pdf: bytes) -> Dict[int, List[dict]]:
246
246
  return result
247
247
 
248
248
 
249
- def get_widget_full_key(widget: dict) -> Union[str, None]:
249
+ def get_widget_full_key(widget: dict) -> str:
250
250
  """Constructs a widget's full hierarchical key including parent names.
251
251
 
252
252
  Args:
253
253
  widget: PDF widget dictionary
254
254
 
255
255
  Returns:
256
- Union[str, None]: Full key in format "parent.child" if parent exists,
257
- otherwise None
256
+ str: Full key in format "parent.child" if parent exists, otherwise the widget's key
258
257
  """
259
258
 
260
259
  key = extract_widget_property(widget, WIDGET_KEY_PATTERNS, None, str)
@@ -264,9 +263,9 @@ def get_widget_full_key(widget: dict) -> Union[str, None]:
264
263
  and T in widget[Parent].get_object()
265
264
  and widget[Parent].get_object()[T] != key
266
265
  ):
267
- return f"{widget[Parent].get_object()[T]}.{key}"
266
+ key = f"{get_widget_full_key(widget[Parent].get_object())}.{key}"
268
267
 
269
- return None
268
+ return key
270
269
 
271
270
 
272
271
  def construct_widget(widget: dict, key: str) -> Union[WIDGET_TYPES, None]:
@@ -104,10 +104,13 @@ def checkbox_radio_to_draw(
104
104
  return new_widget
105
105
 
106
106
 
107
- def preview_widget_to_draw(widget: WIDGET_TYPES, with_preview_text: bool) -> Text:
107
+ def preview_widget_to_draw(
108
+ widget_name: str, widget: WIDGET_TYPES, with_preview_text: bool
109
+ ) -> Text:
108
110
  """Creates preview version of a widget showing field name/location.
109
111
 
110
112
  Args:
113
+ widget_name: Name of the widget to generate preview for
111
114
  widget: Widget to generate preview for
112
115
  with_preview_text: Whether to include field name in preview
113
116
 
@@ -117,7 +120,7 @@ def preview_widget_to_draw(widget: WIDGET_TYPES, with_preview_text: bool) -> Tex
117
120
 
118
121
  new_widget = Text(
119
122
  name=widget.name,
120
- value="{" + f" {widget.name} " + "}" if with_preview_text else None,
123
+ value="{" + f" {widget_name} " + "}" if with_preview_text else None,
121
124
  )
122
125
  new_widget.font = DEFAULT_FONT
123
126
  new_widget.font_size = DEFAULT_FONT_SIZE
@@ -362,9 +362,10 @@ class PdfWrapper(FormWrapper):
362
362
  fill(
363
363
  self.stream,
364
364
  {
365
- key: preview_widget_to_draw(value, True)
365
+ key: preview_widget_to_draw(key, value, True)
366
366
  for key, value in self.widgets.items()
367
367
  },
368
+ getattr(self, "use_full_widget_name"),
368
369
  )
369
370
  )
370
371
 
@@ -391,9 +392,10 @@ class PdfWrapper(FormWrapper):
391
392
  fill(
392
393
  self.stream,
393
394
  {
394
- key: preview_widget_to_draw(value, False)
395
+ key: preview_widget_to_draw(key, value, False)
395
396
  for key, value in self.widgets.items()
396
397
  },
398
+ getattr(self, "use_full_widget_name"),
397
399
  )
398
400
  ),
399
401
  color,
@@ -434,7 +436,9 @@ class PdfWrapper(FormWrapper):
434
436
  if self.read():
435
437
  self.widgets = set_character_x_paddings(self.stream, self.widgets)
436
438
 
437
- self.stream = remove_all_widgets(fill(self.stream, self.widgets))
439
+ self.stream = remove_all_widgets(
440
+ fill(self.stream, self.widgets, getattr(self, "use_full_widget_name"))
441
+ )
438
442
 
439
443
  return self
440
444
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 2.2.5
3
+ Version: 2.2.7
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -2,12 +2,12 @@
2
2
 
3
3
  import os
4
4
 
5
- from PyPDFForm import FormWrapper
5
+ from PyPDFForm import FormWrapper, PdfWrapper
6
6
 
7
7
 
8
- def test_dropdown_one(sample_template_with_dropdown, pdf_samples, request):
8
+ def test_dropdown_two(sample_template_with_dropdown, pdf_samples, request):
9
9
  expected_path = os.path.join(
10
- pdf_samples, "adobe_mode", "dropdown", "dropdown_one.pdf"
10
+ pdf_samples, "adobe_mode", "dropdown", "dropdown_two.pdf"
11
11
  )
12
12
  with open(expected_path, "rb+") as f:
13
13
  obj = FormWrapper(sample_template_with_dropdown).fill(
@@ -19,7 +19,7 @@ def test_dropdown_one(sample_template_with_dropdown, pdf_samples, request):
19
19
  "check_2": True,
20
20
  "check_3": True,
21
21
  "radio_1": 1,
22
- "dropdown_1": 0,
22
+ "dropdown_1": 1,
23
23
  },
24
24
  adobe_mode=True,
25
25
  )
@@ -95,3 +95,64 @@ def test_issue_613(pdf_samples, request):
95
95
 
96
96
  assert len(obj.read()) == len(expected)
97
97
  assert obj.stream == expected
98
+
99
+
100
+ def test_sample_template_libary(pdf_samples, request):
101
+ expected_path = os.path.join(
102
+ pdf_samples, "adobe_mode", "test_sample_template_libary.pdf"
103
+ )
104
+ template = (
105
+ PdfWrapper(os.path.join(pdf_samples, "dummy.pdf"))
106
+ .create_widget(
107
+ widget_type="text",
108
+ name="new_text_field_widget",
109
+ page_number=1,
110
+ x=60,
111
+ y=710,
112
+ )
113
+ .create_widget(
114
+ widget_type="checkbox",
115
+ name="new_checkbox_widget",
116
+ page_number=1,
117
+ x=100,
118
+ y=600,
119
+ )
120
+ .create_widget(
121
+ widget_type="radio",
122
+ name="new_radio_group",
123
+ page_number=1,
124
+ x=[50, 100, 150],
125
+ y=[50, 100, 150],
126
+ )
127
+ .create_widget(
128
+ widget_type="dropdown",
129
+ name="new_dropdown_widget",
130
+ page_number=1,
131
+ x=300,
132
+ y=710,
133
+ options=[
134
+ "foo",
135
+ "bar",
136
+ "foobar",
137
+ ],
138
+ )
139
+ )
140
+
141
+ with open(expected_path, "rb+") as f:
142
+ obj = FormWrapper(template.read()).fill(
143
+ {
144
+ "new_text_field_widget": "test text",
145
+ "new_checkbox_widget": True,
146
+ "new_radio_group": 1,
147
+ "new_dropdown_widget": 2,
148
+ },
149
+ adobe_mode=True,
150
+ )
151
+
152
+ request.config.results["expected_path"] = expected_path
153
+ request.config.results["stream"] = obj.read()
154
+
155
+ expected = f.read()
156
+
157
+ assert len(obj.read()) == len(expected)
158
+ assert obj.stream == expected
@@ -75,7 +75,7 @@ def test_dropdown_two(sample_template_with_dropdown, pdf_samples, request):
75
75
 
76
76
  def test_dropdown_two_flatten(sample_template_with_dropdown, pdf_samples, request):
77
77
  expected_path = os.path.join(
78
- pdf_samples, "simple", "dropdown", "dropdown_two_simple.pdf"
78
+ pdf_samples, "simple", "dropdown", "dropdown_two_flatten.pdf"
79
79
  )
80
80
  with open(expected_path, "rb+") as f:
81
81
  obj = FormWrapper(sample_template_with_dropdown).fill(
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes