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