PyPDFForm 2.4.0__py3-none-any.whl → 3.0.0__py3-none-any.whl
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/__init__.py +22 -6
- PyPDFForm/adapter.py +28 -26
- PyPDFForm/constants.py +29 -34
- PyPDFForm/coordinate.py +23 -399
- PyPDFForm/filler.py +79 -303
- PyPDFForm/font.py +166 -164
- PyPDFForm/hooks.py +256 -0
- PyPDFForm/image.py +72 -22
- PyPDFForm/middleware/base.py +54 -48
- PyPDFForm/middleware/checkbox.py +29 -56
- PyPDFForm/middleware/dropdown.py +41 -30
- PyPDFForm/middleware/image.py +10 -22
- PyPDFForm/middleware/radio.py +30 -31
- PyPDFForm/middleware/signature.py +32 -47
- PyPDFForm/middleware/text.py +59 -48
- PyPDFForm/patterns.py +61 -141
- PyPDFForm/template.py +80 -427
- PyPDFForm/utils.py +142 -128
- PyPDFForm/watermark.py +77 -208
- PyPDFForm/widgets/base.py +57 -76
- PyPDFForm/widgets/checkbox.py +18 -21
- PyPDFForm/widgets/dropdown.py +18 -25
- PyPDFForm/widgets/image.py +11 -9
- PyPDFForm/widgets/radio.py +25 -35
- PyPDFForm/widgets/signature.py +29 -40
- PyPDFForm/widgets/text.py +18 -17
- PyPDFForm/wrapper.py +373 -437
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/METADATA +6 -7
- pypdfform-3.0.0.dist-info/RECORD +35 -0
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/WHEEL +1 -1
- pypdfform-2.4.0.dist-info/RECORD +0 -34
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {pypdfform-2.4.0.dist-info → pypdfform-3.0.0.dist-info}/top_level.txt +0 -0
PyPDFForm/wrapper.py
CHANGED
|
@@ -1,42 +1,43 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
The
|
|
14
|
-
for
|
|
2
|
+
"""
|
|
3
|
+
A module for wrapping PDF form operations, providing a high-level interface
|
|
4
|
+
for filling, creating, and manipulating PDF forms.
|
|
5
|
+
|
|
6
|
+
This module simplifies common tasks such as:
|
|
7
|
+
- Filling PDF forms with data from a dictionary.
|
|
8
|
+
- Creating new form fields (widgets) on a PDF.
|
|
9
|
+
- Drawing text and images onto a PDF.
|
|
10
|
+
- Registering custom fonts for use in form fields.
|
|
11
|
+
- Merging multiple PDF forms.
|
|
12
|
+
|
|
13
|
+
The core class, `PdfWrapper`, encapsulates a PDF document and provides
|
|
14
|
+
methods for interacting with its form fields and content. It leverages
|
|
15
|
+
lower-level modules within the `PyPDFForm` library to handle the
|
|
16
|
+
underlying PDF manipulation.
|
|
15
17
|
"""
|
|
16
18
|
|
|
17
19
|
from __future__ import annotations
|
|
18
20
|
|
|
19
21
|
from functools import cached_property
|
|
20
|
-
from typing import BinaryIO, Dict, List, Tuple, Union
|
|
22
|
+
from typing import BinaryIO, Dict, List, Sequence, Tuple, Union
|
|
21
23
|
|
|
22
24
|
from .adapter import fp_or_f_obj_or_stream_to_stream
|
|
23
25
|
from .constants import (DEFAULT_FONT, DEFAULT_FONT_COLOR, DEFAULT_FONT_SIZE,
|
|
24
|
-
|
|
25
|
-
VERSION_IDENTIFIERS)
|
|
26
|
+
VERSION_IDENTIFIER_PREFIX, VERSION_IDENTIFIERS)
|
|
26
27
|
from .coordinate import generate_coordinate_grid
|
|
27
|
-
from .filler import fill
|
|
28
|
-
from .font import register_font
|
|
28
|
+
from .filler import fill
|
|
29
|
+
from .font import (get_all_available_fonts, register_font,
|
|
30
|
+
register_font_acroform)
|
|
31
|
+
from .hooks import trigger_widget_hooks
|
|
29
32
|
from .image import rotate_image
|
|
30
33
|
from .middleware.dropdown import Dropdown
|
|
34
|
+
from .middleware.signature import Signature
|
|
31
35
|
from .middleware.text import Text
|
|
32
|
-
from .template import
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
from .utils import (generate_unique_suffix, get_page_streams, merge_two_pdfs,
|
|
36
|
-
preview_widget_to_draw, remove_all_widgets)
|
|
36
|
+
from .template import build_widgets, update_widget_keys
|
|
37
|
+
from .utils import (enable_adobe_mode, generate_unique_suffix,
|
|
38
|
+
get_page_streams, merge_two_pdfs, remove_all_widgets)
|
|
37
39
|
from .watermark import (copy_watermark_widgets, create_watermarks_and_draw,
|
|
38
40
|
merge_watermarks_with_pdf)
|
|
39
|
-
from .widgets.base import handle_non_acro_form_params
|
|
40
41
|
from .widgets.checkbox import CheckBoxWidget
|
|
41
42
|
from .widgets.dropdown import DropdownWidget
|
|
42
43
|
from .widgets.image import ImageWidget
|
|
@@ -45,383 +46,339 @@ from .widgets.signature import SignatureWidget
|
|
|
45
46
|
from .widgets.text import TextWidget
|
|
46
47
|
|
|
47
48
|
|
|
48
|
-
class
|
|
49
|
-
"""
|
|
49
|
+
class PdfWrapper:
|
|
50
|
+
"""
|
|
51
|
+
A class to wrap PDF form operations, providing a simplified interface
|
|
52
|
+
for common tasks such as filling, creating, and manipulating PDF forms.
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
+
The `PdfWrapper` class encapsulates a PDF document and provides methods
|
|
55
|
+
for interacting with its form fields (widgets) and content. It leverages
|
|
56
|
+
lower-level modules within the `PyPDFForm` library to handle the
|
|
57
|
+
underlying PDF manipulation.
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
Attributes:
|
|
60
|
+
USER_PARAMS (list): A list of user-configurable parameters and their default values.
|
|
61
|
+
These parameters can be set during initialization using keyword arguments.
|
|
62
|
+
Current parameters include:
|
|
63
|
+
- `use_full_widget_name` (bool): Whether to use the full widget name when filling the form.
|
|
64
|
+
- `adobe_mode` (bool): Whether to enable Adobe-specific compatibility mode.
|
|
57
65
|
|
|
58
|
-
The FormWrapper is designed to be extended by PdfWrapper which adds
|
|
59
|
-
more advanced features like form analysis and widget creation.
|
|
60
66
|
"""
|
|
61
67
|
|
|
68
|
+
USER_PARAMS = [
|
|
69
|
+
("use_full_widget_name", False),
|
|
70
|
+
("adobe_mode", False),
|
|
71
|
+
]
|
|
72
|
+
|
|
62
73
|
def __init__(
|
|
63
74
|
self,
|
|
64
75
|
template: Union[bytes, str, BinaryIO] = b"",
|
|
65
76
|
**kwargs,
|
|
66
77
|
) -> None:
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
Args:
|
|
70
|
-
template: PDF form as bytes, file path, or file object. Defaults to
|
|
71
|
-
empty bytes if not provided.
|
|
72
|
-
**kwargs: Additional options:
|
|
73
|
-
use_full_widget_name: If True, uses complete widget names including
|
|
74
|
-
field hierarchy (default: False)
|
|
78
|
+
"""
|
|
79
|
+
Constructor method for the `PdfWrapper` class.
|
|
75
80
|
|
|
76
|
-
Initializes
|
|
77
|
-
- Internal PDF stream from the template
|
|
78
|
-
- Basic form filling capabilities
|
|
79
|
-
- Widget naming configuration from kwargs
|
|
81
|
+
Initializes a new `PdfWrapper` object with the given template PDF and optional keyword arguments.
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
Args:
|
|
84
|
+
template (Union[bytes, str, BinaryIO]): The template PDF, provided as either:
|
|
85
|
+
- bytes: The raw PDF data as a byte string.
|
|
86
|
+
- str: The file path to the PDF.
|
|
87
|
+
- BinaryIO: An open file-like object containing the PDF data.
|
|
88
|
+
Defaults to an empty byte string (b""), which creates a blank PDF.
|
|
89
|
+
**kwargs: Additional keyword arguments to configure the `PdfWrapper`.
|
|
90
|
+
These arguments are used to set the user-configurable parameters defined in `USER_PARAMS`.
|
|
91
|
+
For example: `use_full_widget_name=True` or `adobe_mode=False`.
|
|
84
92
|
"""
|
|
85
93
|
|
|
86
94
|
super().__init__()
|
|
87
|
-
self.
|
|
88
|
-
self.
|
|
95
|
+
self._stream = fp_or_f_obj_or_stream_to_stream(template)
|
|
96
|
+
self.widgets = {}
|
|
97
|
+
self._available_fonts = {} # for setting /F1
|
|
98
|
+
self._font_register_events = [] # for reregister
|
|
99
|
+
self._key_update_tracker = {} # for update key preserve old key attrs
|
|
100
|
+
self._keys_to_update = [] # for bulk update keys
|
|
89
101
|
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
# sets attrs from kwargs
|
|
103
|
+
for attr, default in self.USER_PARAMS:
|
|
104
|
+
setattr(self, attr, kwargs.get(attr, default))
|
|
92
105
|
|
|
93
|
-
|
|
94
|
-
like fill() have been performed. No parsing or analysis of the PDF
|
|
95
|
-
content is done - the bytes are returned as-is.
|
|
106
|
+
self._init_helper()
|
|
96
107
|
|
|
97
|
-
|
|
98
|
-
bytes: The complete PDF document as a byte string
|
|
108
|
+
def __add__(self, other: PdfWrapper) -> PdfWrapper:
|
|
99
109
|
"""
|
|
110
|
+
Merges two PDF wrappers together, creating a new `PdfWrapper` containing the combined content.
|
|
100
111
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def fill(
|
|
104
|
-
self,
|
|
105
|
-
data: Dict[str, Union[str, bool, int]],
|
|
106
|
-
**kwargs,
|
|
107
|
-
) -> FormWrapper:
|
|
108
|
-
"""Fills form fields in the PDF with provided values.
|
|
109
|
-
|
|
110
|
-
Takes a dictionary of field names to values and updates the corresponding
|
|
111
|
-
form fields in the PDF. Supports these value types:
|
|
112
|
-
- Strings for text fields
|
|
113
|
-
- Booleans for checkboxes (True=checked, False=unchecked)
|
|
114
|
-
- Integers for numeric fields and dropdown selections
|
|
115
|
-
|
|
116
|
-
Only fields that exist in the template PDF will be filled - unknown field
|
|
117
|
-
names are silently ignored.
|
|
112
|
+
This method allows you to combine two PDF forms into a single form. It handles potential
|
|
113
|
+
naming conflicts between form fields by adding a unique suffix to the field names in the second form.
|
|
118
114
|
|
|
119
115
|
Args:
|
|
120
|
-
|
|
121
|
-
str: For text fields
|
|
122
|
-
bool: For checkboxes (True=checked)
|
|
123
|
-
int: For numeric fields and dropdown selections
|
|
124
|
-
**kwargs: Additional options:
|
|
125
|
-
flatten (bool): If True, makes form fields read-only after filling
|
|
126
|
-
(default: False)
|
|
127
|
-
adobe_mode (bool): If True, uses Adobe-compatible filling logic
|
|
128
|
-
(default: False)
|
|
116
|
+
other (PdfWrapper): The other `PdfWrapper` object to merge with.
|
|
129
117
|
|
|
130
118
|
Returns:
|
|
131
|
-
|
|
119
|
+
PdfWrapper: A new `PdfWrapper` object containing the merged PDFs.
|
|
132
120
|
"""
|
|
133
121
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if self.stream
|
|
137
|
-
else {}
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
for key, value in data.items():
|
|
141
|
-
if key in widgets:
|
|
142
|
-
widgets[key].value = value
|
|
143
|
-
|
|
144
|
-
self.stream = simple_fill(
|
|
145
|
-
self.read(),
|
|
146
|
-
widgets,
|
|
147
|
-
use_full_widget_name=self.use_full_widget_name,
|
|
148
|
-
flatten=kwargs.get("flatten", False),
|
|
149
|
-
adobe_mode=kwargs.get("adobe_mode", False),
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
return self
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
class PdfWrapper(FormWrapper):
|
|
156
|
-
"""Extended PDF form wrapper with advanced features.
|
|
157
|
-
|
|
158
|
-
Inherits from FormWrapper and adds capabilities for:
|
|
159
|
-
- Creating and modifying form widgets
|
|
160
|
-
- Drawing text and images
|
|
161
|
-
- Merging PDF documents
|
|
162
|
-
- Generating coordinate grids
|
|
163
|
-
- Form schema generation
|
|
164
|
-
- Font registration
|
|
165
|
-
|
|
166
|
-
Key Features:
|
|
167
|
-
- Maintains widget state and properties
|
|
168
|
-
- Supports per-page operations
|
|
169
|
-
- Handles PDF version management
|
|
170
|
-
- Provides preview functionality
|
|
171
|
-
"""
|
|
172
|
-
|
|
173
|
-
USER_PARAMS = [
|
|
174
|
-
("global_font", None),
|
|
175
|
-
("global_font_size", None),
|
|
176
|
-
("global_font_color", None),
|
|
177
|
-
("use_full_widget_name", False),
|
|
178
|
-
("render_widgets", True),
|
|
179
|
-
]
|
|
122
|
+
if not self.read():
|
|
123
|
+
return other
|
|
180
124
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
template: Union[bytes, str, BinaryIO] = b"",
|
|
184
|
-
**kwargs,
|
|
185
|
-
) -> None:
|
|
186
|
-
"""Initializes the PDF wrapper with template and configuration.
|
|
125
|
+
if not other.read():
|
|
126
|
+
return self
|
|
187
127
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
global_font: Default font name for text fields
|
|
193
|
-
global_font_size: Default font size
|
|
194
|
-
global_font_color: Default font color as RGB tuple
|
|
195
|
-
use_full_widget_name: Whether to use full widget names
|
|
196
|
-
render_widgets: Whether to render widgets in the PDF
|
|
197
|
-
|
|
198
|
-
Initializes:
|
|
199
|
-
- Widgets dictionary to track form fields
|
|
200
|
-
- Keys update queue for deferred operations
|
|
201
|
-
- Any specified global settings from kwargs
|
|
202
|
-
"""
|
|
203
|
-
|
|
204
|
-
super().__init__(template)
|
|
205
|
-
self.widgets = {}
|
|
206
|
-
self._keys_to_update = []
|
|
128
|
+
unique_suffix = generate_unique_suffix()
|
|
129
|
+
for k in self.widgets:
|
|
130
|
+
if k in other.widgets:
|
|
131
|
+
other.update_widget_key(k, f"{k}-{unique_suffix}", defer=True)
|
|
207
132
|
|
|
208
|
-
|
|
209
|
-
setattr(self, attr, kwargs.get(attr, default))
|
|
133
|
+
other.commit_widget_key_updates()
|
|
210
134
|
|
|
211
|
-
|
|
135
|
+
# user params are based on the first object
|
|
136
|
+
result = self.__class__(
|
|
137
|
+
merge_two_pdfs(self.read(), other.read()),
|
|
138
|
+
**{each[0]: getattr(self, each[0], each[1]) for each in self.USER_PARAMS},
|
|
139
|
+
)
|
|
212
140
|
|
|
213
|
-
|
|
214
|
-
|
|
141
|
+
# inherit fonts
|
|
142
|
+
for event in self._font_register_events:
|
|
143
|
+
result.register_font(event[0], event[1])
|
|
215
144
|
|
|
216
|
-
|
|
217
|
-
- Rebuild the widgets dictionary
|
|
218
|
-
- Preserve existing widget properties
|
|
219
|
-
- Apply global font settings to text widgets
|
|
220
|
-
- Handle special refresh cases for specific widgets
|
|
145
|
+
return result
|
|
221
146
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
If None, all text widgets will have their fonts updated.
|
|
147
|
+
def _init_helper(self) -> None:
|
|
148
|
+
"""
|
|
149
|
+
Helper method to initialize widgets and available fonts.
|
|
226
150
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
151
|
+
This method is called during initialization and after certain operations
|
|
152
|
+
that modify the PDF content (e.g., filling, creating widgets, updating keys).
|
|
153
|
+
It rebuilds the widget dictionary and updates the available fonts.
|
|
230
154
|
"""
|
|
231
155
|
|
|
232
|
-
refresh_not_needed = {}
|
|
233
156
|
new_widgets = (
|
|
234
157
|
build_widgets(
|
|
235
158
|
self.read(),
|
|
236
159
|
getattr(self, "use_full_widget_name"),
|
|
237
|
-
getattr(self, "render_widgets"),
|
|
238
160
|
)
|
|
239
161
|
if self.read()
|
|
240
162
|
else {}
|
|
241
163
|
)
|
|
164
|
+
# ensure old widgets don't get overwritten
|
|
242
165
|
for k, v in self.widgets.items():
|
|
243
166
|
if k in new_widgets:
|
|
244
167
|
new_widgets[k] = v
|
|
245
|
-
|
|
168
|
+
|
|
169
|
+
# update key preserve old key attrs
|
|
170
|
+
for k, v in new_widgets.items():
|
|
171
|
+
if k in self._key_update_tracker:
|
|
172
|
+
for name, value in self.widgets[
|
|
173
|
+
self._key_update_tracker[k]
|
|
174
|
+
].__dict__.items():
|
|
175
|
+
if not name.startswith("_"):
|
|
176
|
+
setattr(v, name, value)
|
|
177
|
+
self._key_update_tracker = {}
|
|
178
|
+
|
|
246
179
|
self.widgets = new_widgets
|
|
247
180
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
181
|
+
if self.read():
|
|
182
|
+
self._available_fonts.update(**get_all_available_fonts(self.read()))
|
|
183
|
+
|
|
184
|
+
def _reregister_font(self) -> PdfWrapper:
|
|
185
|
+
"""
|
|
186
|
+
Reregisters fonts after PDF content modifications.
|
|
187
|
+
|
|
188
|
+
This method is called after operations that modify the PDF content
|
|
189
|
+
(e.g., drawing text, drawing images) to ensure that custom fonts
|
|
190
|
+
are correctly registered and available for use.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
font_register_events_len = len(self._font_register_events)
|
|
194
|
+
for i in range(font_register_events_len):
|
|
195
|
+
event = self._font_register_events[i]
|
|
196
|
+
self.register_font(event[0], event[1], False)
|
|
197
|
+
self._font_register_events = self._font_register_events[
|
|
198
|
+
font_register_events_len:
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
return self
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def schema(self) -> dict:
|
|
205
|
+
"""
|
|
206
|
+
Returns the JSON schema of the PDF form, describing the structure and data types of the form fields.
|
|
207
|
+
|
|
208
|
+
This schema can be used to generate user interfaces or validate data before filling the form.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
dict: A dictionary representing the JSON schema of the PDF form.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
"type": "object",
|
|
216
|
+
"properties": {
|
|
217
|
+
key: value.schema_definition for key, value in self.widgets.items()
|
|
218
|
+
},
|
|
219
|
+
}
|
|
257
220
|
|
|
258
221
|
@property
|
|
259
222
|
def sample_data(self) -> dict:
|
|
260
|
-
"""
|
|
223
|
+
"""
|
|
224
|
+
Returns sample data for the PDF form, providing example values for each form field.
|
|
261
225
|
|
|
262
|
-
|
|
263
|
-
sample value based on its type:
|
|
264
|
-
- Text fields: Field name (truncated if max_length specified)
|
|
265
|
-
- Checkboxes: True
|
|
266
|
-
- Dropdowns: Index of last available choice
|
|
267
|
-
- Other fields: Type-specific sample values
|
|
226
|
+
This sample data can be used for testing or demonstration purposes.
|
|
268
227
|
|
|
269
228
|
Returns:
|
|
270
|
-
dict:
|
|
229
|
+
dict: A dictionary containing sample data for the PDF form.
|
|
271
230
|
"""
|
|
272
231
|
|
|
273
232
|
return {key: value.sample_value for key, value in self.widgets.items()}
|
|
274
233
|
|
|
275
234
|
@property
|
|
276
235
|
def version(self) -> Union[str, None]:
|
|
277
|
-
"""
|
|
278
|
-
|
|
279
|
-
The version is extracted from the PDF header which contains a version
|
|
280
|
-
identifier like '%PDF-1.4'. This method returns just the version number
|
|
281
|
-
portion (e.g. '1.4') if found, or None if no valid version identifier
|
|
282
|
-
is present.
|
|
236
|
+
"""
|
|
237
|
+
Returns the PDF version of the underlying PDF document.
|
|
283
238
|
|
|
284
239
|
Returns:
|
|
285
|
-
str: The PDF version
|
|
286
|
-
None: If no valid version identifier exists in the PDF
|
|
240
|
+
Union[str, None]: The PDF version as a string, or None if the version cannot be determined.
|
|
287
241
|
"""
|
|
288
242
|
|
|
289
243
|
for each in VERSION_IDENTIFIERS:
|
|
290
|
-
if self.
|
|
244
|
+
if self.read().startswith(each):
|
|
291
245
|
return each.replace(VERSION_IDENTIFIER_PREFIX, b"").decode()
|
|
292
246
|
|
|
293
247
|
return None
|
|
294
248
|
|
|
295
|
-
@
|
|
296
|
-
def
|
|
297
|
-
"""
|
|
249
|
+
@property
|
|
250
|
+
def fonts(self) -> list:
|
|
251
|
+
"""
|
|
252
|
+
Returns a list of the names of the currently registered fonts.
|
|
298
253
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
254
|
+
Returns:
|
|
255
|
+
list: A list of font names (str).
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
return list(self._available_fonts.keys())
|
|
259
|
+
|
|
260
|
+
@cached_property
|
|
261
|
+
def pages(self) -> Sequence[PdfWrapper]:
|
|
262
|
+
"""
|
|
263
|
+
Returns a sequence of `PdfWrapper` objects, each representing a single page in the PDF document.
|
|
302
264
|
|
|
303
|
-
|
|
304
|
-
repeated calls.
|
|
265
|
+
This allows you to work with individual pages of the PDF, for example, to extract text or images from a specific page.
|
|
305
266
|
|
|
306
267
|
Returns:
|
|
307
|
-
|
|
268
|
+
Sequence[PdfWrapper]: A sequence of `PdfWrapper` objects, one for each page in the PDF.
|
|
308
269
|
"""
|
|
309
270
|
|
|
310
|
-
|
|
271
|
+
result = [
|
|
311
272
|
self.__class__(
|
|
312
|
-
copy_watermark_widgets(each, self.
|
|
273
|
+
copy_watermark_widgets(each, self.read(), None, i),
|
|
313
274
|
**{param: getattr(self, param) for param, _ in self.USER_PARAMS},
|
|
314
275
|
)
|
|
315
276
|
for i, each in enumerate(get_page_streams(remove_all_widgets(self.read())))
|
|
316
277
|
]
|
|
317
278
|
|
|
318
|
-
|
|
319
|
-
|
|
279
|
+
# because copy_watermark_widgets and remove_all_widgets
|
|
280
|
+
if self._font_register_events:
|
|
281
|
+
for event in self._font_register_events:
|
|
282
|
+
for page in result:
|
|
283
|
+
page.register_font(event[0], event[1])
|
|
320
284
|
|
|
321
|
-
|
|
322
|
-
Note this only changes the version identifier, not the actual PDF features used.
|
|
285
|
+
return result
|
|
323
286
|
|
|
324
|
-
|
|
325
|
-
|
|
287
|
+
def read(self) -> bytes:
|
|
288
|
+
"""
|
|
289
|
+
Reads the PDF content from the underlying stream.
|
|
290
|
+
|
|
291
|
+
This method returns the current state of the PDF as a byte string.
|
|
292
|
+
It also triggers any pending widget hooks and applies Adobe mode if enabled.
|
|
326
293
|
|
|
327
294
|
Returns:
|
|
328
|
-
|
|
295
|
+
bytes: The PDF content as bytes.
|
|
329
296
|
"""
|
|
330
297
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
298
|
+
if any(widget.hooks_to_trigger for widget in self.widgets.values()):
|
|
299
|
+
for widget in self.widgets.values():
|
|
300
|
+
if (
|
|
301
|
+
isinstance(widget, (Text, Dropdown))
|
|
302
|
+
and widget.font not in self._available_fonts.values()
|
|
303
|
+
and widget.font in self._available_fonts
|
|
304
|
+
):
|
|
305
|
+
widget.font = self._available_fonts.get(
|
|
306
|
+
widget.font
|
|
307
|
+
) # from `new_font` to `/F1`
|
|
308
|
+
|
|
309
|
+
self._stream = trigger_widget_hooks(
|
|
310
|
+
self._stream,
|
|
311
|
+
self.widgets,
|
|
312
|
+
getattr(self, "use_full_widget_name"),
|
|
313
|
+
)
|
|
336
314
|
|
|
337
|
-
|
|
315
|
+
if getattr(self, "adobe_mode") and self._stream:
|
|
316
|
+
self._stream = enable_adobe_mode(self._stream) # cached
|
|
338
317
|
|
|
339
|
-
|
|
340
|
-
"""Merges two PDF forms together using the + operator.
|
|
318
|
+
return self._stream
|
|
341
319
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
- Maintaining all page content and ordering
|
|
320
|
+
def write(self, path: str) -> PdfWrapper:
|
|
321
|
+
"""
|
|
322
|
+
Writes the PDF content to a file.
|
|
346
323
|
|
|
347
324
|
Args:
|
|
348
|
-
|
|
325
|
+
path (str): The file path to write the PDF to.
|
|
349
326
|
|
|
350
327
|
Returns:
|
|
351
|
-
PdfWrapper:
|
|
328
|
+
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
352
329
|
"""
|
|
353
330
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if not other.stream:
|
|
358
|
-
return self
|
|
359
|
-
|
|
360
|
-
unique_suffix = generate_unique_suffix()
|
|
361
|
-
for k in self.widgets:
|
|
362
|
-
if k in other.widgets:
|
|
363
|
-
other.update_widget_key(k, f"{k}-{unique_suffix}", defer=True)
|
|
364
|
-
|
|
365
|
-
other.commit_widget_key_updates()
|
|
331
|
+
with open(path, "wb+") as f:
|
|
332
|
+
f.write(self.read())
|
|
366
333
|
|
|
367
|
-
return self
|
|
334
|
+
return self
|
|
368
335
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
336
|
+
def change_version(self, version: str) -> PdfWrapper:
|
|
337
|
+
"""
|
|
338
|
+
Changes the PDF version of the underlying document.
|
|
372
339
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
- Widget names are drawn slightly above their original positions
|
|
376
|
-
- Helps visualize form field locations without interactive widgets
|
|
340
|
+
Args:
|
|
341
|
+
version (str): The new PDF version string (e.g., "1.7").
|
|
377
342
|
|
|
378
343
|
Returns:
|
|
379
|
-
|
|
344
|
+
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
380
345
|
"""
|
|
381
346
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
key: preview_widget_to_draw(key, value, True)
|
|
387
|
-
for key, value in self.widgets.items()
|
|
388
|
-
},
|
|
389
|
-
getattr(self, "use_full_widget_name"),
|
|
390
|
-
)
|
|
347
|
+
self._stream = self.read().replace(
|
|
348
|
+
VERSION_IDENTIFIER_PREFIX + bytes(self.version, "utf-8"),
|
|
349
|
+
VERSION_IDENTIFIER_PREFIX + bytes(version, "utf-8"),
|
|
350
|
+
1,
|
|
391
351
|
)
|
|
392
352
|
|
|
353
|
+
return self
|
|
354
|
+
|
|
393
355
|
def generate_coordinate_grid(
|
|
394
356
|
self, color: Tuple[float, float, float] = (1, 0, 0), margin: float = 100
|
|
395
357
|
) -> PdfWrapper:
|
|
396
|
-
"""
|
|
397
|
-
|
|
398
|
-
Creates a visual grid showing x,y coordinates to help with:
|
|
399
|
-
- Precise widget placement
|
|
400
|
-
- Measuring distances between elements
|
|
401
|
-
- Debugging layout issues
|
|
358
|
+
"""
|
|
359
|
+
Generates a coordinate grid on the PDF, useful for debugging layout issues.
|
|
402
360
|
|
|
403
361
|
Args:
|
|
404
|
-
color:
|
|
405
|
-
margin:
|
|
362
|
+
color (Tuple[float, float, float]): The color of the grid lines, specified as an RGB tuple (default: red).
|
|
363
|
+
margin (float): The margin around the grid, in points (default: 100).
|
|
406
364
|
|
|
407
365
|
Returns:
|
|
408
|
-
PdfWrapper:
|
|
409
|
-
"""
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
for key, value in self.widgets.items()
|
|
418
|
-
},
|
|
419
|
-
getattr(self, "use_full_widget_name"),
|
|
420
|
-
)
|
|
366
|
+
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
stream_with_widgets = self.read()
|
|
370
|
+
self._stream = copy_watermark_widgets(
|
|
371
|
+
generate_coordinate_grid(
|
|
372
|
+
remove_all_widgets(self.read()),
|
|
373
|
+
color,
|
|
374
|
+
margin,
|
|
421
375
|
),
|
|
422
|
-
|
|
423
|
-
|
|
376
|
+
stream_with_widgets,
|
|
377
|
+
None,
|
|
378
|
+
None,
|
|
424
379
|
)
|
|
380
|
+
# because copy_watermark_widgets and remove_all_widgets
|
|
381
|
+
self._reregister_font()
|
|
425
382
|
|
|
426
383
|
return self
|
|
427
384
|
|
|
@@ -430,40 +387,45 @@ class PdfWrapper(FormWrapper):
|
|
|
430
387
|
data: Dict[str, Union[str, bool, int]],
|
|
431
388
|
**kwargs,
|
|
432
389
|
) -> PdfWrapper:
|
|
433
|
-
"""
|
|
434
|
-
|
|
435
|
-
Extends FormWrapper.fill() with additional features:
|
|
436
|
-
- Maintains widget properties like fonts and styles
|
|
437
|
-
- Converts dropdowns to text fields while preserving choices
|
|
438
|
-
- Updates text field attributes and character spacing
|
|
390
|
+
"""
|
|
391
|
+
Fills the PDF form with data from a dictionary.
|
|
439
392
|
|
|
440
393
|
Args:
|
|
441
|
-
data
|
|
442
|
-
|
|
394
|
+
data (Dict[str, Union[str, bool, int]]): A dictionary where keys are form field names
|
|
395
|
+
and values are the data to fill the fields with. Values can be strings, booleans, or integers.
|
|
396
|
+
**kwargs: Additional keyword arguments:
|
|
397
|
+
- `flatten` (bool): Whether to flatten the form after filling, making the fields read-only (default: False).
|
|
443
398
|
|
|
444
399
|
Returns:
|
|
445
|
-
PdfWrapper:
|
|
400
|
+
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
446
401
|
"""
|
|
447
402
|
|
|
448
403
|
for key, value in data.items():
|
|
449
404
|
if key in self.widgets:
|
|
450
405
|
self.widgets[key].value = value
|
|
451
406
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
self.stream, self.widgets, getattr(self, "use_full_widget_name")
|
|
407
|
+
filled_stream, image_drawn_stream = fill(
|
|
408
|
+
self.read(),
|
|
409
|
+
self.widgets,
|
|
410
|
+
use_full_widget_name=getattr(self, "use_full_widget_name"),
|
|
411
|
+
flatten=kwargs.get("flatten", False),
|
|
458
412
|
)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
413
|
+
|
|
414
|
+
if image_drawn_stream is not None:
|
|
415
|
+
keys_to_copy = [
|
|
416
|
+
k for k, v in self.widgets.items() if not isinstance(v, Signature)
|
|
417
|
+
] # only copy non-image fields
|
|
418
|
+
filled_stream = copy_watermark_widgets(
|
|
419
|
+
remove_all_widgets(image_drawn_stream),
|
|
420
|
+
filled_stream,
|
|
421
|
+
keys_to_copy,
|
|
422
|
+
None,
|
|
462
423
|
)
|
|
463
424
|
|
|
464
|
-
self.
|
|
465
|
-
|
|
466
|
-
|
|
425
|
+
self._stream = filled_stream
|
|
426
|
+
if image_drawn_stream is not None:
|
|
427
|
+
# because copy_watermark_widgets and remove_all_widgets
|
|
428
|
+
self._reregister_font()
|
|
467
429
|
|
|
468
430
|
return self
|
|
469
431
|
|
|
@@ -477,35 +439,26 @@ class PdfWrapper(FormWrapper):
|
|
|
477
439
|
**kwargs,
|
|
478
440
|
) -> PdfWrapper:
|
|
479
441
|
"""
|
|
480
|
-
Creates a new
|
|
481
|
-
|
|
482
|
-
Supported widget types:
|
|
483
|
-
- "text": Text input field
|
|
484
|
-
- "checkbox": Checkbox field
|
|
485
|
-
- "dropdown": Dropdown/combobox field
|
|
486
|
-
- "radio": Radio button field
|
|
487
|
-
- "signature": Signature field
|
|
488
|
-
- "image": Image field
|
|
442
|
+
Creates a new form field (widget) on the PDF.
|
|
489
443
|
|
|
490
444
|
Args:
|
|
491
|
-
widget_type (str):
|
|
492
|
-
"text"
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
445
|
+
widget_type (str): The type of widget to create. Valid values are:
|
|
446
|
+
- "text": A text field.
|
|
447
|
+
- "checkbox": A checkbox field.
|
|
448
|
+
- "dropdown": A dropdown field.
|
|
449
|
+
- "radio": A radio button field.
|
|
450
|
+
- "signature": A signature field.
|
|
451
|
+
- "image": An image field.
|
|
452
|
+
name (str): The name of the widget. This name will be used to identify the widget when filling the form.
|
|
453
|
+
page_number (int): The page number to create the widget on (1-based).
|
|
454
|
+
x (Union[float, List[float]]): The x coordinate(s) of the widget.
|
|
455
|
+
If a list is provided, it specifies the x coordinates of multiple instances of the widget.
|
|
456
|
+
y (Union[float, List[float]]): The y coordinate(s) of the widget.
|
|
457
|
+
If a list is provided, it specifies the y coordinates of multiple instances of the widget.
|
|
458
|
+
**kwargs: Additional keyword arguments specific to the widget type.
|
|
502
459
|
|
|
503
460
|
Returns:
|
|
504
|
-
PdfWrapper:
|
|
505
|
-
|
|
506
|
-
Notes:
|
|
507
|
-
- If an unsupported widget_type is provided, the method returns self unchanged.
|
|
508
|
-
- After widget creation, the internal widget state is refreshed.
|
|
461
|
+
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
509
462
|
"""
|
|
510
463
|
|
|
511
464
|
_class = None
|
|
@@ -527,39 +480,34 @@ class PdfWrapper(FormWrapper):
|
|
|
527
480
|
obj = _class(name=name, page_number=page_number, x=x, y=y, **kwargs)
|
|
528
481
|
watermarks = obj.watermarks(self.read())
|
|
529
482
|
|
|
530
|
-
self.
|
|
531
|
-
|
|
532
|
-
self.stream = handle_non_acro_form_params(
|
|
533
|
-
self.stream, name, obj.non_acro_form_params
|
|
534
|
-
)
|
|
535
|
-
|
|
536
|
-
key_to_refresh = ""
|
|
537
|
-
if widget_type in ("text", "dropdown"):
|
|
538
|
-
key_to_refresh = name
|
|
483
|
+
self._stream = copy_watermark_widgets(self.read(), watermarks, [name], None)
|
|
484
|
+
hook_params = obj.hook_params
|
|
539
485
|
|
|
540
|
-
self._init_helper(
|
|
486
|
+
self._init_helper()
|
|
487
|
+
for k, v in hook_params:
|
|
488
|
+
self.widgets[name].__setattr__(k, v)
|
|
541
489
|
|
|
542
490
|
return self
|
|
543
491
|
|
|
544
492
|
def update_widget_key(
|
|
545
493
|
self, old_key: str, new_key: str, index: int = 0, defer: bool = False
|
|
546
494
|
) -> PdfWrapper:
|
|
547
|
-
"""
|
|
495
|
+
"""
|
|
496
|
+
Updates the key (name) of a widget, allowing you to rename form fields.
|
|
548
497
|
|
|
549
|
-
|
|
550
|
-
|
|
498
|
+
This method allows you to change the name of a form field in the PDF. This can be useful for
|
|
499
|
+
standardizing field names or resolving naming conflicts. The update can be performed immediately
|
|
500
|
+
or deferred until `commit_widget_key_updates` is called.
|
|
551
501
|
|
|
552
502
|
Args:
|
|
553
|
-
old_key:
|
|
554
|
-
new_key:
|
|
555
|
-
index:
|
|
556
|
-
defer: If True,
|
|
503
|
+
old_key (str): The old key of the widget that you want to rename.
|
|
504
|
+
new_key (str): The new key to assign to the widget.
|
|
505
|
+
index (int): The index of the widget if there are multiple widgets with the same name (default: 0).
|
|
506
|
+
defer (bool): Whether to defer the update. If True, the update is added to a queue and applied
|
|
507
|
+
when `commit_widget_key_updates` is called. If False, the update is applied immediately (default: False).
|
|
557
508
|
|
|
558
509
|
Returns:
|
|
559
|
-
PdfWrapper:
|
|
560
|
-
|
|
561
|
-
Raises:
|
|
562
|
-
NotImplementedError: When use_full_widget_name is enabled
|
|
510
|
+
PdfWrapper: The PdfWrapper object.
|
|
563
511
|
"""
|
|
564
512
|
|
|
565
513
|
if getattr(self, "use_full_widget_name"):
|
|
@@ -569,7 +517,8 @@ class PdfWrapper(FormWrapper):
|
|
|
569
517
|
self._keys_to_update.append((old_key, new_key, index))
|
|
570
518
|
return self
|
|
571
519
|
|
|
572
|
-
self.
|
|
520
|
+
self._key_update_tracker[new_key] = old_key
|
|
521
|
+
self._stream = update_widget_keys(
|
|
573
522
|
self.read(), self.widgets, [old_key], [new_key], [index]
|
|
574
523
|
)
|
|
575
524
|
self._init_helper()
|
|
@@ -577,17 +526,14 @@ class PdfWrapper(FormWrapper):
|
|
|
577
526
|
return self
|
|
578
527
|
|
|
579
528
|
def commit_widget_key_updates(self) -> PdfWrapper:
|
|
580
|
-
"""
|
|
529
|
+
"""
|
|
530
|
+
Commits deferred widget key updates, applying all queued key renames to the PDF.
|
|
581
531
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
multiple fields.
|
|
532
|
+
This method applies all widget key updates that were deferred using the `defer=True` option
|
|
533
|
+
in the `update_widget_key` method. It updates the underlying PDF stream with the new key names.
|
|
585
534
|
|
|
586
535
|
Returns:
|
|
587
|
-
PdfWrapper:
|
|
588
|
-
|
|
589
|
-
Raises:
|
|
590
|
-
NotImplementedError: When use_full_widget_name is enabled
|
|
536
|
+
PdfWrapper: The PdfWrapper object.
|
|
591
537
|
"""
|
|
592
538
|
|
|
593
539
|
if getattr(self, "use_full_widget_name"):
|
|
@@ -597,9 +543,12 @@ class PdfWrapper(FormWrapper):
|
|
|
597
543
|
new_keys = [each[1] for each in self._keys_to_update]
|
|
598
544
|
indices = [each[2] for each in self._keys_to_update]
|
|
599
545
|
|
|
600
|
-
self.
|
|
546
|
+
self._stream = update_widget_keys(
|
|
601
547
|
self.read(), self.widgets, old_keys, new_keys, indices
|
|
602
548
|
)
|
|
549
|
+
|
|
550
|
+
for each in self._keys_to_update:
|
|
551
|
+
self._key_update_tracker[each[1]] = each[0]
|
|
603
552
|
self._init_helper()
|
|
604
553
|
self._keys_to_update = []
|
|
605
554
|
|
|
@@ -613,26 +562,21 @@ class PdfWrapper(FormWrapper):
|
|
|
613
562
|
y: Union[float, int],
|
|
614
563
|
**kwargs,
|
|
615
564
|
) -> PdfWrapper:
|
|
616
|
-
"""
|
|
617
|
-
|
|
618
|
-
Adds non-interactive text that becomes part of the PDF content rather
|
|
619
|
-
than a form field. The text is drawn using a temporary Text widget and
|
|
620
|
-
merged via watermark operations, preserving existing form fields.
|
|
621
|
-
|
|
622
|
-
Supports multi-line text (using NEW_LINE_SYMBOL) and custom formatting.
|
|
565
|
+
"""
|
|
566
|
+
Draws text on the PDF.
|
|
623
567
|
|
|
624
568
|
Args:
|
|
625
|
-
text: The text
|
|
626
|
-
page_number:
|
|
627
|
-
x:
|
|
628
|
-
y:
|
|
629
|
-
**kwargs:
|
|
630
|
-
font:
|
|
631
|
-
font_size:
|
|
632
|
-
font_color:
|
|
569
|
+
text (str): The text to draw.
|
|
570
|
+
page_number (int): The page number to draw on.
|
|
571
|
+
x (Union[float, int]): The x coordinate of the text.
|
|
572
|
+
y (Union[float, int]): The y coordinate of the text.
|
|
573
|
+
**kwargs: Additional keyword arguments:
|
|
574
|
+
- `font` (str): The name of the font to use (default: DEFAULT_FONT).
|
|
575
|
+
- `font_size` (float): The font size in points (default: DEFAULT_FONT_SIZE).
|
|
576
|
+
- `font_color` (Tuple[float, float, float]): The font color as an RGB tuple (default: DEFAULT_FONT_COLOR).
|
|
633
577
|
|
|
634
578
|
Returns:
|
|
635
|
-
PdfWrapper:
|
|
579
|
+
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
636
580
|
"""
|
|
637
581
|
|
|
638
582
|
new_widget = Text("new")
|
|
@@ -641,11 +585,8 @@ class PdfWrapper(FormWrapper):
|
|
|
641
585
|
new_widget.font_size = kwargs.get("font_size", DEFAULT_FONT_SIZE)
|
|
642
586
|
new_widget.font_color = kwargs.get("font_color", DEFAULT_FONT_COLOR)
|
|
643
587
|
|
|
644
|
-
if NEW_LINE_SYMBOL in text:
|
|
645
|
-
new_widget.text_lines = text.split(NEW_LINE_SYMBOL)
|
|
646
|
-
|
|
647
588
|
watermarks = create_watermarks_and_draw(
|
|
648
|
-
self.
|
|
589
|
+
self.read(),
|
|
649
590
|
page_number,
|
|
650
591
|
"text",
|
|
651
592
|
[
|
|
@@ -658,10 +599,12 @@ class PdfWrapper(FormWrapper):
|
|
|
658
599
|
)
|
|
659
600
|
|
|
660
601
|
stream_with_widgets = self.read()
|
|
661
|
-
self.
|
|
662
|
-
self.
|
|
663
|
-
remove_all_widgets(self.
|
|
602
|
+
self._stream = merge_watermarks_with_pdf(self.read(), watermarks)
|
|
603
|
+
self._stream = copy_watermark_widgets(
|
|
604
|
+
remove_all_widgets(self.read()), stream_with_widgets, None, None
|
|
664
605
|
)
|
|
606
|
+
# because copy_watermark_widgets and remove_all_widgets
|
|
607
|
+
self._reregister_font()
|
|
665
608
|
|
|
666
609
|
return self
|
|
667
610
|
|
|
@@ -675,82 +618,75 @@ class PdfWrapper(FormWrapper):
|
|
|
675
618
|
height: Union[float, int],
|
|
676
619
|
rotation: Union[float, int] = 0,
|
|
677
620
|
) -> PdfWrapper:
|
|
678
|
-
"""
|
|
679
|
-
|
|
680
|
-
The image is merged via watermark operations, preserving existing form fields.
|
|
681
|
-
Supports common formats (JPEG, PNG) from bytes, file paths, or file objects.
|
|
621
|
+
"""
|
|
622
|
+
Draws an image on the PDF.
|
|
682
623
|
|
|
683
624
|
Args:
|
|
684
|
-
image
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
625
|
+
image (Union[bytes, str, BinaryIO]): The image data, provided as either:
|
|
626
|
+
- bytes: The raw image data as a byte string.
|
|
627
|
+
- str: The file path to the image.
|
|
628
|
+
- BinaryIO: An open file-like object containing the image data.
|
|
629
|
+
page_number (int): The page number to draw the image on.
|
|
630
|
+
x (Union[float, int]): The x coordinate of the image.
|
|
631
|
+
y (Union[float, int]): The y coordinate of the image.
|
|
632
|
+
width (Union[float, int]): The width of the image.
|
|
633
|
+
height (Union[float, int]): The height of the image.
|
|
634
|
+
rotation (Union[float, int]): The rotation of the image in degrees (default: 0).
|
|
691
635
|
|
|
692
636
|
Returns:
|
|
693
|
-
PdfWrapper:
|
|
637
|
+
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
694
638
|
"""
|
|
695
639
|
|
|
696
640
|
image = fp_or_f_obj_or_stream_to_stream(image)
|
|
697
641
|
image = rotate_image(image, rotation)
|
|
698
642
|
watermarks = create_watermarks_and_draw(
|
|
699
|
-
self.
|
|
643
|
+
self.read(),
|
|
700
644
|
page_number,
|
|
701
645
|
"image",
|
|
702
646
|
[{"stream": image, "x": x, "y": y, "width": width, "height": height}],
|
|
703
647
|
)
|
|
704
648
|
|
|
705
649
|
stream_with_widgets = self.read()
|
|
706
|
-
self.
|
|
707
|
-
self.
|
|
708
|
-
remove_all_widgets(self.
|
|
650
|
+
self._stream = merge_watermarks_with_pdf(self.read(), watermarks)
|
|
651
|
+
self._stream = copy_watermark_widgets(
|
|
652
|
+
remove_all_widgets(self.read()), stream_with_widgets, None, None
|
|
709
653
|
)
|
|
654
|
+
# because copy_watermark_widgets and remove_all_widgets
|
|
655
|
+
self._reregister_font()
|
|
710
656
|
|
|
711
657
|
return self
|
|
712
658
|
|
|
713
|
-
@property
|
|
714
|
-
def schema(self) -> dict:
|
|
715
|
-
"""Generates a JSON schema describing the PDF form's fields and types.
|
|
716
|
-
|
|
717
|
-
The schema includes:
|
|
718
|
-
- Field names as property names
|
|
719
|
-
- Type information (string, boolean, integer)
|
|
720
|
-
- Field-specific constraints like max lengths for text fields
|
|
721
|
-
- Choice indices for dropdown fields
|
|
722
|
-
|
|
723
|
-
Note: Does not include required field indicators since the PDF form's
|
|
724
|
-
validation rules are not extracted.
|
|
725
|
-
|
|
726
|
-
Returns:
|
|
727
|
-
dict: A JSON Schema dictionary following Draft 7 format
|
|
728
|
-
"""
|
|
729
|
-
|
|
730
|
-
return {
|
|
731
|
-
"type": "object",
|
|
732
|
-
"properties": {
|
|
733
|
-
key: value.schema_definition for key, value in self.widgets.items()
|
|
734
|
-
},
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
@classmethod
|
|
738
659
|
def register_font(
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
660
|
+
self,
|
|
661
|
+
font_name: str,
|
|
662
|
+
ttf_file: Union[bytes, str, BinaryIO],
|
|
663
|
+
first_time: bool = True,
|
|
664
|
+
) -> PdfWrapper:
|
|
665
|
+
"""
|
|
666
|
+
Registers a custom font for use in the PDF.
|
|
745
667
|
|
|
746
668
|
Args:
|
|
747
|
-
font_name:
|
|
748
|
-
ttf_file: The TTF
|
|
669
|
+
font_name (str): The name of the font. This name will be used to reference the font when drawing text.
|
|
670
|
+
ttf_file (Union[bytes, str, BinaryIO]): The TTF file data, provided as either:
|
|
671
|
+
- bytes: The raw TTF file data as a byte string.
|
|
672
|
+
- str: The file path to the TTF file.
|
|
673
|
+
- BinaryIO: An open file-like object containing the TTF file data.
|
|
674
|
+
first_time (bool): Whether this is the first time the font is being registered (default: True).
|
|
675
|
+
If True and `adobe_mode` is enabled, a blank text string is drawn to ensure the font is properly embedded in the PDF.
|
|
749
676
|
|
|
750
677
|
Returns:
|
|
751
|
-
|
|
678
|
+
PdfWrapper: The `PdfWrapper` object, allowing for method chaining.
|
|
752
679
|
"""
|
|
753
680
|
|
|
754
681
|
ttf_file = fp_or_f_obj_or_stream_to_stream(ttf_file)
|
|
755
682
|
|
|
756
|
-
|
|
683
|
+
if register_font(font_name, ttf_file) if ttf_file is not None else False:
|
|
684
|
+
if first_time and getattr(self, "adobe_mode"):
|
|
685
|
+
self.draw_text(" ", 1, 0, 0, font=font_name)
|
|
686
|
+
self._stream, new_font_name = register_font_acroform(
|
|
687
|
+
self.read(), ttf_file, getattr(self, "adobe_mode")
|
|
688
|
+
)
|
|
689
|
+
self._available_fonts[font_name] = new_font_name
|
|
690
|
+
self._font_register_events.append((font_name, ttf_file))
|
|
691
|
+
|
|
692
|
+
return self
|