python-pdffiller 1.0.1__py3-none-any.whl → 1.1.1__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/fill_form.py +6 -3
- pdffiller/io/custom_pdf_writer.py +143 -0
- pdffiller/io/output.py +7 -6
- pdffiller/pdf.py +56 -201
- pdffiller/typing.py +2 -0
- pdffiller/widgets/base.py +28 -1
- pdffiller/widgets/checkbox.py +3 -1
- pdffiller/widgets/text.py +3 -1
- {python_pdffiller-1.0.1.dist-info → python_pdffiller-1.1.1.dist-info}/METADATA +1 -1
- {python_pdffiller-1.0.1.dist-info → python_pdffiller-1.1.1.dist-info}/RECORD +16 -15
- {python_pdffiller-1.0.1.dist-info → python_pdffiller-1.1.1.dist-info}/WHEEL +0 -0
- {python_pdffiller-1.0.1.dist-info → python_pdffiller-1.1.1.dist-info}/entry_points.txt +0 -0
- {python_pdffiller-1.0.1.dist-info → python_pdffiller-1.1.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {python_pdffiller-1.0.1.dist-info → python_pdffiller-1.1.1.dist-info}/licenses/COPYING +0 -0
- {python_pdffiller-1.0.1.dist-info → python_pdffiller-1.1.1.dist-info}/top_level.txt +0 -0
pdffiller/_version.py
CHANGED
|
@@ -17,7 +17,7 @@ from pdffiller.exceptions import (
|
|
|
17
17
|
)
|
|
18
18
|
from pdffiller.io.output import PdfFillerOutput
|
|
19
19
|
from pdffiller.pdf import Pdf
|
|
20
|
-
from pdffiller.typing import Any, Dict
|
|
20
|
+
from pdffiller.typing import Any, Dict
|
|
21
21
|
|
|
22
22
|
from ..exit_codes import ERROR_ENCOUNTERED
|
|
23
23
|
|
|
@@ -94,7 +94,7 @@ def fill_form(parser: PdfFillerArgumentParser, *args: Any) -> Any:
|
|
|
94
94
|
if not opts.data and not opts.input_data:
|
|
95
95
|
raise CommandLineError("no data file path given")
|
|
96
96
|
|
|
97
|
-
input_data: Dict[str,
|
|
97
|
+
input_data: Dict[str, str] = {}
|
|
98
98
|
if opts.input_data:
|
|
99
99
|
try:
|
|
100
100
|
input_data = json.loads(opts.input_data)
|
|
@@ -131,8 +131,11 @@ def fill_form(parser: PdfFillerArgumentParser, *args: Any) -> Any:
|
|
|
131
131
|
input_dict[html.unescape(field["name"])] = html.unescape(field["value"])
|
|
132
132
|
input_data = input_dict
|
|
133
133
|
|
|
134
|
+
output.info(f"input file: {opts.file}")
|
|
135
|
+
output.info("input values are:")
|
|
136
|
+
output.info(json.dumps(input_data, indent=4))
|
|
134
137
|
try:
|
|
135
|
-
pdf = Pdf(
|
|
138
|
+
pdf = Pdf()
|
|
136
139
|
pdf.fill(opts.file, opts.output, input_data, opts.flatten)
|
|
137
140
|
except PdfFillerException as exp:
|
|
138
141
|
output.error(str(exp))
|
|
@@ -0,0 +1,143 @@
|
|
|
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(
|
|
136
|
+
page, parent_annotation, annotation, value[1], value[2], flatten=flatten
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
self._update_field_annotation(
|
|
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__}")
|
pdffiller/io/output.py
CHANGED
|
@@ -7,7 +7,7 @@ from colorama import Fore, Style
|
|
|
7
7
|
from pdffiller import const
|
|
8
8
|
from pdffiller.exceptions import CommandLineError
|
|
9
9
|
from pdffiller.io.colors import color_enabled, is_terminal
|
|
10
|
-
from pdffiller.typing import Dict, Optional, Union
|
|
10
|
+
from pdffiller.typing import Dict, Optional, TextIO, Union
|
|
11
11
|
|
|
12
12
|
LEVEL_QUIET = 80 # -q
|
|
13
13
|
LEVEL_ERROR = 70 # Errors
|
|
@@ -56,14 +56,11 @@ class PdfFillerOutput:
|
|
|
56
56
|
# Singleton
|
|
57
57
|
_output_level: int = LEVEL_STATUS
|
|
58
58
|
_output_file: Optional[str] = None
|
|
59
|
+
_output_stream: Optional[TextIO] = None
|
|
59
60
|
|
|
60
61
|
def __init__(self, scope: str = "") -> None:
|
|
61
|
-
self.stream = sys.stderr
|
|
62
|
+
self.stream = self._output_stream or sys.stderr
|
|
62
63
|
self._scope = scope
|
|
63
|
-
if self._output_file:
|
|
64
|
-
self.stream = open( # pylint: disable=consider-using-with
|
|
65
|
-
self._output_file, "wt", encoding="utf-8"
|
|
66
|
-
)
|
|
67
64
|
self._color: bool = color_enabled(self.stream)
|
|
68
65
|
|
|
69
66
|
@classmethod
|
|
@@ -104,6 +101,10 @@ class PdfFillerOutput:
|
|
|
104
101
|
:param file_path: Optional path to output log file.
|
|
105
102
|
"""
|
|
106
103
|
cls._output_file = file_path
|
|
104
|
+
if cls._output_file:
|
|
105
|
+
cls._output_stream = open( # pylint: disable=consider-using-with
|
|
106
|
+
cls._output_file, "wt", encoding="utf-8"
|
|
107
|
+
)
|
|
107
108
|
|
|
108
109
|
@classmethod
|
|
109
110
|
def level_allowed(cls, level: int) -> bool:
|
pdffiller/pdf.py
CHANGED
|
@@ -12,27 +12,22 @@ methods for interacting with its form fields and content.
|
|
|
12
12
|
|
|
13
13
|
from collections import OrderedDict
|
|
14
14
|
|
|
15
|
-
from pypdf import PdfReader
|
|
15
|
+
from pypdf import PdfReader
|
|
16
16
|
from pypdf.errors import PyPdfError
|
|
17
|
-
from pypdf.generic import
|
|
18
|
-
ArrayObject,
|
|
19
|
-
DictionaryObject,
|
|
20
|
-
NameObject,
|
|
21
|
-
NumberObject,
|
|
22
|
-
TextStringObject,
|
|
23
|
-
)
|
|
17
|
+
from pypdf.generic import ArrayObject, DictionaryObject, NameObject
|
|
24
18
|
|
|
19
|
+
from pdffiller.io.custom_pdf_writer import CustomPdfWriter
|
|
25
20
|
from pdffiller.io.output import PdfFillerOutput
|
|
26
21
|
|
|
27
22
|
from .typing import (
|
|
28
23
|
Any,
|
|
29
|
-
Callable,
|
|
30
24
|
cast,
|
|
31
25
|
Dict,
|
|
32
26
|
List,
|
|
33
27
|
Optional,
|
|
34
28
|
PathLike,
|
|
35
29
|
StrByteType,
|
|
30
|
+
Tuple,
|
|
36
31
|
Type,
|
|
37
32
|
Union,
|
|
38
33
|
)
|
|
@@ -124,6 +119,8 @@ class Pdf:
|
|
|
124
119
|
if not content:
|
|
125
120
|
return
|
|
126
121
|
|
|
122
|
+
output = PdfFillerOutput()
|
|
123
|
+
output.verbose("loading file in memory")
|
|
127
124
|
loaded_widgets: OrderedDict[str, Widget] = OrderedDict()
|
|
128
125
|
try:
|
|
129
126
|
pdf_file = PdfReader(content)
|
|
@@ -132,6 +129,7 @@ class Pdf:
|
|
|
132
129
|
return
|
|
133
130
|
|
|
134
131
|
for i, page in enumerate(pdf_file.pages):
|
|
132
|
+
output.verbose(f"loading page {i+1}/{len(pdf_file.pages)}")
|
|
135
133
|
widgets: Optional[ArrayObject] = page.annotations
|
|
136
134
|
if not widgets:
|
|
137
135
|
continue
|
|
@@ -179,7 +177,9 @@ class Pdf:
|
|
|
179
177
|
elif widget_type in ["list", "combo"] and value:
|
|
180
178
|
choices = [each[1:] for each in widget[PdfAttributes.Opt]]
|
|
181
179
|
if key not in loaded_widgets:
|
|
182
|
-
new_widget = self.TYPE_TO_OBJECT[widget_type](
|
|
180
|
+
new_widget = self.TYPE_TO_OBJECT[widget_type](
|
|
181
|
+
key, i, value, self.is_readonly(widget_type, widget.get_object())
|
|
182
|
+
)
|
|
183
183
|
if choices and isinstance(new_widget, CheckBoxWidget):
|
|
184
184
|
new_widget.choices = choices
|
|
185
185
|
elif isinstance(new_widget, TextWidget):
|
|
@@ -224,7 +224,7 @@ class Pdf:
|
|
|
224
224
|
self,
|
|
225
225
|
input_file: StrByteType,
|
|
226
226
|
output_file: PathLike,
|
|
227
|
-
data: Dict[str,
|
|
227
|
+
data: Dict[str, str],
|
|
228
228
|
flatten: bool = True,
|
|
229
229
|
) -> "Pdf":
|
|
230
230
|
"""
|
|
@@ -244,69 +244,31 @@ class Pdf:
|
|
|
244
244
|
Returns:
|
|
245
245
|
Pdf: The `Pdf` object, allowing for method chaining.
|
|
246
246
|
"""
|
|
247
|
-
for key, value in data.items():
|
|
248
|
-
if key in self.widgets:
|
|
249
|
-
self.widgets[key].value = value
|
|
250
|
-
|
|
251
247
|
reader = PdfReader(input_file)
|
|
252
|
-
if self.adobe_mode:
|
|
253
|
-
if (
|
|
254
|
-
PdfAttributes.AcroForm in cast(Any, reader.trailer[PdfAttributes.Root])
|
|
255
|
-
and PdfAttributes.XFA
|
|
256
|
-
in cast(Any, reader.trailer[PdfAttributes.Root])[PdfAttributes.AcroForm]
|
|
257
|
-
):
|
|
258
|
-
del cast(Any, reader.trailer[PdfAttributes.Root])[PdfAttributes.AcroForm][
|
|
259
|
-
PdfAttributes.XFA
|
|
260
|
-
]
|
|
261
|
-
|
|
262
|
-
writer = PdfWriter(reader)
|
|
263
|
-
if self.adobe_mode:
|
|
264
|
-
writer.set_need_appearances_writer()
|
|
265
|
-
|
|
266
|
-
fillers: Dict[str, Callable[[DictionaryObject, Any], None]] = {
|
|
267
|
-
"checkbox": self._fill_checkbox,
|
|
268
|
-
"text": self._fill_text,
|
|
269
|
-
"radio": self._fill_radio,
|
|
270
|
-
}
|
|
271
|
-
|
|
272
248
|
output = PdfFillerOutput()
|
|
273
|
-
for page in writer.pages:
|
|
274
|
-
widgets: Optional[ArrayObject] = page.annotations
|
|
275
|
-
if not widgets:
|
|
276
|
-
continue
|
|
277
|
-
for widget in widgets:
|
|
278
|
-
if (
|
|
279
|
-
PdfAttributes.Subtype not in widget
|
|
280
|
-
or widget[PdfAttributes.Subtype] != PdfAttributes.Widget
|
|
281
|
-
):
|
|
282
|
-
continue
|
|
283
|
-
|
|
284
|
-
annotation = cast(DictionaryObject, widget.get_object())
|
|
285
|
-
if PdfAttributes.T not in widget:
|
|
286
|
-
widget = widget[PdfAttributes.Parent]
|
|
287
|
-
widget_key: Optional[str] = self._get_widget_name(widget)
|
|
288
|
-
if not widget_key or widget_key not in self.widgets:
|
|
289
|
-
continue
|
|
290
|
-
|
|
291
|
-
widget_type: Optional[str] = self._get_field_type(widget)
|
|
292
|
-
if not widget_type:
|
|
293
|
-
continue
|
|
294
|
-
|
|
295
|
-
if widget_type != "radio":
|
|
296
|
-
self.flatten_generic(annotation, flatten)
|
|
297
|
-
else:
|
|
298
|
-
self.flatten_radio(annotation, flatten)
|
|
299
|
-
|
|
300
|
-
if widget_key not in data or widget_type not in fillers:
|
|
301
|
-
continue
|
|
302
|
-
|
|
303
|
-
current_widget = self.widgets[widget_key]
|
|
304
|
-
if current_widget.value is None:
|
|
305
|
-
continue
|
|
306
|
-
|
|
307
|
-
output.debug(f"Filling {current_widget.name} with {current_widget.value}")
|
|
308
|
-
fillers[widget_type](annotation, current_widget)
|
|
309
249
|
|
|
250
|
+
self._init_helper(input_file)
|
|
251
|
+
fields: Dict[str, Union[str, List[str], Tuple[str, str, float]]] = {}
|
|
252
|
+
|
|
253
|
+
output.verbose("checking value for radio/checkbox ...")
|
|
254
|
+
for name, value in data.items():
|
|
255
|
+
widget = self.widgets.get(name)
|
|
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=True, remove_orphans=True)
|
|
270
|
+
|
|
271
|
+
output.info(f"write {output_file} on the disk")
|
|
310
272
|
with open(output_file, "wb") as f:
|
|
311
273
|
writer.write(f)
|
|
312
274
|
|
|
@@ -346,143 +308,36 @@ class Pdf:
|
|
|
346
308
|
return None
|
|
347
309
|
|
|
348
310
|
@staticmethod
|
|
349
|
-
def
|
|
350
|
-
"""
|
|
351
|
-
Updates the value of a text annotation, setting the text content.
|
|
352
|
-
|
|
353
|
-
This function modifies the value (V) and appearance (AP) of the text
|
|
354
|
-
annotation to reflect the new text content.
|
|
355
|
-
|
|
356
|
-
Args:
|
|
357
|
-
annotation (DictionaryObject): The text annotation dictionary.
|
|
358
|
-
widget (TextWidget): The Text widget object containing the text value.
|
|
359
|
-
"""
|
|
360
|
-
if PdfAttributes.Parent in annotation and PdfAttributes.T not in annotation:
|
|
361
|
-
annotation[NameObject(PdfAttributes.Parent)] = TextStringObject(widget.value)
|
|
362
|
-
annotation[NameObject(PdfAttributes.AP)] = TextStringObject(widget.value)
|
|
363
|
-
else:
|
|
364
|
-
annotation[NameObject(PdfAttributes.V)] = TextStringObject(widget.value)
|
|
365
|
-
annotation[NameObject(PdfAttributes.AP)] = TextStringObject(widget.value)
|
|
366
|
-
|
|
367
|
-
@staticmethod
|
|
368
|
-
def _fill_checkbox(annotation: DictionaryObject, widget: CheckBoxWidget) -> None:
|
|
369
|
-
value: Union[bool, str, None] = widget.value
|
|
370
|
-
if value is None:
|
|
371
|
-
value = False
|
|
372
|
-
if not isinstance(value, bool):
|
|
373
|
-
annotation[NameObject(PdfAttributes.AS)] = NameObject("/" + widget.value)
|
|
374
|
-
annotation[NameObject(PdfAttributes.V)] = NameObject("/" + widget.value)
|
|
375
|
-
return
|
|
376
|
-
|
|
377
|
-
for each in cast(Any, annotation[PdfAttributes.AP])[PdfAttributes.N]:
|
|
378
|
-
if (value and str(each) != PdfAttributes.Off) or (
|
|
379
|
-
not value and str(each) == PdfAttributes.Off
|
|
380
|
-
):
|
|
381
|
-
annotation[NameObject(PdfAttributes.AS)] = NameObject(each)
|
|
382
|
-
annotation[NameObject(PdfAttributes.V)] = NameObject(each)
|
|
383
|
-
return
|
|
384
|
-
|
|
385
|
-
if PdfAttributes.V in annotation:
|
|
386
|
-
del annotation[PdfAttributes.V]
|
|
387
|
-
if PdfAttributes.AS in annotation:
|
|
388
|
-
del annotation[PdfAttributes.AS]
|
|
389
|
-
|
|
390
|
-
@staticmethod
|
|
391
|
-
def _fill_radio(annotation: DictionaryObject, widget: RadioWidget) -> None:
|
|
392
|
-
|
|
393
|
-
for each in (
|
|
394
|
-
cast(Any, annotation[PdfAttributes.Kids])
|
|
395
|
-
if PdfAttributes.T in annotation
|
|
396
|
-
else cast(Any, annotation[PdfAttributes.Parent])[PdfAttributes.Kids]
|
|
397
|
-
):
|
|
398
|
-
# determine the export value of each kid
|
|
399
|
-
keys = each[PdfAttributes.AP][PdfAttributes.N].keys()
|
|
400
|
-
if PdfAttributes.Off in keys:
|
|
401
|
-
keys.remove(PdfAttributes.Off)
|
|
402
|
-
export = list(keys)[0] or None
|
|
403
|
-
|
|
404
|
-
if f"/{widget.value}" == export:
|
|
405
|
-
annotation[NameObject(PdfAttributes.AS)] = NameObject(export)
|
|
406
|
-
cast(Any, annotation[NameObject(PdfAttributes.Parent)])[
|
|
407
|
-
NameObject(PdfAttributes.V)
|
|
408
|
-
] = NameObject(export)
|
|
409
|
-
else:
|
|
410
|
-
annotation[NameObject(PdfAttributes.AS)] = NameObject(PdfAttributes.Off)
|
|
411
|
-
|
|
412
|
-
@staticmethod
|
|
413
|
-
def flatten_radio(annot: DictionaryObject, val: bool) -> None:
|
|
311
|
+
def is_readonly(widget_type: str, annot: DictionaryObject) -> bool:
|
|
414
312
|
"""
|
|
415
|
-
|
|
416
|
-
making it non-editable or editable based on the `val` parameter.
|
|
313
|
+
Determines whether readonly flag is set or not.
|
|
417
314
|
|
|
418
|
-
This function
|
|
419
|
-
|
|
420
|
-
unset the ReadOnly flag, preventing or allowing the user from changing the
|
|
421
|
-
selected option.
|
|
315
|
+
This function evaluates if readonly is activating by checking Ff (flags) entry
|
|
316
|
+
in the annotation dictionary.
|
|
422
317
|
|
|
423
318
|
Args:
|
|
424
|
-
|
|
425
|
-
|
|
319
|
+
widget_type (str): The widget type
|
|
320
|
+
annot (DictionaryObject): The annotation dictionary.
|
|
321
|
+
Returns:
|
|
322
|
+
True if read-only is set, else False
|
|
426
323
|
"""
|
|
427
|
-
if
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
(
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
NameObject(PdfAttributes.Ff), 0
|
|
434
|
-
)
|
|
324
|
+
if widget_type == "radio":
|
|
325
|
+
if PdfAttributes.Parent in annot:
|
|
326
|
+
return (
|
|
327
|
+
int(
|
|
328
|
+
cast(Any, annot[NameObject(PdfAttributes.Parent)]).get(
|
|
329
|
+
NameObject(PdfAttributes.Ff), 0
|
|
435
330
|
)
|
|
436
|
-
| PdfAttributes.READ_ONLY
|
|
437
|
-
if val
|
|
438
|
-
else int(
|
|
439
|
-
cast(Any, annot[NameObject(PdfAttributes.Parent)]).get(
|
|
440
|
-
NameObject(PdfAttributes.Ff), 0
|
|
441
|
-
)
|
|
442
|
-
)
|
|
443
|
-
& ~PdfAttributes.READ_ONLY
|
|
444
331
|
)
|
|
332
|
+
& PdfAttributes.READ_ONLY
|
|
333
|
+
== PdfAttributes.READ_ONLY
|
|
445
334
|
)
|
|
335
|
+
return (
|
|
336
|
+
int(annot.get(NameObject(PdfAttributes.Ff), 0)) & PdfAttributes.READ_ONLY
|
|
337
|
+
== PdfAttributes.READ_ONLY
|
|
446
338
|
)
|
|
447
|
-
else:
|
|
448
|
-
annot[NameObject(PdfAttributes.Ff)] = NumberObject(
|
|
449
|
-
(
|
|
450
|
-
int(annot.get(NameObject(PdfAttributes.Ff), 0)) | PdfAttributes.READ_ONLY
|
|
451
|
-
if val
|
|
452
|
-
else int(annot.get(NameObject(PdfAttributes.Ff), 0)) & ~PdfAttributes.READ_ONLY
|
|
453
|
-
)
|
|
454
|
-
)
|
|
455
|
-
|
|
456
|
-
@staticmethod
|
|
457
|
-
def flatten_generic(annot: DictionaryObject, val: bool) -> None:
|
|
458
|
-
"""
|
|
459
|
-
Flattens a generic annotation by setting or unsetting the ReadOnly flag,
|
|
460
|
-
making it non-editable or editable based on the `val` parameter.
|
|
461
339
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
Args:
|
|
467
|
-
annot (DictionaryObject): The annotation dictionary.
|
|
468
|
-
val (bool): True to flatten (make read-only), False to unflatten (make editable).
|
|
469
|
-
"""
|
|
470
|
-
if PdfAttributes.Parent in annot and PdfAttributes.Ff not in annot:
|
|
471
|
-
cast(Any, annot[NameObject(PdfAttributes.Parent)])[NameObject(PdfAttributes.Ff)] = (
|
|
472
|
-
NumberObject(
|
|
473
|
-
(
|
|
474
|
-
int(annot.get(NameObject(PdfAttributes.Ff), 0)) | PdfAttributes.READ_ONLY
|
|
475
|
-
if val
|
|
476
|
-
else int(annot.get(NameObject(PdfAttributes.Ff), 0))
|
|
477
|
-
& ~PdfAttributes.READ_ONLY
|
|
478
|
-
)
|
|
479
|
-
)
|
|
480
|
-
)
|
|
481
|
-
else:
|
|
482
|
-
annot[NameObject(PdfAttributes.Ff)] = NumberObject(
|
|
483
|
-
(
|
|
484
|
-
int(annot.get(NameObject(PdfAttributes.Ff), 0)) | PdfAttributes.READ_ONLY
|
|
485
|
-
if val
|
|
486
|
-
else int(annot.get(NameObject(PdfAttributes.Ff), 0)) & ~PdfAttributes.READ_ONLY
|
|
487
|
-
)
|
|
488
|
-
)
|
|
340
|
+
return (
|
|
341
|
+
int(annot.get(NameObject(PdfAttributes.Ff), 0)) & PdfAttributes.READ_ONLY
|
|
342
|
+
== PdfAttributes.READ_ONLY
|
|
343
|
+
)
|
pdffiller/typing.py
CHANGED
pdffiller/widgets/base.py
CHANGED
|
@@ -20,7 +20,9 @@ class Widget:
|
|
|
20
20
|
as name, value, and schema definition.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
def __init__(
|
|
23
|
+
def __init__(
|
|
24
|
+
self, name: str, page_number: int, value: Optional[Any] = None, readonly: bool = False
|
|
25
|
+
) -> None:
|
|
24
26
|
"""
|
|
25
27
|
Initialize a new widget.
|
|
26
28
|
|
|
@@ -28,11 +30,13 @@ class Widget:
|
|
|
28
30
|
name (str): The name of the widget.
|
|
29
31
|
page_number (int): The associated page index
|
|
30
32
|
value (Any): The initial value of the widget. Defaults to None.
|
|
33
|
+
readonly (bool): True if readonly is set, else False.
|
|
31
34
|
"""
|
|
32
35
|
super().__init__()
|
|
33
36
|
self._name: str = name
|
|
34
37
|
self.page_number: int = page_number
|
|
35
38
|
self._value: Optional[Any] = value
|
|
39
|
+
self._readonly: bool = readonly
|
|
36
40
|
self._description: Optional[str] = None
|
|
37
41
|
|
|
38
42
|
@property
|
|
@@ -65,6 +69,26 @@ class Widget:
|
|
|
65
69
|
"""
|
|
66
70
|
self._value = value
|
|
67
71
|
|
|
72
|
+
@property
|
|
73
|
+
def readonly(self) -> bool:
|
|
74
|
+
"""
|
|
75
|
+
Determine whether readonly mode is set or not.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
bool: True if readonly mode is set, else False.
|
|
79
|
+
"""
|
|
80
|
+
return self._readonly
|
|
81
|
+
|
|
82
|
+
@readonly.setter
|
|
83
|
+
def readonly(self, readonly: bool) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Specify if the readonly mode is set
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
readonly (bool): True if readonly mode is set, else False.
|
|
89
|
+
"""
|
|
90
|
+
self._readonly = readonly
|
|
91
|
+
|
|
68
92
|
@property
|
|
69
93
|
def description(self) -> Optional[str]:
|
|
70
94
|
"""
|
|
@@ -101,6 +125,9 @@ class Widget:
|
|
|
101
125
|
if self._value:
|
|
102
126
|
result["FieldValue"] = self._value
|
|
103
127
|
|
|
128
|
+
if self._readonly:
|
|
129
|
+
result["Readonly"] = self._readonly
|
|
130
|
+
|
|
104
131
|
if self._description is not None:
|
|
105
132
|
result["Description"] = self._description
|
|
106
133
|
|
pdffiller/widgets/checkbox.py
CHANGED
|
@@ -24,6 +24,7 @@ class CheckBoxWidget(Widget):
|
|
|
24
24
|
name: str,
|
|
25
25
|
page_number: int,
|
|
26
26
|
value: Optional[str] = None,
|
|
27
|
+
readonly: bool = False,
|
|
27
28
|
choices: Optional[List[str]] = None,
|
|
28
29
|
) -> None:
|
|
29
30
|
"""
|
|
@@ -33,9 +34,10 @@ class CheckBoxWidget(Widget):
|
|
|
33
34
|
name (str): The name of the checkbox.
|
|
34
35
|
page_number (int): The associated page index
|
|
35
36
|
value (str): The initial value of the checkbox. Defaults to None.
|
|
37
|
+
readonly (bool): True if readonly is set, else False.
|
|
36
38
|
choices: The list of available choices. Defaults to None.
|
|
37
39
|
"""
|
|
38
|
-
super().__init__(name, page_number, value)
|
|
40
|
+
super().__init__(name, page_number, value, readonly)
|
|
39
41
|
self.choices: Optional[List[str]] = choices
|
|
40
42
|
|
|
41
43
|
@property
|
pdffiller/widgets/text.py
CHANGED
|
@@ -25,6 +25,7 @@ class TextWidget(Widget):
|
|
|
25
25
|
name: str,
|
|
26
26
|
page_number: int,
|
|
27
27
|
value: Optional[str] = None,
|
|
28
|
+
readonly: bool = False,
|
|
28
29
|
max_length: Optional[int] = None,
|
|
29
30
|
) -> None:
|
|
30
31
|
"""
|
|
@@ -34,9 +35,10 @@ class TextWidget(Widget):
|
|
|
34
35
|
name (str): The name of the checkbox.
|
|
35
36
|
page_number (int): The associated page index
|
|
36
37
|
value (str): The initial value of the checkbox. Defaults to None.
|
|
38
|
+
readonly (bool): True if readonly is set, else False.
|
|
37
39
|
max_length (int): The maximum length of the text field. Defaults to None.
|
|
38
40
|
"""
|
|
39
|
-
super().__init__(name, page_number, value)
|
|
41
|
+
super().__init__(name, page_number, value, readonly)
|
|
40
42
|
self.max_length: Optional[int] = max_length
|
|
41
43
|
|
|
42
44
|
@property
|
|
@@ -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=vef-epYlxNmC71LS6tXOTkDXOOxxiF0dTMJwJJdSnOM,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=VpixUDvzgXYxNX6A5LkMPiLTBKq75yejf_lz-xbWD_E,12287
|
|
7
7
|
pdffiller/py.typed.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
pdffiller/typing.py,sha256=
|
|
8
|
+
pdffiller/typing.py,sha256=dW2hGtyaNgTowUl1zsEt3ldhbHM2KfCyHAASb0RZcEs,980
|
|
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
|
|
@@ -18,19 +18,20 @@ pdffiller/cli/once_argument.py,sha256=olQvNhObObS0iXVL1YCa-_lH76SV0I4FvnlgR9roqo
|
|
|
18
18
|
pdffiller/cli/smart_formatter.py,sha256=59hwF07nKbp-P9IfbqKgMFsfbvjIw5SACCZpUF4nnuE,318
|
|
19
19
|
pdffiller/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
20
|
pdffiller/cli/commands/dump_data_fields.py,sha256=YR8aENCGkCRro0jTuuvMg2HCEos9aF81aL4SxP--_2E,2262
|
|
21
|
-
pdffiller/cli/commands/fill_form.py,sha256=
|
|
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/
|
|
24
|
+
pdffiller/io/custom_pdf_writer.py,sha256=rz4kPXJ0JgQhF3896OKizFdnEoPe989NruBA75HMnBQ,6960
|
|
25
|
+
pdffiller/io/output.py,sha256=QMASWRWmfZGG9DdtlfpWXM3VJAMgWGQwPUzoYp_9FFY,11298
|
|
25
26
|
pdffiller/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
pdffiller/widgets/base.py,sha256=
|
|
27
|
-
pdffiller/widgets/checkbox.py,sha256=
|
|
27
|
+
pdffiller/widgets/base.py,sha256=omGVQsQgMa-ALESnUd3_94oVIYScAMl0SPhHC_DG8Lg,3613
|
|
28
|
+
pdffiller/widgets/checkbox.py,sha256=iijStLAsY1G4cljW3a9NxVS_8qxJewFEw-B8jU2aKXk,1711
|
|
28
29
|
pdffiller/widgets/radio.py,sha256=Db9Oc3Q8ge8qqTVPLoz3I1_SJBGyJ8KfA33ixZMr78c,1070
|
|
29
|
-
pdffiller/widgets/text.py,sha256=
|
|
30
|
-
python_pdffiller-1.
|
|
31
|
-
python_pdffiller-1.
|
|
32
|
-
python_pdffiller-1.
|
|
33
|
-
python_pdffiller-1.
|
|
34
|
-
python_pdffiller-1.
|
|
35
|
-
python_pdffiller-1.
|
|
36
|
-
python_pdffiller-1.
|
|
30
|
+
pdffiller/widgets/text.py,sha256=SiuyBvZPZ6idCmtZ_05zE26iN6Rz67OfOj1fUm98YQI,2397
|
|
31
|
+
python_pdffiller-1.1.1.dist-info/licenses/AUTHORS.rst,sha256=1_hVzMKgmoXvGgrcZC7sIbU_6PvvkB6vwqevAqzrIkQ,205
|
|
32
|
+
python_pdffiller-1.1.1.dist-info/licenses/COPYING,sha256=ADPe-bH2wYq8nFf6EPJyovzTJyl3jSPnm09mGI8FSTo,1074
|
|
33
|
+
python_pdffiller-1.1.1.dist-info/METADATA,sha256=G0UFHlAH4UOctSuJ6J-X0fHitovbcnnmq8LUv7zA79U,4319
|
|
34
|
+
python_pdffiller-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
35
|
+
python_pdffiller-1.1.1.dist-info/entry_points.txt,sha256=RESKKpPPdWl0wDET96ntuFoUydALx9j0mxtbt-MEBjU,49
|
|
36
|
+
python_pdffiller-1.1.1.dist-info/top_level.txt,sha256=5MGWCBFYlu_Ax-I5PgQkV9Xw7O48maPe9z8Qj_yVPL4,10
|
|
37
|
+
python_pdffiller-1.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|