python-pdffiller 1.1.2__py3-none-any.whl → 2.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.
- pdffiller/_version.py +1 -1
- pdffiller/cli/commands/dump_data_fields.py +2 -1
- pdffiller/pdf.py +97 -227
- pdffiller/typing.py +1 -2
- {python_pdffiller-1.1.2.dist-info → python_pdffiller-2.0.0.dist-info}/METADATA +2 -2
- {python_pdffiller-1.1.2.dist-info → python_pdffiller-2.0.0.dist-info}/RECORD +11 -12
- {python_pdffiller-1.1.2.dist-info → python_pdffiller-2.0.0.dist-info}/WHEEL +1 -1
- pdffiller/io/custom_pdf_writer.py +0 -143
- {python_pdffiller-1.1.2.dist-info → python_pdffiller-2.0.0.dist-info}/entry_points.txt +0 -0
- {python_pdffiller-1.1.2.dist-info → python_pdffiller-2.0.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {python_pdffiller-1.1.2.dist-info → python_pdffiller-2.0.0.dist-info}/licenses/COPYING +0 -0
- {python_pdffiller-1.1.2.dist-info → python_pdffiller-2.0.0.dist-info}/top_level.txt +0 -0
pdffiller/_version.py
CHANGED
|
@@ -30,7 +30,8 @@ def dump_fields_text_formatter(pdf: Pdf) -> None:
|
|
|
30
30
|
|
|
31
31
|
def dump_fields_json_formatter(pdf: Pdf) -> None:
|
|
32
32
|
"""Print output text for dump_fields command as simple text"""
|
|
33
|
-
|
|
33
|
+
if not pdf:
|
|
34
|
+
return
|
|
34
35
|
cli_out_write(json.dumps(pdf.schema, indent=4, ensure_ascii=False))
|
|
35
36
|
|
|
36
37
|
|
pdffiller/pdf.py
CHANGED
|
@@ -12,25 +12,12 @@ methods for interacting with its form fields and content.
|
|
|
12
12
|
|
|
13
13
|
from collections import OrderedDict
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
from pypdf.errors import PyPdfError
|
|
17
|
-
from pypdf.generic import ArrayObject, DictionaryObject, NameObject
|
|
15
|
+
import pymupdf
|
|
18
16
|
|
|
19
|
-
from pdffiller.
|
|
17
|
+
from pdffiller.exceptions import PdfFillerException
|
|
20
18
|
from pdffiller.io.output import PdfFillerOutput
|
|
21
19
|
|
|
22
|
-
from .typing import
|
|
23
|
-
Any,
|
|
24
|
-
cast,
|
|
25
|
-
Dict,
|
|
26
|
-
List,
|
|
27
|
-
Optional,
|
|
28
|
-
PathLike,
|
|
29
|
-
StrByteType,
|
|
30
|
-
Tuple,
|
|
31
|
-
Type,
|
|
32
|
-
Union,
|
|
33
|
-
)
|
|
20
|
+
from .typing import Any, cast, Dict, List, Optional, PathLike, StreamType, Type
|
|
34
21
|
from .widgets.base import Widget
|
|
35
22
|
from .widgets.checkbox import CheckBoxWidget
|
|
36
23
|
from .widgets.radio import RadioWidget
|
|
@@ -40,27 +27,6 @@ from .widgets.text import TextWidget
|
|
|
40
27
|
class PdfAttributes: # pylint: disable=too-few-public-methods
|
|
41
28
|
"""Various constants, enums, and flags to aid readability."""
|
|
42
29
|
|
|
43
|
-
Widget = "/Widget"
|
|
44
|
-
Subtype = "/Subtype"
|
|
45
|
-
Parent = "/Parent"
|
|
46
|
-
T = "/T"
|
|
47
|
-
V = "/V"
|
|
48
|
-
AS = "/AS"
|
|
49
|
-
Kids = "/Kids"
|
|
50
|
-
AP = "/AP"
|
|
51
|
-
N = "/N"
|
|
52
|
-
D = "/D"
|
|
53
|
-
Opt = "/Opt"
|
|
54
|
-
MaxLen = "/MaxLen"
|
|
55
|
-
AcroForm = "/AcroForm"
|
|
56
|
-
Root = "/Root"
|
|
57
|
-
XFA = "/XFA"
|
|
58
|
-
FT = "/FT"
|
|
59
|
-
Ff = "/Ff"
|
|
60
|
-
Tx = "/Tx"
|
|
61
|
-
Ch = "/Ch"
|
|
62
|
-
Btn = "/Btn"
|
|
63
|
-
Off = "/Off"
|
|
64
30
|
READ_ONLY = 1 << 0
|
|
65
31
|
|
|
66
32
|
|
|
@@ -75,15 +41,13 @@ class Pdf:
|
|
|
75
41
|
"""
|
|
76
42
|
|
|
77
43
|
TYPE_TO_OBJECT: Dict[str, Type[Widget]] = {
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
44
|
+
"Text": TextWidget,
|
|
45
|
+
"RadioButton": RadioWidget,
|
|
46
|
+
"CheckBox": CheckBoxWidget,
|
|
81
47
|
}
|
|
82
48
|
|
|
83
49
|
def __init__(
|
|
84
|
-
self,
|
|
85
|
-
content: Optional[StrByteType] = None,
|
|
86
|
-
adode_mode: Optional[bool] = True,
|
|
50
|
+
self, filename: Optional[PathLike] = None, stream: Optional[StreamType] = None
|
|
87
51
|
) -> None:
|
|
88
52
|
"""
|
|
89
53
|
Constructor method for the `Pdf` class.
|
|
@@ -91,108 +55,58 @@ class Pdf:
|
|
|
91
55
|
Initializes a new `Pdf` object with the given template PDF and optional keyword arguments.
|
|
92
56
|
|
|
93
57
|
Args:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
- BinaryIO: An open file-like object containing the PDF data.
|
|
97
|
-
adobe_mode (bool): Whether to enable Adobe-specific compatibility mode.
|
|
58
|
+
filename (Optional[PathLike]): Path to the input pdf
|
|
59
|
+
stream (Optional[StreamType]): An open file-like object containing the PDF data.
|
|
98
60
|
"""
|
|
99
61
|
|
|
100
62
|
super().__init__()
|
|
101
63
|
self.widgets: OrderedDict[str, Widget] = OrderedDict()
|
|
102
|
-
self.
|
|
103
|
-
|
|
104
|
-
self._init_helper(content)
|
|
64
|
+
self._init_helper(filename, stream)
|
|
105
65
|
|
|
106
|
-
def _init_helper(
|
|
66
|
+
def _init_helper(
|
|
67
|
+
self, filename: Optional[PathLike] = None, stream: Optional[StreamType] = None
|
|
68
|
+
) -> None:
|
|
107
69
|
"""
|
|
108
70
|
Helper method to initialize widgets
|
|
109
71
|
|
|
110
|
-
This method is called during initialization and after certain operations
|
|
111
|
-
that modify the PDF content.
|
|
112
|
-
It rebuilds the widget dictionary.
|
|
113
|
-
|
|
114
72
|
Args:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
- BinaryIO: An open file-like object containing the PDF data.
|
|
73
|
+
filename (Optional[PathLike]): Path to the input pdf
|
|
74
|
+
stream (Optional[StreamType]): An open file-like object containing the PDF data.
|
|
118
75
|
"""
|
|
119
|
-
if not
|
|
76
|
+
if not filename and not stream:
|
|
120
77
|
return
|
|
121
78
|
|
|
122
79
|
output = PdfFillerOutput()
|
|
123
|
-
output.
|
|
80
|
+
output.info("loading file in memory")
|
|
124
81
|
loaded_widgets: OrderedDict[str, Widget] = OrderedDict()
|
|
125
82
|
try:
|
|
126
|
-
|
|
127
|
-
except
|
|
83
|
+
doc = pymupdf.open(filename=filename, stream=stream)
|
|
84
|
+
except Exception as ex: # pylint: disable=broad-exception-caught
|
|
128
85
|
PdfFillerOutput().error(str(ex))
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
choices
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
widget = widget[PdfAttributes.Parent]
|
|
146
|
-
key = self._get_widget_name(widget)
|
|
147
|
-
if not key:
|
|
148
|
-
continue
|
|
149
|
-
widget_type: Optional[str] = self._get_field_type(widget)
|
|
150
|
-
if not widget_type:
|
|
151
|
-
continue
|
|
152
|
-
|
|
153
|
-
value = widget[PdfAttributes.V] if PdfAttributes.V in widget else None
|
|
154
|
-
if widget_type == "radio":
|
|
155
|
-
if value:
|
|
156
|
-
value = value[1:]
|
|
157
|
-
choices = []
|
|
158
|
-
if PdfAttributes.Kids in widget:
|
|
159
|
-
for each in widget[PdfAttributes.Kids]:
|
|
160
|
-
for each in each[PdfAttributes.AP][PdfAttributes.N].keys():
|
|
161
|
-
if each[1:] not in choices:
|
|
162
|
-
choices.append(each[1:])
|
|
163
|
-
|
|
164
|
-
elif widget_type == "checkbox":
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
PdfAttributes.AP in widget
|
|
168
|
-
and PdfAttributes.N in widget[PdfAttributes.AP]
|
|
169
|
-
and PdfAttributes.D in widget[PdfAttributes.AP]
|
|
170
|
-
and PdfAttributes.AS in widget
|
|
171
|
-
):
|
|
172
|
-
choices = [
|
|
173
|
-
each[1:] for each in (widget[PdfAttributes.AP][PdfAttributes.N]).keys()
|
|
174
|
-
]
|
|
175
|
-
if "Off" not in choices:
|
|
176
|
-
choices.insert(0, "Off")
|
|
177
|
-
elif widget_type in ["list", "combo"] and value:
|
|
178
|
-
choices = [each[1:] for each in widget[PdfAttributes.Opt]]
|
|
179
|
-
if key not in loaded_widgets:
|
|
180
|
-
new_widget = self.TYPE_TO_OBJECT[widget_type](
|
|
181
|
-
key, i, value, self.is_readonly(widget_type, widget.get_object())
|
|
86
|
+
raise PdfFillerException(
|
|
87
|
+
f"failed to load {filename or 'file from input string'}"
|
|
88
|
+
) from ex
|
|
89
|
+
|
|
90
|
+
for i, page in enumerate(doc.pages()):
|
|
91
|
+
output.verbose(f"loading page {i+1}/{doc.page_count}")
|
|
92
|
+
for widget in page.widgets():
|
|
93
|
+
button_states = widget.button_states()
|
|
94
|
+
choices = button_states["normal"] if button_states else None
|
|
95
|
+
|
|
96
|
+
if widget.field_name not in loaded_widgets:
|
|
97
|
+
if widget.field_type_string not in self.TYPE_TO_OBJECT:
|
|
98
|
+
output.verbose(f"unsupported {widget.field_type_string} widget type")
|
|
99
|
+
continue
|
|
100
|
+
new_widget = self.TYPE_TO_OBJECT[widget.field_type_string](
|
|
101
|
+
widget.field_name, i, widget.field_value, widget.field_flags & (1 << 0)
|
|
182
102
|
)
|
|
183
103
|
if choices and isinstance(new_widget, CheckBoxWidget):
|
|
184
104
|
new_widget.choices = choices
|
|
185
105
|
elif isinstance(new_widget, TextWidget):
|
|
186
|
-
max_length =
|
|
187
|
-
|
|
188
|
-
if PdfAttributes.MaxLen in widget
|
|
189
|
-
else None
|
|
190
|
-
)
|
|
191
|
-
if max_length:
|
|
192
|
-
new_widget.max_length = max_length
|
|
193
|
-
loaded_widgets[key] = new_widget
|
|
106
|
+
new_widget.max_length = widget.text_maxlen
|
|
107
|
+
loaded_widgets[widget.field_name] = new_widget
|
|
194
108
|
else:
|
|
195
|
-
new_widget = loaded_widgets[
|
|
109
|
+
new_widget = loaded_widgets[widget.field_name]
|
|
196
110
|
if choices and isinstance(new_widget, CheckBoxWidget):
|
|
197
111
|
for each in choices:
|
|
198
112
|
if new_widget.choices is not None:
|
|
@@ -201,7 +115,9 @@ class Pdf:
|
|
|
201
115
|
else:
|
|
202
116
|
new_widget.choices = [each]
|
|
203
117
|
|
|
204
|
-
cast(CheckBoxWidget, loaded_widgets[
|
|
118
|
+
cast(CheckBoxWidget, loaded_widgets[widget.field_name]).choices = (
|
|
119
|
+
new_widget.choices
|
|
120
|
+
)
|
|
205
121
|
|
|
206
122
|
self.widgets = loaded_widgets
|
|
207
123
|
|
|
@@ -222,7 +138,7 @@ class Pdf:
|
|
|
222
138
|
|
|
223
139
|
def fill(
|
|
224
140
|
self,
|
|
225
|
-
input_file:
|
|
141
|
+
input_file: PathLike,
|
|
226
142
|
output_file: PathLike,
|
|
227
143
|
data: Dict[str, str],
|
|
228
144
|
flatten: bool = True,
|
|
@@ -231,9 +147,7 @@ class Pdf:
|
|
|
231
147
|
Fill the PDF form with data from a dictionary.
|
|
232
148
|
|
|
233
149
|
Args:
|
|
234
|
-
input_file (
|
|
235
|
-
- str: The file path to the PDF.
|
|
236
|
-
- BinaryIO: An open file-like object containing the PDF data.
|
|
150
|
+
input_file (PathLike): The input file path.
|
|
237
151
|
output_file (PathLike): The output file path.
|
|
238
152
|
data (Dict[str, Union[str, bool, int]]): A dictionary where keys are form field names
|
|
239
153
|
and values are the data to fill the fields with. Values can be strings, booleans,
|
|
@@ -244,108 +158,64 @@ class Pdf:
|
|
|
244
158
|
Returns:
|
|
245
159
|
Pdf: The `Pdf` object, allowing for method chaining.
|
|
246
160
|
"""
|
|
247
|
-
|
|
161
|
+
try:
|
|
162
|
+
document = pymupdf.open(filename=input_file)
|
|
163
|
+
except Exception as ex:
|
|
164
|
+
PdfFillerOutput().error(str(ex))
|
|
165
|
+
raise PdfFillerException(f"failed to open {input_file}") from ex
|
|
166
|
+
|
|
248
167
|
output = PdfFillerOutput()
|
|
249
168
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
fields[name] = value
|
|
257
|
-
if isinstance(widget, CheckBoxWidget):
|
|
258
|
-
if value and value[0] != "/":
|
|
259
|
-
output.info(f"override {name} value with /{value}")
|
|
260
|
-
fields[name] = f"/{value}"
|
|
261
|
-
|
|
262
|
-
output.info("fill pdf with input values")
|
|
263
|
-
writer = CustomPdfWriter(reader)
|
|
264
|
-
writer.update_field_values(None, fields, auto_regenerate=False, flatten=flatten)
|
|
265
|
-
if flatten:
|
|
266
|
-
output.info("remove all annotations")
|
|
267
|
-
writer.remove_annotations(None)
|
|
268
|
-
output.info("compress file")
|
|
269
|
-
writer.compress_identical_objects(remove_identicals=False, remove_orphans=True)
|
|
270
|
-
|
|
271
|
-
output.info(f"write {output_file} on the disk")
|
|
272
|
-
with open(output_file, "wb") as f:
|
|
273
|
-
writer.write(f)
|
|
274
|
-
|
|
275
|
-
output.info("try to remove identical objects")
|
|
276
|
-
try:
|
|
277
|
-
writer.compress_identical_objects(remove_identicals=True, remove_orphans=False)
|
|
278
|
-
output.info(f"write {output_file} on the disk")
|
|
279
|
-
with open(output_file, "wb") as f:
|
|
280
|
-
writer.write(f)
|
|
281
|
-
except Exception: # pylint: disable=broad-exception-caught
|
|
282
|
-
output.warning("An error occurs when removing identical objects")
|
|
283
|
-
return self
|
|
169
|
+
output.info("filling pdf with input values")
|
|
170
|
+
# Iterate over all pages and process fields
|
|
171
|
+
for page in document:
|
|
172
|
+
for field in page.widgets():
|
|
173
|
+
if field.field_name in data:
|
|
174
|
+
value = data[field.field_name]
|
|
284
175
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
176
|
+
# Handling checkboxes
|
|
177
|
+
if (
|
|
178
|
+
field.field_type
|
|
179
|
+
== pymupdf.PDF_WIDGET_TYPE_CHECKBOX # pylint: disable=no-member
|
|
180
|
+
):
|
|
181
|
+
print(f"{field.field_name} => {field.on_state()} vs {value}")
|
|
182
|
+
if value.strip() and "Off" != value.strip():
|
|
183
|
+
output.verbose(
|
|
184
|
+
f"updating checkbox with {value} from {field.field_value}"
|
|
185
|
+
)
|
|
186
|
+
field.field_value = True
|
|
187
|
+
else:
|
|
188
|
+
field.field_value = False
|
|
189
|
+
|
|
190
|
+
# Handling radio buttons
|
|
191
|
+
elif (
|
|
192
|
+
field.field_type
|
|
193
|
+
== pymupdf.PDF_WIDGET_TYPE_RADIOBUTTON # pylint: disable=no-member
|
|
194
|
+
):
|
|
195
|
+
if value == field.on_state():
|
|
196
|
+
output.verbose(
|
|
197
|
+
f"updating radiobutton with {value} from {field.field_value}"
|
|
198
|
+
)
|
|
199
|
+
field.field_value = value
|
|
200
|
+
|
|
201
|
+
# Handling other fields types
|
|
202
|
+
else:
|
|
203
|
+
output.verbose(
|
|
204
|
+
f"updating {field.field_name} with {value} from {field.field_value}"
|
|
205
|
+
)
|
|
206
|
+
field.field_value = value
|
|
295
207
|
|
|
296
|
-
|
|
208
|
+
# Update the widget!
|
|
209
|
+
field.update()
|
|
297
210
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
ft = annotation[PdfAttributes.FT] if PdfAttributes.FT in annotation else None
|
|
303
|
-
ff = annotation[PdfAttributes.Ff] if PdfAttributes.Ff in annotation else None
|
|
304
|
-
|
|
305
|
-
if ft == PdfAttributes.Tx:
|
|
306
|
-
return "text"
|
|
307
|
-
if ft == PdfAttributes.Ch:
|
|
308
|
-
if ff and int(ff) & 1 << 17: # test 18th bit
|
|
309
|
-
return "combo"
|
|
310
|
-
return "list"
|
|
311
|
-
if ft == PdfAttributes.Btn:
|
|
312
|
-
if ff and int(ff) & 1 << 15: # test 16th bit
|
|
313
|
-
return "radio"
|
|
314
|
-
return "checkbox"
|
|
315
|
-
|
|
316
|
-
return None
|
|
317
|
-
|
|
318
|
-
@staticmethod
|
|
319
|
-
def is_readonly(widget_type: str, annot: DictionaryObject) -> bool:
|
|
320
|
-
"""
|
|
321
|
-
Determines whether readonly flag is set or not.
|
|
211
|
+
try:
|
|
212
|
+
if flatten:
|
|
213
|
+
output.info("remove all annotations")
|
|
214
|
+
document.bake(annots=False)
|
|
322
215
|
|
|
323
|
-
|
|
324
|
-
|
|
216
|
+
# Save the modified PDF
|
|
217
|
+
document.save(output_file)
|
|
218
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
219
|
+
output.warning("an error occurs when saving file")
|
|
325
220
|
|
|
326
|
-
|
|
327
|
-
widget_type (str): The widget type
|
|
328
|
-
annot (DictionaryObject): The annotation dictionary.
|
|
329
|
-
Returns:
|
|
330
|
-
True if read-only is set, else False
|
|
331
|
-
"""
|
|
332
|
-
if widget_type == "radio":
|
|
333
|
-
if PdfAttributes.Parent in annot:
|
|
334
|
-
return (
|
|
335
|
-
int(
|
|
336
|
-
cast(Any, annot[NameObject(PdfAttributes.Parent)]).get(
|
|
337
|
-
NameObject(PdfAttributes.Ff), 0
|
|
338
|
-
)
|
|
339
|
-
)
|
|
340
|
-
& PdfAttributes.READ_ONLY
|
|
341
|
-
== PdfAttributes.READ_ONLY
|
|
342
|
-
)
|
|
343
|
-
return (
|
|
344
|
-
int(annot.get(NameObject(PdfAttributes.Ff), 0)) & PdfAttributes.READ_ONLY
|
|
345
|
-
== PdfAttributes.READ_ONLY
|
|
346
|
-
)
|
|
347
|
-
|
|
348
|
-
return (
|
|
349
|
-
int(annot.get(NameObject(PdfAttributes.Ff), 0)) & PdfAttributes.READ_ONLY
|
|
350
|
-
== PdfAttributes.READ_ONLY
|
|
351
|
-
)
|
|
221
|
+
return self
|
pdffiller/typing.py
CHANGED
|
@@ -43,7 +43,7 @@ __all__ = [
|
|
|
43
43
|
"Type",
|
|
44
44
|
"Union",
|
|
45
45
|
"SubParserType",
|
|
46
|
-
"
|
|
46
|
+
"StreamType",
|
|
47
47
|
"TextIO",
|
|
48
48
|
]
|
|
49
49
|
|
|
@@ -52,7 +52,6 @@ ExitCode = Union[str, int, None]
|
|
|
52
52
|
PathLike = Union[str, Path]
|
|
53
53
|
|
|
54
54
|
StreamType = IO[Any]
|
|
55
|
-
StrByteType = Union[PathLike, StreamType]
|
|
56
55
|
|
|
57
56
|
if TYPE_CHECKING:
|
|
58
57
|
# pylint: disable=protected-access,line-too-long
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-pdffiller
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Interact with PDF by inspecting or filling it
|
|
5
5
|
Author-email: Jacques Raphanel <jraphanel@sismic.fr>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,7 +26,7 @@ Requires-Python: >=3.9
|
|
|
26
26
|
Description-Content-Type: text/x-rst
|
|
27
27
|
License-File: COPYING
|
|
28
28
|
License-File: AUTHORS.rst
|
|
29
|
-
Requires-Dist:
|
|
29
|
+
Requires-Dist: pymupdf==1.24.10
|
|
30
30
|
Requires-Dist: colorama
|
|
31
31
|
Requires-Dist: pyyaml
|
|
32
32
|
Dynamic: license-file
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
pdffiller/__init__.py,sha256=0HtgXhEV1fKTKAcOXGcq4UsCFflDIPCDQvckshZf-1k,195
|
|
2
2
|
pdffiller/__main__.py,sha256=7NPQgZVx6VSZS7OrmyJQ_O1vL4wiSqhiILi-outwUqM,107
|
|
3
|
-
pdffiller/_version.py,sha256=
|
|
3
|
+
pdffiller/_version.py,sha256=52zTPeF0KaGbSTkNknOnH2BFjTPDTqn6KWd0vLwHy7o,172
|
|
4
4
|
pdffiller/const.py,sha256=if_j5I8ftczpjrzZjA7idv-XpvIj1-XBF4oe6VtQvF0,434
|
|
5
5
|
pdffiller/exceptions.py,sha256=CdN93bZ0mBBS5vLxg14FYZUy4xkYqoD3_SzqtSkZr4g,1624
|
|
6
|
-
pdffiller/pdf.py,sha256=
|
|
6
|
+
pdffiller/pdf.py,sha256=zy-mIC5GQBSK2iKOceRiioMQGTTrp9leFpLSaJBxV50,8417
|
|
7
7
|
pdffiller/py.typed.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
pdffiller/typing.py,sha256=
|
|
8
|
+
pdffiller/typing.py,sha256=4GJGevlU-YOR9fmuPT8jmyg5MBhn-2TB1K88b6C8VRw,937
|
|
9
9
|
pdffiller/utils.py,sha256=pmGf3QwkhKwosk_eFCauzHM-XHp_WGVQAtZlxa7taYY,827
|
|
10
10
|
pdffiller/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
pdffiller/cli/args.py,sha256=1MUtxIVgdBWJCjgp2gxUuGW-vH4qne92A7Rns2DylsQ,870
|
|
@@ -17,21 +17,20 @@ pdffiller/cli/formatters.py,sha256=k0Y0FaRuHFlW8pxePxGsYA7UEmG-gLkb_H3rFbyTuV4,4
|
|
|
17
17
|
pdffiller/cli/once_argument.py,sha256=olQvNhObObS0iXVL1YCa-_lH76SV0I4FvnlgR9roqo4,687
|
|
18
18
|
pdffiller/cli/smart_formatter.py,sha256=59hwF07nKbp-P9IfbqKgMFsfbvjIw5SACCZpUF4nnuE,318
|
|
19
19
|
pdffiller/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
pdffiller/cli/commands/dump_data_fields.py,sha256=
|
|
20
|
+
pdffiller/cli/commands/dump_data_fields.py,sha256=o7LmFBdl9GfJOyZM1tOgeesx004QWIIXVp2_pX6FB1Q,2292
|
|
21
21
|
pdffiller/cli/commands/fill_form.py,sha256=5wjbjwYLsytY6ea-n_KDbU9UDmkN00NYj_gm25MQqJ8,4758
|
|
22
22
|
pdffiller/io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
pdffiller/io/colors.py,sha256=QCBEWksTVNurOJQYO0zh1X_xxIOXxXmYSJhbCqnNjI8,1710
|
|
24
|
-
pdffiller/io/custom_pdf_writer.py,sha256=5lKz59fobhQZ74tmV-4hosVi6Kd6PFTIKNptFO9-p4g,7077
|
|
25
24
|
pdffiller/io/output.py,sha256=QMASWRWmfZGG9DdtlfpWXM3VJAMgWGQwPUzoYp_9FFY,11298
|
|
26
25
|
pdffiller/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
26
|
pdffiller/widgets/base.py,sha256=omGVQsQgMa-ALESnUd3_94oVIYScAMl0SPhHC_DG8Lg,3613
|
|
28
27
|
pdffiller/widgets/checkbox.py,sha256=iijStLAsY1G4cljW3a9NxVS_8qxJewFEw-B8jU2aKXk,1711
|
|
29
28
|
pdffiller/widgets/radio.py,sha256=Db9Oc3Q8ge8qqTVPLoz3I1_SJBGyJ8KfA33ixZMr78c,1070
|
|
30
29
|
pdffiller/widgets/text.py,sha256=SiuyBvZPZ6idCmtZ_05zE26iN6Rz67OfOj1fUm98YQI,2397
|
|
31
|
-
python_pdffiller-
|
|
32
|
-
python_pdffiller-
|
|
33
|
-
python_pdffiller-
|
|
34
|
-
python_pdffiller-
|
|
35
|
-
python_pdffiller-
|
|
36
|
-
python_pdffiller-
|
|
37
|
-
python_pdffiller-
|
|
30
|
+
python_pdffiller-2.0.0.dist-info/licenses/AUTHORS.rst,sha256=1_hVzMKgmoXvGgrcZC7sIbU_6PvvkB6vwqevAqzrIkQ,205
|
|
31
|
+
python_pdffiller-2.0.0.dist-info/licenses/COPYING,sha256=ADPe-bH2wYq8nFf6EPJyovzTJyl3jSPnm09mGI8FSTo,1074
|
|
32
|
+
python_pdffiller-2.0.0.dist-info/METADATA,sha256=0QGzU481QrZElmH6WwsUetDF_rep_1SWSXXH_zCyeCk,4175
|
|
33
|
+
python_pdffiller-2.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
34
|
+
python_pdffiller-2.0.0.dist-info/entry_points.txt,sha256=RESKKpPPdWl0wDET96ntuFoUydALx9j0mxtbt-MEBjU,49
|
|
35
|
+
python_pdffiller-2.0.0.dist-info/top_level.txt,sha256=5MGWCBFYlu_Ax-I5PgQkV9Xw7O48maPe9z8Qj_yVPL4,10
|
|
36
|
+
python_pdffiller-2.0.0.dist-info/RECORD,,
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
from pypdf import PageObject, PdfWriter
|
|
2
|
-
from pypdf.constants import AnnotationDictionaryAttributes as AA
|
|
3
|
-
from pypdf.constants import CatalogDictionary
|
|
4
|
-
from pypdf.constants import FieldDictionaryAttributes as FA
|
|
5
|
-
from pypdf.constants import InteractiveFormDictEntries
|
|
6
|
-
from pypdf.constants import PageAttributes as PG
|
|
7
|
-
from pypdf.errors import PyPdfError
|
|
8
|
-
from pypdf.generic import (
|
|
9
|
-
ArrayObject,
|
|
10
|
-
DictionaryObject,
|
|
11
|
-
NameObject,
|
|
12
|
-
NumberObject,
|
|
13
|
-
RectangleObject,
|
|
14
|
-
TextStringObject,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
from ..typing import cast, Dict, List, Optional, Tuple, Union
|
|
18
|
-
from .output import PdfFillerOutput
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class CustomPdfWriter(PdfWriter): # pylint: disable=abstract-method
|
|
22
|
-
"""Overwrite PdfWriter to by-pass the related to flatten mode."""
|
|
23
|
-
|
|
24
|
-
def update_field_values(
|
|
25
|
-
self,
|
|
26
|
-
page: Union[PageObject, List[PageObject], None],
|
|
27
|
-
fields: Dict[str, Union[str, List[str], Tuple[str, str, float]]],
|
|
28
|
-
flags: FA.FfBits = PdfWriter.FFBITS_NUL,
|
|
29
|
-
auto_regenerate: Optional[bool] = True,
|
|
30
|
-
flatten: bool = False,
|
|
31
|
-
) -> None:
|
|
32
|
-
"""
|
|
33
|
-
Update the form field values for a given page from a fields dictionary.
|
|
34
|
-
|
|
35
|
-
Copy field texts and values from fields to page.
|
|
36
|
-
If the field links to a parent object, add the information to the parent.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
page: `PageObject` - references **PDF writer's page** where the
|
|
40
|
-
annotations and field data will be updated.
|
|
41
|
-
`List[Pageobject]` - provides list of pages to be processed.
|
|
42
|
-
`None` - all pages.
|
|
43
|
-
fields: a Python dictionary of:
|
|
44
|
-
|
|
45
|
-
* field names (/T) as keys and text values (/V) as value
|
|
46
|
-
* field names (/T) as keys and list of text values (/V) for multiple choice list
|
|
47
|
-
* field names (/T) as keys and tuple of:
|
|
48
|
-
* text values (/V)
|
|
49
|
-
* font id (e.g. /F1, the font id must exist)
|
|
50
|
-
* font size (0 for autosize)
|
|
51
|
-
|
|
52
|
-
flags: A set of flags from :class:`~pypdf.constants.FieldDictionaryAttributes.FfBits`.
|
|
53
|
-
|
|
54
|
-
auto_regenerate: Set/unset the need_appearances flag;
|
|
55
|
-
the flag is unchanged if auto_regenerate is None.
|
|
56
|
-
|
|
57
|
-
flatten: Whether or not to flatten the annotation. If True, this adds the annotation's
|
|
58
|
-
appearance stream to the page contents. Note that this option does not remove the
|
|
59
|
-
annotation itself.
|
|
60
|
-
|
|
61
|
-
"""
|
|
62
|
-
if CatalogDictionary.ACRO_FORM not in self._root_object:
|
|
63
|
-
raise PyPdfError("No /AcroForm dictionary in PDF of PdfWriter Object")
|
|
64
|
-
af = cast(DictionaryObject, self._root_object[CatalogDictionary.ACRO_FORM])
|
|
65
|
-
if InteractiveFormDictEntries.Fields not in af:
|
|
66
|
-
raise PyPdfError("No /Fields dictionary in PDF of PdfWriter Object")
|
|
67
|
-
if isinstance(auto_regenerate, bool):
|
|
68
|
-
self.set_need_appearances_writer(auto_regenerate)
|
|
69
|
-
# Iterate through pages, update field values
|
|
70
|
-
if page is None:
|
|
71
|
-
page = list(self.pages)
|
|
72
|
-
if isinstance(page, list):
|
|
73
|
-
for p in page:
|
|
74
|
-
if PG.ANNOTS in p: # just to prevent warnings
|
|
75
|
-
self.update_field_values(p, fields, flags, None, flatten=flatten)
|
|
76
|
-
return
|
|
77
|
-
output = PdfFillerOutput()
|
|
78
|
-
if PG.ANNOTS not in page:
|
|
79
|
-
output.warning(f"No fields to update on this page {__name__}")
|
|
80
|
-
return
|
|
81
|
-
for annotation in page[PG.ANNOTS]: # type: ignore
|
|
82
|
-
annotation = cast(DictionaryObject, annotation.get_object())
|
|
83
|
-
if annotation.get("/Subtype", "") != "/Widget":
|
|
84
|
-
continue
|
|
85
|
-
if "/FT" in annotation and "/T" in annotation:
|
|
86
|
-
parent_annotation = annotation
|
|
87
|
-
else:
|
|
88
|
-
parent_annotation = annotation.get(PG.PARENT, DictionaryObject()).get_object()
|
|
89
|
-
|
|
90
|
-
for field, value in fields.items():
|
|
91
|
-
if not (
|
|
92
|
-
self._get_qualified_field_name(parent_annotation) == field
|
|
93
|
-
or parent_annotation.get("/T", None) == field
|
|
94
|
-
):
|
|
95
|
-
continue
|
|
96
|
-
if parent_annotation.get("/FT", None) == "/Ch" and "/I" in parent_annotation:
|
|
97
|
-
del parent_annotation["/I"]
|
|
98
|
-
if flags:
|
|
99
|
-
annotation[NameObject(FA.Ff)] = NumberObject(flags)
|
|
100
|
-
if not (
|
|
101
|
-
value is None and flatten
|
|
102
|
-
): # Only change values if given by user and not flattening.
|
|
103
|
-
if isinstance(value, list):
|
|
104
|
-
lst = ArrayObject(TextStringObject(v) for v in value)
|
|
105
|
-
parent_annotation[NameObject(FA.V)] = lst
|
|
106
|
-
elif isinstance(value, tuple):
|
|
107
|
-
annotation[NameObject(FA.V)] = TextStringObject(
|
|
108
|
-
value[0],
|
|
109
|
-
)
|
|
110
|
-
else:
|
|
111
|
-
parent_annotation[NameObject(FA.V)] = TextStringObject(value)
|
|
112
|
-
if parent_annotation.get(FA.FT) == "/Btn":
|
|
113
|
-
# Checkbox button (no /FT found in Radio widgets)
|
|
114
|
-
v = NameObject(value)
|
|
115
|
-
ap = cast(DictionaryObject, annotation[NameObject(AA.AP)])
|
|
116
|
-
normal_ap = cast(DictionaryObject, ap["/N"])
|
|
117
|
-
exist = True
|
|
118
|
-
if v not in normal_ap:
|
|
119
|
-
v = NameObject("/Off")
|
|
120
|
-
exist = False
|
|
121
|
-
appearance_stream_obj = normal_ap.get(v)
|
|
122
|
-
# other cases will be updated through the for loop
|
|
123
|
-
annotation[NameObject(AA.AS)] = v
|
|
124
|
-
annotation[NameObject(FA.V)] = v
|
|
125
|
-
if flatten and appearance_stream_obj is not None and exist:
|
|
126
|
-
# We basically copy the entire appearance stream, which should be
|
|
127
|
-
# an XObject that is already registered. No need to add font resources.
|
|
128
|
-
rct = cast(RectangleObject, annotation[AA.Rect])
|
|
129
|
-
self._add_apstream_object(
|
|
130
|
-
page, appearance_stream_obj, field, rct[0], rct[1]
|
|
131
|
-
)
|
|
132
|
-
elif parent_annotation.get(FA.FT) == "/Tx" or parent_annotation.get(FA.FT) == "/Ch":
|
|
133
|
-
# textbox
|
|
134
|
-
if isinstance(value, tuple):
|
|
135
|
-
self._update_field_annotation( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
136
|
-
page, parent_annotation, annotation, value[1], value[2], flatten=flatten
|
|
137
|
-
)
|
|
138
|
-
else:
|
|
139
|
-
self._update_field_annotation( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
140
|
-
page, parent_annotation, annotation, flatten=flatten
|
|
141
|
-
)
|
|
142
|
-
elif annotation.get(FA.FT) == "/Sig": # deprecated # not implemented yet
|
|
143
|
-
output.warning(f"Signature forms not implemented yet {__name__}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|