PyPDFForm 2.4.0__tar.gz → 3.0.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 (84) hide show
  1. {pypdfform-2.4.0 → pypdfform-3.0.0}/PKG-INFO +6 -7
  2. pypdfform-3.0.0/PyPDFForm/__init__.py +28 -0
  3. pypdfform-3.0.0/PyPDFForm/adapter.py +68 -0
  4. {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm/constants.py +29 -34
  5. pypdfform-3.0.0/PyPDFForm/coordinate.py +111 -0
  6. pypdfform-3.0.0/PyPDFForm/filler.py +176 -0
  7. pypdfform-3.0.0/PyPDFForm/font.py +247 -0
  8. pypdfform-3.0.0/PyPDFForm/hooks.py +256 -0
  9. pypdfform-3.0.0/PyPDFForm/image.py +119 -0
  10. pypdfform-3.0.0/PyPDFForm/middleware/base.py +116 -0
  11. pypdfform-3.0.0/PyPDFForm/middleware/checkbox.py +70 -0
  12. pypdfform-3.0.0/PyPDFForm/middleware/dropdown.py +83 -0
  13. pypdfform-3.0.0/PyPDFForm/middleware/image.py +22 -0
  14. pypdfform-3.0.0/PyPDFForm/middleware/radio.py +72 -0
  15. pypdfform-3.0.0/PyPDFForm/middleware/signature.py +73 -0
  16. pypdfform-3.0.0/PyPDFForm/middleware/text.py +123 -0
  17. pypdfform-3.0.0/PyPDFForm/patterns.py +230 -0
  18. pypdfform-3.0.0/PyPDFForm/template.py +267 -0
  19. pypdfform-3.0.0/PyPDFForm/utils.py +322 -0
  20. pypdfform-3.0.0/PyPDFForm/watermark.py +271 -0
  21. pypdfform-3.0.0/PyPDFForm/widgets/base.py +151 -0
  22. pypdfform-3.0.0/PyPDFForm/widgets/checkbox.py +36 -0
  23. pypdfform-3.0.0/PyPDFForm/widgets/dropdown.py +49 -0
  24. pypdfform-3.0.0/PyPDFForm/widgets/image.py +26 -0
  25. pypdfform-3.0.0/PyPDFForm/widgets/radio.py +68 -0
  26. pypdfform-3.0.0/PyPDFForm/widgets/signature.py +120 -0
  27. pypdfform-3.0.0/PyPDFForm/widgets/text.py +42 -0
  28. pypdfform-3.0.0/PyPDFForm/wrapper.py +692 -0
  29. {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm.egg-info/PKG-INFO +6 -7
  30. {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm.egg-info/SOURCES.txt +3 -6
  31. {pypdfform-2.4.0 → pypdfform-3.0.0}/README.md +5 -6
  32. {pypdfform-2.4.0 → pypdfform-3.0.0}/pyproject.toml +1 -0
  33. pypdfform-3.0.0/tests/test_adobe_mode.py +198 -0
  34. pypdfform-3.0.0/tests/test_create_widget.py +1135 -0
  35. pypdfform-3.0.0/tests/test_dropdown.py +360 -0
  36. pypdfform-3.0.0/tests/test_fill_max_length_text_field.py +446 -0
  37. {pypdfform-2.4.0 → pypdfform-3.0.0}/tests/test_fill_method.py +6 -6
  38. {pypdfform-2.4.0 → pypdfform-3.0.0}/tests/test_functional.py +503 -223
  39. {pypdfform-2.4.0 → pypdfform-3.0.0}/tests/test_paragraph.py +183 -86
  40. {pypdfform-2.4.0 → pypdfform-3.0.0}/tests/test_signature.py +12 -18
  41. pypdfform-3.0.0/tests/test_widget_attr_trigger.py +208 -0
  42. pypdfform-2.4.0/PyPDFForm/__init__.py +0 -12
  43. pypdfform-2.4.0/PyPDFForm/adapter.py +0 -66
  44. pypdfform-2.4.0/PyPDFForm/coordinate.py +0 -487
  45. pypdfform-2.4.0/PyPDFForm/filler.py +0 -400
  46. pypdfform-2.4.0/PyPDFForm/font.py +0 -245
  47. pypdfform-2.4.0/PyPDFForm/image.py +0 -69
  48. pypdfform-2.4.0/PyPDFForm/middleware/base.py +0 -110
  49. pypdfform-2.4.0/PyPDFForm/middleware/checkbox.py +0 -97
  50. pypdfform-2.4.0/PyPDFForm/middleware/dropdown.py +0 -72
  51. pypdfform-2.4.0/PyPDFForm/middleware/image.py +0 -34
  52. pypdfform-2.4.0/PyPDFForm/middleware/radio.py +0 -73
  53. pypdfform-2.4.0/PyPDFForm/middleware/signature.py +0 -88
  54. pypdfform-2.4.0/PyPDFForm/middleware/text.py +0 -112
  55. pypdfform-2.4.0/PyPDFForm/patterns.py +0 -310
  56. pypdfform-2.4.0/PyPDFForm/template.py +0 -614
  57. pypdfform-2.4.0/PyPDFForm/utils.py +0 -308
  58. pypdfform-2.4.0/PyPDFForm/watermark.py +0 -402
  59. pypdfform-2.4.0/PyPDFForm/widgets/base.py +0 -170
  60. pypdfform-2.4.0/PyPDFForm/widgets/checkbox.py +0 -39
  61. pypdfform-2.4.0/PyPDFForm/widgets/dropdown.py +0 -56
  62. pypdfform-2.4.0/PyPDFForm/widgets/image.py +0 -24
  63. pypdfform-2.4.0/PyPDFForm/widgets/radio.py +0 -78
  64. pypdfform-2.4.0/PyPDFForm/widgets/signature.py +0 -131
  65. pypdfform-2.4.0/PyPDFForm/widgets/text.py +0 -41
  66. pypdfform-2.4.0/PyPDFForm/wrapper.py +0 -756
  67. pypdfform-2.4.0/tests/test_adobe_mode.py +0 -158
  68. pypdfform-2.4.0/tests/test_create_widget.py +0 -680
  69. pypdfform-2.4.0/tests/test_dropdown.py +0 -181
  70. pypdfform-2.4.0/tests/test_dropdown_simple.py +0 -176
  71. pypdfform-2.4.0/tests/test_fill_max_length_text_field.py +0 -228
  72. pypdfform-2.4.0/tests/test_fill_max_length_text_field_simple.py +0 -208
  73. pypdfform-2.4.0/tests/test_functional_simple.py +0 -177
  74. pypdfform-2.4.0/tests/test_paragraph_simple.py +0 -246
  75. pypdfform-2.4.0/tests/test_preview.py +0 -86
  76. {pypdfform-2.4.0 → pypdfform-3.0.0}/LICENSE +0 -0
  77. {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm/middleware/__init__.py +0 -0
  78. {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm/widgets/__init__.py +0 -0
  79. {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm/widgets/bedrock.py +0 -0
  80. {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  81. {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm.egg-info/requires.txt +0 -0
  82. {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm.egg-info/top_level.txt +0 -0
  83. {pypdfform-2.4.0 → pypdfform-3.0.0}/setup.cfg +0 -0
  84. {pypdfform-2.4.0 → pypdfform-3.0.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: 2.4.0
3
+ Version: 3.0.0
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -42,7 +42,7 @@ Dynamic: license-file
42
42
  <p align="center">
43
43
  <a href="https://pypi.org/project/PyPDFForm/"><img src="https://img.shields.io/pypi/v/pypdfform?logo=pypi&logoColor=white&label=version&labelColor=black&color=magenta&style=for-the-badge"></a>
44
44
  <a href="https://chinapandaman.github.io/PyPDFForm/"><img src="https://img.shields.io/github/v/release/chinapandaman/pypdfform?logo=read%20the%20docs&logoColor=white&label=docs&labelColor=black&color=cyan&style=for-the-badge"></a>
45
- <a href="https://github.com/chinapandaman/PyPDFForm/actions/workflows/python-package.yml"><img src="https://img.shields.io/github/actions/workflow/status/chinapandaman/pypdfform/python-package.yml?logo=github&logoColor=white&label=tests&labelColor=black&color=green&style=for-the-badge"></a>
45
+ <a href="https://github.com/chinapandaman/PyPDFForm/actions/workflows/python-package.yml"><img src="https://img.shields.io/badge/coverage-100%25-green?logo=codecov&logoColor=white&labelColor=black&style=for-the-badge"></a>
46
46
  <a href="https://github.com/chinapandaman/PyPDFForm/raw/master/LICENSE"><img src="https://img.shields.io/github/license/chinapandaman/pypdfform?logo=github&logoColor=white&label=license&labelColor=black&color=orange&style=for-the-badge"></a>
47
47
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/pypi/pyversions/pypdfform?logo=python&logoColor=white&label=python&labelColor=black&color=gold&style=for-the-badge"></a>
48
48
  <a href="https://pypistats.org/packages/pypdfform"><img src="https://img.shields.io/pypi/dm/pypdfform?logo=pypi&logoColor=white&label=downloads&labelColor=black&color=blue&style=for-the-badge"></a>
@@ -55,7 +55,7 @@ functionalities needed to interact with PDF forms:
55
55
 
56
56
  * Inspect what data a PDF form needs to be filled with.
57
57
  * Fill a PDF form by simply creating a Python dictionary.
58
- * Create a subset of form widgets on a PDF.
58
+ * Create form fields on a PDF.
59
59
 
60
60
  It also supports other common utilities such as extracting pages and merging multiple PDFs together.
61
61
 
@@ -75,7 +75,7 @@ A sample PDF form can be found [here](https://github.com/chinapandaman/PyPDFForm
75
75
  ```python
76
76
  from PyPDFForm import PdfWrapper
77
77
 
78
- filled = PdfWrapper("sample_template.pdf").fill(
78
+ filled = PdfWrapper("sample_template.pdf", adobe_mode=True).fill(
79
79
  {
80
80
  "test": "test_1",
81
81
  "check": True,
@@ -86,12 +86,11 @@ filled = PdfWrapper("sample_template.pdf").fill(
86
86
  },
87
87
  )
88
88
 
89
- with open("output.pdf", "wb+") as output:
90
- output.write(filled.read())
89
+ filled.write("output.pdf")
91
90
  ```
92
91
 
93
92
  After running the above code snippet you can find `output.pdf` at the location you specified,
94
- and it should look like [this](https://github.com/chinapandaman/PyPDFForm/raw/master/pdf_samples/sample_filled.pdf).
93
+ and it should look like [this](https://github.com/chinapandaman/PyPDFForm/raw/master/pdf_samples/adobe_mode/sample_filled.pdf).
95
94
 
96
95
  ## Documentation
97
96
 
@@ -0,0 +1,28 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ PyPDFForm is a pure Python library designed to streamline the process of filling PDF forms programmatically.
4
+
5
+ It provides a simple and intuitive API for interacting with PDF forms, allowing users to:
6
+
7
+ - Fill text fields with custom data.
8
+ - Check or uncheck checkboxes.
9
+ - Select radio button options.
10
+ - Add images to image fields.
11
+ - Flatten the filled form to prevent further modifications.
12
+
13
+ The library supports various PDF form features, including:
14
+
15
+ - Text field alignment (left, center, right).
16
+ - Font customization (size, color, family).
17
+ - Image field resizing and positioning.
18
+ - Handling of complex form structures.
19
+
20
+ PyPDFForm aims to simplify PDF form manipulation, making it accessible to developers of all skill levels.
21
+ """
22
+
23
+ __version__ = "3.0.0"
24
+
25
+ from .middleware.text import Text # exposing for setting global font attrs
26
+ from .wrapper import PdfWrapper
27
+
28
+ __all__ = ["PdfWrapper", "Text"]
@@ -0,0 +1,68 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Module for adapting different types of input to a consistent byte stream.
4
+
5
+ This module provides utility functions to adapt various types of input,
6
+ such as file paths, file-like objects, and byte streams, into a consistent
7
+ byte stream format. This is particularly useful when dealing with PDF form
8
+ filling operations, where the input PDF template can be provided in different
9
+ forms. The module ensures that the input is properly converted into a byte
10
+ stream before further processing.
11
+ """
12
+
13
+ from os.path import isfile
14
+ from typing import Any, BinaryIO, Union
15
+
16
+
17
+ def readable(obj: Any) -> bool:
18
+ """
19
+ Check if an object has a readable "read" attribute.
20
+
21
+ This function determines whether the provided object has a "read" attribute that is callable.
22
+ It is used to identify file-like objects or streams that can be read from.
23
+
24
+ Args:
25
+ obj (Any): The object to check for a readable "read" attribute.
26
+
27
+ Returns:
28
+ bool: True if the object has a callable "read" attribute, indicating it is readable.
29
+ Returns False otherwise.
30
+ """
31
+ return callable(getattr(obj, "read", None))
32
+
33
+
34
+ def fp_or_f_obj_or_stream_to_stream(
35
+ fp_or_f_obj_or_stream: Union[bytes, str, BinaryIO],
36
+ ) -> bytes:
37
+ """
38
+ Adapt a file path, file object, or stream to a byte stream.
39
+
40
+ This function takes a file path, a file object, or a byte stream and adapts it to a consistent byte stream.
41
+ It handles different input types, including:
42
+ - byte streams (bytes)
43
+ - file paths (str)
44
+ - file-like objects with a read() method (BinaryIO)
45
+
46
+ Args:
47
+ fp_or_f_obj_or_stream (Union[bytes, str, BinaryIO]): The input to adapt.
48
+ It can be a byte stream, a file path (string), or a file object.
49
+
50
+ Returns:
51
+ bytes: The byte stream representation of the input.
52
+ Returns an empty byte string if the file path does not exist.
53
+ """
54
+ # not cached to handle writing to the same disk file
55
+ result = b""
56
+ if isinstance(fp_or_f_obj_or_stream, bytes):
57
+ result = fp_or_f_obj_or_stream
58
+
59
+ elif readable(fp_or_f_obj_or_stream):
60
+ result = fp_or_f_obj_or_stream.read()
61
+
62
+ elif isinstance(fp_or_f_obj_or_stream, str):
63
+ if not isfile(fp_or_f_obj_or_stream):
64
+ pass
65
+ else:
66
+ with open(fp_or_f_obj_or_stream, "rb+") as _file:
67
+ result = _file.read()
68
+ return result
@@ -1,15 +1,18 @@
1
1
  # -*- coding: utf-8 -*-
2
- """Centralized location for all PyPDFForm constants and default values.
2
+ """
3
+ Module containing constants used throughout the PyPDFForm library.
4
+
5
+ This module defines a collection of constants that are used across various
6
+ modules within the PyPDFForm library. These constants include:
3
7
 
4
- This module defines all the constants used throughout the package including:
5
- - PDF version identifiers and format strings
6
- - PDF field type and property constants
7
- - Default values for fonts, colors and sizes
8
- - Special identifiers and symbols
9
- - Field flag bitmasks
10
- - Button style definitions
8
+ - String identifiers for PDF syntax elements (e.g., /Annots, /Rect, /FT).
9
+ - Numerical values representing field flags (e.g., READ_ONLY, MULTILINE).
10
+ - Default values for fonts, font sizes, and font colors.
11
+ - Identifiers for image fields and coordinate grid calculations.
12
+ - Version identifiers for PDF versions.
11
13
 
12
- All constants are organized by category and used consistently across the package.
14
+ Using constants improves code readability and maintainability by providing
15
+ meaningful names for frequently used values and reducing the risk of typos.
13
16
  """
14
17
 
15
18
  from typing import Union
@@ -54,25 +57,33 @@ I = "/I" # noqa: E741
54
57
  N = "/N"
55
58
  Sig = "/Sig"
56
59
  DA = "/DA"
60
+ DR = "/DR"
57
61
  DV = "/DV"
58
62
  Btn = "/Btn"
59
63
  MaxLen = "/MaxLen"
60
64
  Q = "/Q"
61
65
  Ch = "/Ch"
62
66
  Opt = "/Opt"
63
- MK = "/MK"
64
- CA = "/CA"
65
- BC = "/BC"
66
- BG = "/BG"
67
- BS = "/BS"
68
- W = "/W"
69
- S = "/S"
70
- D = "/D"
71
- U = "/U"
72
67
  AS = "/AS"
73
68
  Yes = "/Yes"
74
69
  Off = "/Off"
75
70
 
71
+ # Font dict
72
+ Length1 = "/Length1"
73
+ Type = "/Type"
74
+ FontDescriptor = "/FontDescriptor"
75
+ FontName = "/FontName"
76
+ FontFile2 = "/FontFile2"
77
+ Font = "/Font"
78
+ Subtype = "/Subtype"
79
+ TrueType = "/TrueType"
80
+ BaseFont = "/BaseFont"
81
+ Encoding = "/Encoding"
82
+ WinAnsiEncoding = "/WinAnsiEncoding"
83
+
84
+ Resources = "/Resources"
85
+ FONT_NAME_PREFIX = "/F"
86
+
76
87
  # For Adobe Acrobat
77
88
  AcroForm = "/AcroForm"
78
89
  Root = "/Root"
@@ -86,27 +97,11 @@ COMB = 1 << 24
86
97
 
87
98
  FONT_SIZE_IDENTIFIER = "Tf"
88
99
  FONT_COLOR_IDENTIFIER = " rg"
89
- DEFAULT_BORDER_WIDTH = 1
90
100
  DEFAULT_FONT = "Helvetica"
91
101
  DEFAULT_FONT_SIZE = 12
92
102
  DEFAULT_FONT_COLOR = (0, 0, 0)
93
- PREVIEW_FONT_COLOR = (1, 0, 0)
94
-
95
- NEW_LINE_SYMBOL = "\n"
96
103
 
97
104
  IMAGE_FIELD_IDENTIFIER = "event.target.buttonImportIcon();"
98
105
 
99
- DEFAULT_CHECKBOX_STYLE = "\u2713"
100
- DEFAULT_RADIO_STYLE = "\u25cf"
101
- BUTTON_STYLES = {
102
- "4": "\u2713", # check
103
- "5": "\u00d7", # cross
104
- "l": "\u25cf", # circle
105
- }
106
-
107
106
  COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO = DEFAULT_FONT_SIZE / 100
108
107
  UNIQUE_SUFFIX_LENGTH = 20
109
-
110
- # Used for adjusting paragraph font size
111
- FONT_SIZE_REDUCE_STEP = 0.5
112
- MARGIN_BETWEEN_LINES = 2
@@ -0,0 +1,111 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Module for generating coordinate grids on PDFs.
4
+
5
+ This module provides functionality to generate coordinate grids on existing PDF documents.
6
+ It allows developers to visualize the coordinate system of each page in a PDF, which can be helpful
7
+ for debugging and precisely positioning elements when filling or drawing on PDF forms.
8
+ """
9
+
10
+ from typing import Tuple
11
+
12
+ from pypdf import PdfReader
13
+ from reportlab.pdfbase.pdfmetrics import stringWidth
14
+
15
+ from .constants import COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO, DEFAULT_FONT
16
+ from .middleware.text import Text
17
+ from .utils import stream_to_io
18
+ from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
19
+
20
+
21
+ def generate_coordinate_grid(
22
+ pdf: bytes, color: Tuple[float, float, float], margin: float
23
+ ) -> bytes:
24
+ """
25
+ Generates a coordinate grid overlay on a PDF document.
26
+
27
+ This function takes a PDF file as bytes, along with a color and margin, and generates
28
+ a coordinate grid on each page of the PDF. The grid consists of lines and text indicating
29
+ the X and Y coordinates. This can be useful for visualizing the layout and positioning
30
+ elements on the PDF.
31
+
32
+ Args:
33
+ pdf (bytes): The PDF file as bytes.
34
+ color (Tuple[float, float, float]): The color of the grid lines and text as a tuple of RGB values (0.0-1.0).
35
+ For example, (0.0, 0.0, 0.0) represents black.
36
+ margin (float): The margin between the grid lines and the edge of the page, in points.
37
+ This value determines the spacing of the grid.
38
+
39
+ Returns:
40
+ bytes: The PDF file with the coordinate grid overlay as bytes.
41
+ """
42
+ pdf_file = PdfReader(stream_to_io(pdf))
43
+ lines_by_page = {}
44
+ texts_by_page = {}
45
+ watermarks = []
46
+
47
+ for i, page in enumerate(pdf_file.pages):
48
+ lines_by_page[i + 1] = []
49
+ texts_by_page[i + 1] = []
50
+ width = float(page.mediabox[2])
51
+ height = float(page.mediabox[3])
52
+
53
+ current = margin
54
+ while current < width:
55
+ lines_by_page[i + 1].append(
56
+ {
57
+ "src_x": current,
58
+ "src_y": 0,
59
+ "dest_x": current,
60
+ "dest_y": height,
61
+ "color": color,
62
+ }
63
+ )
64
+ current += margin
65
+
66
+ current = margin
67
+ while current < height:
68
+ lines_by_page[i + 1].append(
69
+ {
70
+ "src_x": 0,
71
+ "src_y": current,
72
+ "dest_x": width,
73
+ "dest_y": current,
74
+ "color": color,
75
+ }
76
+ )
77
+ current += margin
78
+
79
+ x = margin
80
+ while x < width:
81
+ y = margin
82
+ while y < height:
83
+ value = f"({x}, {y})"
84
+ font_size = margin * COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO
85
+ text = Text("new_coordinate", value)
86
+ text.font = DEFAULT_FONT
87
+ text.font_size = font_size
88
+ text.font_color = color
89
+ texts_by_page[i + 1].append(
90
+ {
91
+ "widget": text,
92
+ "x": x - stringWidth(value, DEFAULT_FONT, font_size),
93
+ "y": y - font_size,
94
+ }
95
+ )
96
+ y += margin
97
+ x += margin
98
+
99
+ for page, lines in lines_by_page.items():
100
+ watermarks.append(
101
+ create_watermarks_and_draw(pdf, page, "line", lines)[page - 1]
102
+ )
103
+
104
+ result = merge_watermarks_with_pdf(pdf, watermarks)
105
+ watermarks = []
106
+ for page, texts in texts_by_page.items():
107
+ watermarks.append(
108
+ create_watermarks_and_draw(pdf, page, "text", texts)[page - 1]
109
+ )
110
+
111
+ return merge_watermarks_with_pdf(result, watermarks)
@@ -0,0 +1,176 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Module containing functions to fill PDF forms.
4
+
5
+ This module provides the core functionality for filling PDF forms programmatically.
6
+ It includes functions for handling various form field types, such as text fields,
7
+ checkboxes, radio buttons, dropdowns, images, and signatures. The module also
8
+ supports flattening the filled form to prevent further modifications.
9
+ """
10
+
11
+ from io import BytesIO
12
+ from typing import Dict, Union, cast
13
+
14
+ from pypdf import PdfReader, PdfWriter
15
+ from pypdf.generic import DictionaryObject
16
+
17
+ from .constants import WIDGET_TYPES, Annots
18
+ from .image import get_draw_image_resolutions, get_image_dimensions
19
+ from .middleware.checkbox import Checkbox
20
+ from .middleware.dropdown import Dropdown
21
+ from .middleware.image import Image
22
+ from .middleware.radio import Radio
23
+ from .middleware.signature import Signature
24
+ from .middleware.text import Text
25
+ from .patterns import (flatten_generic, flatten_radio, update_checkbox_value,
26
+ update_dropdown_value, update_radio_value,
27
+ update_text_value)
28
+ from .template import get_widget_key
29
+ from .utils import stream_to_io
30
+ from .watermark import create_watermarks_and_draw, merge_watermarks_with_pdf
31
+
32
+
33
+ def signature_image_handler(
34
+ widget: dict, middleware: Union[Signature, Image], images_to_draw: list
35
+ ) -> bool:
36
+ """Handles signature and image widgets by extracting image data and preparing it for drawing.
37
+
38
+ This function processes signature and image widgets found in a PDF form. It extracts the
39
+ image data from the widget's middleware and prepares it for drawing on the form. The
40
+ function calculates the position and dimensions of the image based on the widget's
41
+ properties and the `preserve_aspect_ratio` setting. The image data is then stored in a
42
+ list for later drawing.
43
+
44
+ Args:
45
+ widget (dict): The widget dictionary representing the signature or image field.
46
+ middleware (Union[Signature, Image]): The middleware object containing the image data and properties.
47
+ images_to_draw (list): A list to store image data for drawing.
48
+
49
+ Returns:
50
+ bool: True if any image is to be drawn, False otherwise.
51
+ """
52
+ stream = middleware.stream
53
+ any_image_to_draw = False
54
+ if stream is not None:
55
+ any_image_to_draw = True
56
+ image_width, image_height = get_image_dimensions(stream)
57
+ x, y, width, height = get_draw_image_resolutions(
58
+ widget, middleware.preserve_aspect_ratio, image_width, image_height
59
+ )
60
+ images_to_draw.append(
61
+ {
62
+ "stream": stream,
63
+ "x": x,
64
+ "y": y,
65
+ "width": width,
66
+ "height": height,
67
+ }
68
+ )
69
+
70
+ return any_image_to_draw
71
+
72
+
73
+ def get_drawn_stream(to_draw: dict, stream: bytes, action: str) -> bytes:
74
+ """Applies watermarks to specific pages of a PDF based on the provided drawing instructions.
75
+
76
+ This function takes a dictionary of drawing instructions and applies watermarks to the
77
+ specified pages of a PDF. It iterates through the drawing instructions, creates watermarks
78
+ for each page, and merges the watermarks with the original PDF content. The function
79
+ supports various drawing actions, such as adding images or text.
80
+
81
+ Args:
82
+ to_draw (dict): A dictionary containing page numbers as keys and lists of drawing instructions as values.
83
+ Each drawing instruction specifies the type of drawing, position, dimensions, and content.
84
+ stream (bytes): The PDF content as bytes.
85
+ action (str): The type of action to perform (e.g., "image", "text").
86
+
87
+ Returns:
88
+ bytes: The modified PDF content with watermarks applied.
89
+ """
90
+ watermark_list = []
91
+ for page, stuffs in to_draw.items():
92
+ watermark_list.append(b"")
93
+ watermarks = create_watermarks_and_draw(stream, page, action, stuffs)
94
+ for i, watermark in enumerate(watermarks):
95
+ if watermark:
96
+ watermark_list[i] = watermark
97
+
98
+ return merge_watermarks_with_pdf(stream, watermark_list)
99
+
100
+
101
+ def fill(
102
+ template: bytes,
103
+ widgets: Dict[str, WIDGET_TYPES],
104
+ use_full_widget_name: bool,
105
+ flatten: bool = False,
106
+ ) -> tuple:
107
+ """Fills a PDF template with the given widgets.
108
+
109
+ This function fills a PDF template with the provided widget values. It iterates through the
110
+ widgets on each page of the PDF and updates their values based on the provided `widgets`
111
+ dictionary. The function supports various widget types, including text fields, checkboxes,
112
+ radio buttons, dropdowns, images, and signatures. It also supports flattening the filled
113
+ form to prevent further modifications.
114
+
115
+ Args:
116
+ template (bytes): The PDF template as bytes.
117
+ widgets (Dict[str, WIDGET_TYPES]): A dictionary of widgets to fill, where the keys are the
118
+ widget names and the values are the widget objects.
119
+ use_full_widget_name (bool): Whether to use the full widget name when looking up widgets
120
+ in the `widgets` dictionary.
121
+ flatten (bool): Whether to flatten the filled PDF. Defaults to False.
122
+
123
+ Returns:
124
+ tuple: A tuple containing the filled PDF as bytes and the image drawn stream as bytes, if any.
125
+ The image drawn stream is only returned if there are any image or signature widgets
126
+ in the form.
127
+ """
128
+ pdf = PdfReader(stream_to_io(template))
129
+ out = PdfWriter()
130
+ out.append(pdf)
131
+
132
+ radio_button_tracker = {}
133
+ images_to_draw = {}
134
+ any_image_to_draw = False
135
+
136
+ for page_num, page in enumerate(out.pages):
137
+ images_to_draw[page_num + 1] = []
138
+ for annot in page.get(Annots, []):
139
+ annot = cast(DictionaryObject, annot.get_object())
140
+ key = get_widget_key(annot.get_object(), use_full_widget_name)
141
+
142
+ widget = widgets.get(key)
143
+ if widget is None:
144
+ continue
145
+
146
+ # flatten all
147
+ if flatten:
148
+ (flatten_radio if isinstance(widget, Radio) else flatten_generic)(annot)
149
+ if widget.value is None:
150
+ continue
151
+
152
+ if isinstance(widgets[key], (Signature, Image)):
153
+ any_image_to_draw |= signature_image_handler(
154
+ annot, widgets[key], images_to_draw[page_num + 1]
155
+ )
156
+ elif type(widget) is Checkbox:
157
+ update_checkbox_value(annot, widget.value)
158
+ elif isinstance(widget, Radio):
159
+ if key not in radio_button_tracker:
160
+ radio_button_tracker[key] = 0
161
+ radio_button_tracker[key] += 1
162
+ if widget.value == radio_button_tracker[key] - 1:
163
+ update_radio_value(annot)
164
+ elif isinstance(widget, Dropdown):
165
+ update_dropdown_value(annot, widget)
166
+ elif isinstance(widget, Text):
167
+ update_text_value(annot, widget)
168
+
169
+ with BytesIO() as f:
170
+ out.write(f)
171
+ f.seek(0)
172
+ result = f.read()
173
+
174
+ return result, (
175
+ get_drawn_stream(images_to_draw, result, "image") if any_image_to_draw else None
176
+ )