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.
- {pypdfform-2.4.0 → pypdfform-3.0.0}/PKG-INFO +6 -7
- pypdfform-3.0.0/PyPDFForm/__init__.py +28 -0
- pypdfform-3.0.0/PyPDFForm/adapter.py +68 -0
- {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm/constants.py +29 -34
- pypdfform-3.0.0/PyPDFForm/coordinate.py +111 -0
- pypdfform-3.0.0/PyPDFForm/filler.py +176 -0
- pypdfform-3.0.0/PyPDFForm/font.py +247 -0
- pypdfform-3.0.0/PyPDFForm/hooks.py +256 -0
- pypdfform-3.0.0/PyPDFForm/image.py +119 -0
- pypdfform-3.0.0/PyPDFForm/middleware/base.py +116 -0
- pypdfform-3.0.0/PyPDFForm/middleware/checkbox.py +70 -0
- pypdfform-3.0.0/PyPDFForm/middleware/dropdown.py +83 -0
- pypdfform-3.0.0/PyPDFForm/middleware/image.py +22 -0
- pypdfform-3.0.0/PyPDFForm/middleware/radio.py +72 -0
- pypdfform-3.0.0/PyPDFForm/middleware/signature.py +73 -0
- pypdfform-3.0.0/PyPDFForm/middleware/text.py +123 -0
- pypdfform-3.0.0/PyPDFForm/patterns.py +230 -0
- pypdfform-3.0.0/PyPDFForm/template.py +267 -0
- pypdfform-3.0.0/PyPDFForm/utils.py +322 -0
- pypdfform-3.0.0/PyPDFForm/watermark.py +271 -0
- pypdfform-3.0.0/PyPDFForm/widgets/base.py +151 -0
- pypdfform-3.0.0/PyPDFForm/widgets/checkbox.py +36 -0
- pypdfform-3.0.0/PyPDFForm/widgets/dropdown.py +49 -0
- pypdfform-3.0.0/PyPDFForm/widgets/image.py +26 -0
- pypdfform-3.0.0/PyPDFForm/widgets/radio.py +68 -0
- pypdfform-3.0.0/PyPDFForm/widgets/signature.py +120 -0
- pypdfform-3.0.0/PyPDFForm/widgets/text.py +42 -0
- pypdfform-3.0.0/PyPDFForm/wrapper.py +692 -0
- {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm.egg-info/PKG-INFO +6 -7
- {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm.egg-info/SOURCES.txt +3 -6
- {pypdfform-2.4.0 → pypdfform-3.0.0}/README.md +5 -6
- {pypdfform-2.4.0 → pypdfform-3.0.0}/pyproject.toml +1 -0
- pypdfform-3.0.0/tests/test_adobe_mode.py +198 -0
- pypdfform-3.0.0/tests/test_create_widget.py +1135 -0
- pypdfform-3.0.0/tests/test_dropdown.py +360 -0
- pypdfform-3.0.0/tests/test_fill_max_length_text_field.py +446 -0
- {pypdfform-2.4.0 → pypdfform-3.0.0}/tests/test_fill_method.py +6 -6
- {pypdfform-2.4.0 → pypdfform-3.0.0}/tests/test_functional.py +503 -223
- {pypdfform-2.4.0 → pypdfform-3.0.0}/tests/test_paragraph.py +183 -86
- {pypdfform-2.4.0 → pypdfform-3.0.0}/tests/test_signature.py +12 -18
- pypdfform-3.0.0/tests/test_widget_attr_trigger.py +208 -0
- pypdfform-2.4.0/PyPDFForm/__init__.py +0 -12
- pypdfform-2.4.0/PyPDFForm/adapter.py +0 -66
- pypdfform-2.4.0/PyPDFForm/coordinate.py +0 -487
- pypdfform-2.4.0/PyPDFForm/filler.py +0 -400
- pypdfform-2.4.0/PyPDFForm/font.py +0 -245
- pypdfform-2.4.0/PyPDFForm/image.py +0 -69
- pypdfform-2.4.0/PyPDFForm/middleware/base.py +0 -110
- pypdfform-2.4.0/PyPDFForm/middleware/checkbox.py +0 -97
- pypdfform-2.4.0/PyPDFForm/middleware/dropdown.py +0 -72
- pypdfform-2.4.0/PyPDFForm/middleware/image.py +0 -34
- pypdfform-2.4.0/PyPDFForm/middleware/radio.py +0 -73
- pypdfform-2.4.0/PyPDFForm/middleware/signature.py +0 -88
- pypdfform-2.4.0/PyPDFForm/middleware/text.py +0 -112
- pypdfform-2.4.0/PyPDFForm/patterns.py +0 -310
- pypdfform-2.4.0/PyPDFForm/template.py +0 -614
- pypdfform-2.4.0/PyPDFForm/utils.py +0 -308
- pypdfform-2.4.0/PyPDFForm/watermark.py +0 -402
- pypdfform-2.4.0/PyPDFForm/widgets/base.py +0 -170
- pypdfform-2.4.0/PyPDFForm/widgets/checkbox.py +0 -39
- pypdfform-2.4.0/PyPDFForm/widgets/dropdown.py +0 -56
- pypdfform-2.4.0/PyPDFForm/widgets/image.py +0 -24
- pypdfform-2.4.0/PyPDFForm/widgets/radio.py +0 -78
- pypdfform-2.4.0/PyPDFForm/widgets/signature.py +0 -131
- pypdfform-2.4.0/PyPDFForm/widgets/text.py +0 -41
- pypdfform-2.4.0/PyPDFForm/wrapper.py +0 -756
- pypdfform-2.4.0/tests/test_adobe_mode.py +0 -158
- pypdfform-2.4.0/tests/test_create_widget.py +0 -680
- pypdfform-2.4.0/tests/test_dropdown.py +0 -181
- pypdfform-2.4.0/tests/test_dropdown_simple.py +0 -176
- pypdfform-2.4.0/tests/test_fill_max_length_text_field.py +0 -228
- pypdfform-2.4.0/tests/test_fill_max_length_text_field_simple.py +0 -208
- pypdfform-2.4.0/tests/test_functional_simple.py +0 -177
- pypdfform-2.4.0/tests/test_paragraph_simple.py +0 -246
- pypdfform-2.4.0/tests/test_preview.py +0 -86
- {pypdfform-2.4.0 → pypdfform-3.0.0}/LICENSE +0 -0
- {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm/middleware/__init__.py +0 -0
- {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm/widgets/__init__.py +0 -0
- {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm/widgets/bedrock.py +0 -0
- {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm.egg-info/requires.txt +0 -0
- {pypdfform-2.4.0 → pypdfform-3.0.0}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-2.4.0 → pypdfform-3.0.0}/setup.cfg +0 -0
- {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:
|
|
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/
|
|
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
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
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
|
-
|
|
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
|
+
)
|