PyPDFForm 2.2.4__tar.gz → 2.2.6__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.4 → pypdfform-2.2.6}/PKG-INFO +1 -1
  2. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/__init__.py +1 -1
  3. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/constants.py +3 -1
  4. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/coordinate.py +76 -32
  5. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/filler.py +51 -30
  6. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/patterns.py +14 -9
  7. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/utils.py +2 -0
  8. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/watermark.py +91 -96
  9. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/widgets/base.py +1 -1
  10. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/widgets/signature.py +1 -1
  11. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/wrapper.py +9 -6
  12. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm.egg-info/PKG-INFO +1 -1
  13. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_adobe_mode.py +65 -4
  14. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_dropdown_simple.py +1 -1
  15. {pypdfform-2.2.4 → pypdfform-2.2.6}/LICENSE +0 -0
  16. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/adapter.py +0 -0
  17. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/font.py +0 -0
  18. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/image.py +0 -0
  19. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/middleware/__init__.py +0 -0
  20. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/middleware/base.py +0 -0
  21. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/middleware/checkbox.py +0 -0
  22. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/middleware/dropdown.py +0 -0
  23. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/middleware/image.py +0 -0
  24. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/middleware/radio.py +0 -0
  25. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/middleware/signature.py +0 -0
  26. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/middleware/text.py +0 -0
  27. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/template.py +0 -0
  28. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/widgets/__init__.py +0 -0
  29. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/widgets/bedrock.py +0 -0
  30. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/widgets/checkbox.py +0 -0
  31. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/widgets/dropdown.py +0 -0
  32. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/widgets/image.py +0 -0
  33. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/widgets/radio.py +0 -0
  34. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm/widgets/text.py +0 -0
  35. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm.egg-info/SOURCES.txt +0 -0
  36. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  37. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm.egg-info/requires.txt +0 -0
  38. {pypdfform-2.2.4 → pypdfform-2.2.6}/PyPDFForm.egg-info/top_level.txt +0 -0
  39. {pypdfform-2.2.4 → pypdfform-2.2.6}/README.md +0 -0
  40. {pypdfform-2.2.4 → pypdfform-2.2.6}/pyproject.toml +0 -0
  41. {pypdfform-2.2.4 → pypdfform-2.2.6}/setup.cfg +0 -0
  42. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_create_widget.py +0 -0
  43. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_dropdown.py +0 -0
  44. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_fill_max_length_text_field.py +0 -0
  45. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_fill_max_length_text_field_simple.py +0 -0
  46. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_fill_method.py +0 -0
  47. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_functional.py +0 -0
  48. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_functional_simple.py +0 -0
  49. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_paragraph.py +0 -0
  50. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_paragraph_simple.py +0 -0
  51. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_preview.py +0 -0
  52. {pypdfform-2.2.4 → pypdfform-2.2.6}/tests/test_signature.py +0 -0
  53. {pypdfform-2.2.4 → pypdfform-2.2.6}/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.4
3
+ Version: 2.2.6
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.4"
8
+ __version__ = "2.2.6"
9
9
 
10
10
  from .wrapper import FormWrapper, PdfWrapper
11
11
 
@@ -32,7 +32,7 @@ VERSION_IDENTIFIERS = [
32
32
  b"%PDF-1.7",
33
33
  b"%PDF-2.0",
34
34
  ]
35
- VERSION_IDENTIFIER_PREFIX = b"%PDF-"
35
+ VERSION_IDENTIFIER_PREFIX = "%PDF-".encode("utf-8")
36
36
 
37
37
  WIDGET_TYPES = Union[Text, Checkbox, Radio, Dropdown, Signature, Image]
38
38
 
@@ -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
@@ -28,25 +28,51 @@ from .utils import extract_widget_property, handle_color, stream_to_io
28
28
  from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
29
29
 
30
30
 
31
- def get_draw_border_coordinates(widget: dict, shape: str) -> List[float]:
32
- """Calculates coordinates for drawing widget borders.
31
+ def get_draw_border_coordinates(widget: dict, shape: str) -> dict:
32
+ """Calculates coordinates for drawing widget borders in PDF coordinate space.
33
33
 
34
34
  Args:
35
- widget: PDF form widget dictionary containing Rect coordinates
36
- shape: Type of border to draw ("rectangle", "ellipse" or "line")
35
+ widget: PDF form widget dictionary containing Rect coordinates (in PDF points)
36
+ shape: Type of border to draw:
37
+ - "rect": Standard rectangular border
38
+ - "ellipse": Circular/oval border
39
+ - "line": Straight line border
37
40
 
38
41
  Returns:
39
- List[float]: Coordinates in format [x1, y1, x2, y2] for the border
40
- For ellipses: [center_x, center_y, radius_x, radius_y]
41
- For lines: [x1, y1, x2, y2] endpoints
42
+ dict: Coordinate dictionary with different keys depending on shape:
43
+ - For "rect":
44
+ {
45
+ "x": bottom-left x,
46
+ "y": bottom-left y,
47
+ "width": total width,
48
+ "height": total height
49
+ }
50
+ - For "ellipse":
51
+ {
52
+ "x1": left bound,
53
+ "y1": bottom bound,
54
+ "x2": right bound,
55
+ "y2": top bound
56
+ }
57
+ - For "line":
58
+ {
59
+ "src_x": start x,
60
+ "src_y": start y,
61
+ "dest_x": end x,
62
+ "dest_y": end y
63
+ }
64
+
65
+ Note:
66
+ All coordinates are in PDF points (1/72 inch) with origin (0,0) at bottom-left.
67
+ For ellipses, the bounds form a square that would contain the ellipse.
42
68
  """
43
69
 
44
- result = [
45
- float(widget[Rect][0]),
46
- float(widget[Rect][1]),
47
- abs(float(widget[Rect][0]) - float(widget[Rect][2])),
48
- abs(float(widget[Rect][1]) - float(widget[Rect][3])),
49
- ]
70
+ result = {
71
+ "x": float(widget[Rect][0]),
72
+ "y": float(widget[Rect][1]),
73
+ "width": abs(float(widget[Rect][0]) - float(widget[Rect][2])),
74
+ "height": abs(float(widget[Rect][1]) - float(widget[Rect][3])),
75
+ }
50
76
 
51
77
  if shape == "ellipse":
52
78
  width = abs(float(widget[Rect][0]) - float(widget[Rect][2]))
@@ -57,19 +83,19 @@ def get_draw_border_coordinates(widget: dict, shape: str) -> List[float]:
57
83
 
58
84
  less = min(width, height)
59
85
 
60
- result = [
61
- width_mid - less / 2,
62
- height_mid - less / 2,
63
- width_mid + less / 2,
64
- height_mid + less / 2,
65
- ]
86
+ result = {
87
+ "x1": width_mid - less / 2,
88
+ "y1": height_mid - less / 2,
89
+ "x2": width_mid + less / 2,
90
+ "y2": height_mid + less / 2,
91
+ }
66
92
  elif shape == "line":
67
- result = [
68
- float(widget[Rect][0]),
69
- float(widget[Rect][1]),
70
- float(widget[Rect][2]),
71
- float(widget[Rect][1]),
72
- ]
93
+ result = {
94
+ "src_x": float(widget[Rect][0]),
95
+ "src_y": float(widget[Rect][1]),
96
+ "dest_x": float(widget[Rect][2]),
97
+ "dest_y": float(widget[Rect][1]),
98
+ }
73
99
 
74
100
  return result
75
101
 
@@ -397,14 +423,32 @@ def generate_coordinate_grid(
397
423
  current = margin
398
424
  while current < width:
399
425
  lines_by_page[i + 1].append(
400
- [current, 0, current, height, handle_color([r, g, b]), None, 1, None]
426
+ {
427
+ "src_x": current,
428
+ "src_y": 0,
429
+ "dest_x": current,
430
+ "dest_y": height,
431
+ "border_color": handle_color([r, g, b]),
432
+ "background_color": None,
433
+ "border_width": 1,
434
+ "dash_array": None,
435
+ }
401
436
  )
402
437
  current += margin
403
438
 
404
439
  current = margin
405
440
  while current < height:
406
441
  lines_by_page[i + 1].append(
407
- [0, current, width, current, handle_color([r, g, b]), None, 1, None]
442
+ {
443
+ "src_x": 0,
444
+ "src_y": current,
445
+ "dest_x": width,
446
+ "dest_y": current,
447
+ "border_color": handle_color([r, g, b]),
448
+ "background_color": None,
449
+ "border_width": 1,
450
+ "dash_array": None,
451
+ }
408
452
  )
409
453
  current += margin
410
454
 
@@ -419,11 +463,11 @@ def generate_coordinate_grid(
419
463
  text.font_size = font_size
420
464
  text.font_color = color
421
465
  texts_by_page[i + 1].append(
422
- [
423
- text,
424
- x - stringWidth(value, DEFAULT_FONT, font_size),
425
- y - font_size,
426
- ]
466
+ {
467
+ "widget": text,
468
+ "x": x - stringWidth(value, DEFAULT_FONT, font_size),
469
+ "y": y - font_size,
470
+ }
427
471
  )
428
472
  y += margin
429
473
  x += margin
@@ -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,
@@ -104,13 +105,13 @@ def signature_image_handler(
104
105
  widget, middleware.preserve_aspect_ratio, image_width, image_height
105
106
  )
106
107
  images_to_draw.append(
107
- [
108
- stream,
109
- x,
110
- y,
111
- width,
112
- height,
113
- ]
108
+ {
109
+ "stream": stream,
110
+ "x": x,
111
+ "y": y,
112
+ "width": width,
113
+ "height": height,
114
+ }
114
115
  )
115
116
 
116
117
  return any_image_to_draw
@@ -172,19 +173,24 @@ def border_handler(
172
173
  shape = "rect"
173
174
 
174
175
  list_to_append.append(
175
- get_draw_border_coordinates(widget, shape)
176
- + [
177
- middleware.border_color,
178
- middleware.background_color,
179
- middleware.border_width,
180
- middleware.dash_array,
181
- ]
176
+ {
177
+ **get_draw_border_coordinates(widget, shape),
178
+ "border_color": middleware.border_color,
179
+ "background_color": middleware.background_color,
180
+ "border_width": middleware.border_width,
181
+ "dash_array": middleware.dash_array,
182
+ }
182
183
  )
183
184
 
184
185
  if shape == "line":
185
186
  rect_borders_to_draw.append(
186
- get_draw_border_coordinates(widget, "rect")
187
- + [None, middleware.background_color, 0, None]
187
+ {
188
+ **get_draw_border_coordinates(widget, "rect"),
189
+ "border_color": None,
190
+ "background_color": middleware.background_color,
191
+ "border_width": 0,
192
+ "dash_array": None,
193
+ }
188
194
  )
189
195
 
190
196
 
@@ -281,11 +287,11 @@ def fill(
281
287
  ]
282
288
  ):
283
289
  texts_to_draw[page].append(
284
- [
285
- to_draw,
286
- x,
287
- y,
288
- ]
290
+ {
291
+ "widget": to_draw,
292
+ "x": x,
293
+ "y": y,
294
+ }
289
295
  )
290
296
 
291
297
  result = template_stream
@@ -300,19 +306,34 @@ def fill(
300
306
  return result
301
307
 
302
308
 
303
- def enable_adobe_mode(pdf: PdfReader, adobe_mode: bool) -> None:
304
- """Configures PDF for Adobe Acrobat compatibility.
309
+ def enable_adobe_mode(reader: PdfReader, writer: PdfWriter, adobe_mode: bool) -> None:
310
+ """Configures the PDF for Adobe Acrobat compatibility by setting the NeedAppearances flag
311
+ and ensuring the AcroForm structure is properly initialized.
305
312
 
306
313
  Args:
307
- pdf: PdfReader instance of the PDF
308
- adobe_mode: If True, sets NeedAppearances flag for Acrobat
314
+ reader: PdfReader instance of the PDF
315
+ writer: PdfWriter instance to configure
316
+ adobe_mode: If True, enables Adobe Acrobat compatibility mode
309
317
  """
310
318
 
311
- if adobe_mode and AcroForm in pdf.trailer[Root]:
312
- pdf.trailer[Root][AcroForm].update(
319
+ if not adobe_mode:
320
+ return
321
+
322
+ # https://stackoverflow.com/questions/47288578/pdf-form-filled-with-pypdf2-does-not-show-in-print
323
+ if AcroForm in reader.trailer[Root]:
324
+ reader.trailer[Root][AcroForm].update(
313
325
  {NameObject(NeedAppearances): BooleanObject(True)}
314
326
  )
315
327
 
328
+ if AcroForm not in writer.root_object:
329
+ writer.root_object.update(
330
+ {NameObject(AcroForm): IndirectObject(len(writer.root_object), 0, writer)}
331
+ )
332
+ writer.root_object[AcroForm][NameObject(NeedAppearances)] = BooleanObject( # noqa
333
+ True
334
+ )
335
+ writer.root_object[AcroForm][NameObject(Fields)] = ArrayObject() # noqa
336
+
316
337
 
317
338
  def simple_fill(
318
339
  template: bytes,
@@ -338,8 +359,8 @@ def simple_fill(
338
359
  """
339
360
 
340
361
  pdf = PdfReader(stream_to_io(template))
341
- enable_adobe_mode(pdf, adobe_mode)
342
362
  out = PdfWriter()
363
+ enable_adobe_mode(pdf, out, adobe_mode)
343
364
  out.append(pdf)
344
365
 
345
366
  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:
@@ -11,6 +11,7 @@ This module contains general-purpose utilities used throughout PyPDFForm:
11
11
  """
12
12
 
13
13
  from collections.abc import Callable
14
+ from functools import lru_cache
14
15
  from io import BytesIO
15
16
  from secrets import choice
16
17
  from string import ascii_letters, digits, punctuation
@@ -29,6 +30,7 @@ from .middleware.radio import Radio
29
30
  from .middleware.text import Text
30
31
 
31
32
 
33
+ @lru_cache
32
34
  def stream_to_io(stream: bytes) -> BinaryIO:
33
35
  """Converts a byte stream to a seekable binary IO object.
34
36
 
@@ -22,7 +22,7 @@ from .patterns import WIDGET_KEY_PATTERNS
22
22
  from .utils import extract_widget_property, stream_to_io
23
23
 
24
24
 
25
- def draw_text(*args) -> None:
25
+ def draw_text(canvas: Canvas, **kwargs) -> None:
26
26
  """Draws text onto a watermark canvas with proper formatting.
27
27
 
28
28
  Handles:
@@ -32,16 +32,16 @@ def draw_text(*args) -> None:
32
32
  - Text alignment
33
33
 
34
34
  Args:
35
- args[0]: Canvas object to draw on
36
- args[1]: Text widget with content and properties
37
- args[2]: X coordinate for drawing
38
- args[3]: Y coordinate for drawing
35
+ canvas: Canvas object to draw on
36
+ **kwargs: Additional arguments including:
37
+ widget: Text widget with content and properties
38
+ x: X coordinate for drawing
39
+ y: Y coordinate for drawing
39
40
  """
40
41
 
41
- canvas = args[0]
42
- widget = args[1]
43
- coordinate_x = args[2]
44
- coordinate_y = args[3]
42
+ widget = kwargs["widget"]
43
+ coordinate_x = kwargs["x"]
44
+ coordinate_y = kwargs["y"]
45
45
 
46
46
  text_to_draw = widget.value
47
47
 
@@ -96,106 +96,105 @@ def draw_text(*args) -> None:
96
96
  canvas.restoreState()
97
97
 
98
98
 
99
- def draw_rect(*args) -> None:
99
+ def draw_rect(canvas: Canvas, **kwargs) -> None:
100
100
  """Draws a rectangle onto a watermark canvas.
101
101
 
102
102
  Args:
103
- args[0]: Canvas object to draw on
104
- args[1]: X coordinate of bottom-left corner
105
- args[2]: Y coordinate of bottom-left corner
106
- args[3]: Width of rectangle
107
- args[4]: Height of rectangle
108
- args[5]: Border color
109
- args[6]: Background color
110
- args[7]: Border width
111
- args[8]: Dash pattern for border
103
+ canvas: Canvas object to draw on
104
+ **kwargs: Additional arguments including:
105
+ x: X coordinate of bottom-left corner
106
+ y: Y coordinate of bottom-left corner
107
+ width: Width of rectangle
108
+ height: Height of rectangle
109
+ border_color: Border color
110
+ background_color: Background color
111
+ border_width: Border width
112
+ dash_array: Dash pattern for border
112
113
  """
113
114
 
114
- canvas = args[0]
115
- x = args[1]
116
- y = args[2]
117
- width = args[3]
118
- height = args[4]
115
+ x = kwargs["x"]
116
+ y = kwargs["y"]
117
+ width = kwargs["width"]
118
+ height = kwargs["height"]
119
119
 
120
120
  canvas.saveState()
121
- stroke, fill = set_border_and_background_styles(*args)
121
+ stroke, fill = set_border_and_background_styles(canvas, **kwargs)
122
122
  canvas.rect(x, y, width, height, stroke=stroke, fill=fill)
123
123
  canvas.restoreState()
124
124
 
125
125
 
126
- def draw_ellipse(*args) -> None:
126
+ def draw_ellipse(canvas: Canvas, **kwargs) -> None:
127
127
  """Draws an ellipse onto a watermark canvas.
128
128
 
129
129
  Args:
130
- args[0]: Canvas object to draw on
131
- args[1]: X coordinate of first bounding point
132
- args[2]: Y coordinate of first bounding point
133
- args[3]: X coordinate of second bounding point
134
- args[4]: Y coordinate of second bounding point
135
- args[5]: Border color
136
- args[6]: Background color
137
- args[7]: Border width
138
- args[8]: Dash pattern for border
130
+ canvas: Canvas object to draw on
131
+ **kwargs: Additional arguments including:
132
+ x1: X coordinate of first bounding point
133
+ y1: Y coordinate of first bounding point
134
+ x2: X coordinate of second bounding point
135
+ y2: Y coordinate of second bounding point
136
+ border_color: Border color
137
+ background_color: Background color
138
+ border_width: Border width
139
+ dash_array: Dash pattern for border
139
140
  """
140
141
 
141
- canvas = args[0]
142
- x1 = args[1]
143
- y1 = args[2]
144
- x2 = args[3]
145
- y2 = args[4]
142
+ x1 = kwargs["x1"]
143
+ y1 = kwargs["y1"]
144
+ x2 = kwargs["x2"]
145
+ y2 = kwargs["y2"]
146
146
 
147
147
  canvas.saveState()
148
- stroke, fill = set_border_and_background_styles(*args)
148
+ stroke, fill = set_border_and_background_styles(canvas, **kwargs)
149
149
  canvas.ellipse(x1, y1, x2, y2, stroke=stroke, fill=fill)
150
150
  canvas.restoreState()
151
151
 
152
152
 
153
- def draw_line(*args) -> None:
153
+ def draw_line(canvas: Canvas, **kwargs) -> None:
154
154
  """Draws a line onto a watermark canvas.
155
155
 
156
156
  Args:
157
- args[0]: Canvas object to draw on
158
- args[1]: X coordinate of start point
159
- args[2]: Y coordinate of start point
160
- args[3]: X coordinate of end point
161
- args[4]: Y coordinate of end point
162
- args[5]: Line color
163
- args[6]: Unused (kept for consistency)
164
- args[7]: Line width
165
- args[8]: Dash pattern for line
157
+ canvas: Canvas object to draw on
158
+ **kwargs: Additional arguments including:
159
+ src_x: X coordinate of start point
160
+ src_y: Y coordinate of start point
161
+ dest_x: X coordinate of end point
162
+ dest_y: Y coordinate of end point
163
+ border_color: Line color
164
+ border_width: Line width
165
+ dash_array: Dash pattern for line
166
166
  """
167
167
 
168
- canvas = args[0]
169
- src_x = args[1]
170
- src_y = args[2]
171
- dest_x = args[3]
172
- dest_y = args[4]
168
+ src_x = kwargs["src_x"]
169
+ src_y = kwargs["src_y"]
170
+ dest_x = kwargs["dest_x"]
171
+ dest_y = kwargs["dest_y"]
173
172
 
174
173
  canvas.saveState()
175
- set_border_and_background_styles(*args)
174
+ set_border_and_background_styles(canvas, **kwargs)
176
175
  canvas.line(src_x, src_y, dest_x, dest_y)
177
176
  canvas.restoreState()
178
177
 
179
178
 
180
- def set_border_and_background_styles(*args) -> tuple:
179
+ def set_border_and_background_styles(canvas: Canvas, **kwargs) -> tuple:
181
180
  """Configures stroke and fill styles for drawing operations.
182
181
 
183
182
  Args:
184
- args[0]: Canvas object to configure
185
- args[5]: Border color
186
- args[6]: Background color
187
- args[7]: Border width
188
- args[8]: Dash pattern for border
183
+ canvas: Canvas object to configure
184
+ **kwargs: Additional arguments including:
185
+ border_color: Border color
186
+ background_color: Background color
187
+ border_width: Border width
188
+ dash_array: Dash pattern for border
189
189
 
190
190
  Returns:
191
191
  tuple: (stroke_flag, fill_flag) indicating which styles were set
192
192
  """
193
193
 
194
- canvas = args[0]
195
- border_color = args[5]
196
- background_color = args[6]
197
- border_width = args[7]
198
- dash_array = args[8]
194
+ border_color = kwargs["border_color"]
195
+ background_color = kwargs["background_color"]
196
+ border_width = kwargs["border_width"]
197
+ dash_array = kwargs["dash_array"]
199
198
 
200
199
  stroke = 0
201
200
  fill = 0
@@ -213,24 +212,24 @@ def set_border_and_background_styles(*args) -> tuple:
213
212
  return stroke, fill
214
213
 
215
214
 
216
- def draw_image(*args) -> None:
215
+ def draw_image(canvas: Canvas, **kwargs) -> None:
217
216
  """Draws an image onto a watermark canvas.
218
217
 
219
218
  Args:
220
- args[0]: Canvas object to draw on
221
- args[1]: Image data as bytes
222
- args[2]: X coordinate for drawing
223
- args[3]: Y coordinate for drawing
224
- args[4]: Width of drawn image
225
- args[5]: Height of drawn image
219
+ canvas: Canvas object to draw on
220
+ **kwargs: Additional arguments including:
221
+ stream: Image data as bytes
222
+ x: X coordinate for drawing
223
+ y: Y coordinate for drawing
224
+ width: Width of drawn image
225
+ height: Height of drawn image
226
226
  """
227
227
 
228
- canvas = args[0]
229
- image_stream = args[1]
230
- coordinate_x = args[2]
231
- coordinate_y = args[3]
232
- width = args[4]
233
- height = args[5]
228
+ image_stream = kwargs["stream"]
229
+ coordinate_x = kwargs["x"]
230
+ coordinate_y = kwargs["y"]
231
+ width = kwargs["width"]
232
+ height = kwargs["height"]
234
233
 
235
234
  image_buff = BytesIO()
236
235
  image_buff.write(image_stream)
@@ -252,7 +251,7 @@ def create_watermarks_and_draw(
252
251
  pdf: bytes,
253
252
  page_number: int,
254
253
  action_type: str,
255
- actions: List[list],
254
+ actions: List[dict],
256
255
  ) -> List[bytes]:
257
256
  """Creates watermarks for each page with specified drawing operations.
258
257
 
@@ -277,21 +276,17 @@ def create_watermarks_and_draw(
277
276
  ),
278
277
  )
279
278
 
280
- if action_type == "image":
281
- for each in actions:
282
- draw_image(*([canvas, *each]))
283
- elif action_type == "text":
284
- for each in actions:
285
- draw_text(*([canvas, *each]))
286
- elif action_type == "line":
287
- for each in actions:
288
- draw_line(*([canvas, *each]))
289
- elif action_type == "rect":
290
- for each in actions:
291
- draw_rect(*([canvas, *each]))
292
- elif action_type == "ellipse":
279
+ action_type_to_func = {
280
+ "image": draw_image,
281
+ "text": draw_text,
282
+ "line": draw_line,
283
+ "rect": draw_rect,
284
+ "ellipse": draw_ellipse,
285
+ }
286
+
287
+ if action_type_to_func.get(action_type):
293
288
  for each in actions:
294
- draw_ellipse(*([canvas, *each]))
289
+ action_type_to_func[action_type](canvas, **each)
295
290
 
296
291
  canvas.save()
297
292
  buff.seek(0)
@@ -83,7 +83,7 @@ class Widget:
83
83
  )
84
84
  self.acro_form_params[param] = value
85
85
  elif user_input in self.NONE_DEFAULTS:
86
- self.acro_form_params[param] = None
86
+ self.acro_form_params[param] = None # noqa
87
87
 
88
88
  for each in self.ALLOWED_NON_ACRO_FORM_PARAMS:
89
89
  if each in kwargs:
@@ -101,7 +101,7 @@ class SignatureWidget:
101
101
 
102
102
  input_pdf = PdfReader(stream_to_io(stream))
103
103
  page_count = len(input_pdf.pages)
104
- pdf = PdfReader(stream_to_io(BEDROCK_PDF))
104
+ pdf = PdfReader(stream_to_io(BEDROCK_PDF)) # noqa
105
105
  out = PdfWriter()
106
106
  out.append(pdf)
107
107
 
@@ -620,11 +620,11 @@ class PdfWrapper(FormWrapper):
620
620
  page_number,
621
621
  "text",
622
622
  [
623
- [
624
- new_widget,
625
- x,
626
- y,
627
- ]
623
+ {
624
+ "widget": new_widget,
625
+ "x": x,
626
+ "y": y,
627
+ }
628
628
  ],
629
629
  )
630
630
 
@@ -667,7 +667,10 @@ class PdfWrapper(FormWrapper):
667
667
  image = fp_or_f_obj_or_stream_to_stream(image)
668
668
  image = rotate_image(image, rotation)
669
669
  watermarks = create_watermarks_and_draw(
670
- self.stream, page_number, "image", [[image, x, y, width, height]]
670
+ self.stream,
671
+ page_number,
672
+ "image",
673
+ [{"stream": image, "x": x, "y": y, "width": width, "height": height}],
671
674
  )
672
675
 
673
676
  stream_with_widgets = self.read()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 2.2.4
3
+ Version: 2.2.6
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