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 CHANGED
@@ -3,4 +3,4 @@ __copyright__ = "Copyright 2025 SISMIC"
3
3
  __email__ = "jraphanel@sismic.fr"
4
4
  __license__ = "MIT"
5
5
  __title__ = "pdffiller"
6
- __version__ = "1.0.1"
6
+ __version__ = "1.1.1"
@@ -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, Union
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, Union[str, int, float, bool]] = {}
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(opts.file)
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, PdfWriter
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](key, i, value)
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, Union[str, int, float, bool]],
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 _fill_text(annotation: DictionaryObject, widget: TextWidget) -> None:
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
- Flattens a radio button annotation by setting or unsetting the ReadOnly flag,
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 modifies the Ff (flags) entry in the radio button's annotation
419
- dictionary or its parent dictionary if `Parent` exists in `annot`, to set or
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
- annot (DictionaryObject): The radio button annotation dictionary.
425
- val (bool): True to flatten (make read-only), False to unflatten (make editable).
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 PdfAttributes.Parent in annot:
428
- cast(Any, annot[NameObject(PdfAttributes.Parent)])[NameObject(PdfAttributes.Ff)] = (
429
- NumberObject(
430
- (
431
- int(
432
- cast(Any, annot[NameObject(PdfAttributes.Parent)]).get(
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
- This function modifies the Ff (flags) entry in the annotation dictionary to
463
- set or unset the ReadOnly flag, preventing or allowing the user from
464
- interacting with the form field.
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
@@ -13,6 +13,7 @@ from typing import (
13
13
  Optional,
14
14
  overload,
15
15
  Sequence,
16
+ TextIO,
16
17
  Tuple,
17
18
  Type,
18
19
  TYPE_CHECKING,
@@ -43,6 +44,7 @@ __all__ = [
43
44
  "Union",
44
45
  "SubParserType",
45
46
  "StrByteType",
47
+ "TextIO",
46
48
  ]
47
49
 
48
50
 
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__(self, name: str, page_number: int, value: Optional[Any] = None) -> None:
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
 
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-pdffiller
3
- Version: 1.0.1
3
+ Version: 1.1.1
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
@@ -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=WrkmYOGT4-sAcJzt27DQMBsPn146loVj-KHn5fWAZJQ,172
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=AfsPcDWoar0N7nRImzpJcgUQ3TyeI8yBZfIv1FYA2Rk,18534
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=suxFXOGIF07R8UhrEf-ro_vNA2pXJGfe8CX64dStXTs,954
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=QylVT7cSMbH5FjdNqOtU_eVL9tWQjCceroZ7A2152Q0,4668
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/output.py,sha256=NWV1SabnR_kXmR8tfUKQQoQ_3RV9wqiSnHqzinQDgB0,11217
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=HosjtNWOXRVjCidZZYlWN7Y_M9Ft1P-F4G3EyluUI3I,2901
27
- pdffiller/widgets/checkbox.py,sha256=6kBSN7sc6uzujAFzfC_HO8SvtiK736ym7hdPBIR1eT4,1603
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=JsluVKl7ZNj5_UZI9rL4MSOd6rLiPxSggHH3g9raUJI,2289
30
- python_pdffiller-1.0.1.dist-info/licenses/AUTHORS.rst,sha256=1_hVzMKgmoXvGgrcZC7sIbU_6PvvkB6vwqevAqzrIkQ,205
31
- python_pdffiller-1.0.1.dist-info/licenses/COPYING,sha256=ADPe-bH2wYq8nFf6EPJyovzTJyl3jSPnm09mGI8FSTo,1074
32
- python_pdffiller-1.0.1.dist-info/METADATA,sha256=aURIIDOzRhNXwYzesDa-dxReKz77hnGQrUCrgR39pLg,4319
33
- python_pdffiller-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- python_pdffiller-1.0.1.dist-info/entry_points.txt,sha256=RESKKpPPdWl0wDET96ntuFoUydALx9j0mxtbt-MEBjU,49
35
- python_pdffiller-1.0.1.dist-info/top_level.txt,sha256=5MGWCBFYlu_Ax-I5PgQkV9Xw7O48maPe9z8Qj_yVPL4,10
36
- python_pdffiller-1.0.1.dist-info/RECORD,,
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,,