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 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.1.2"
6
+ __version__ = "2.0.0"
@@ -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
- from pypdf import PdfReader
16
- from pypdf.errors import PyPdfError
17
- from pypdf.generic import ArrayObject, DictionaryObject, NameObject
15
+ import pymupdf
18
16
 
19
- from pdffiller.io.custom_pdf_writer import CustomPdfWriter
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
- "text": TextWidget,
79
- "radio": RadioWidget,
80
- "checkbox": CheckBoxWidget,
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
- content (Optional[StrByteType]): The template PDF, provided as either:
95
- - str: The file path to the PDF.
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.adobe_mode = adode_mode
103
-
104
- self._init_helper(content)
64
+ self._init_helper(filename, stream)
105
65
 
106
- def _init_helper(self, content: Optional[StrByteType] = None) -> None:
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
- content (Optional[StrByteType]): The template PDF, provided as either:
116
- - str: The file path to the PDF.
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 content:
76
+ if not filename and not stream:
120
77
  return
121
78
 
122
79
  output = PdfFillerOutput()
123
- output.verbose("loading file in memory")
80
+ output.info("loading file in memory")
124
81
  loaded_widgets: OrderedDict[str, Widget] = OrderedDict()
125
82
  try:
126
- pdf_file = PdfReader(content)
127
- except PyPdfError as ex:
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
- return
130
-
131
- for i, page in enumerate(pdf_file.pages):
132
- output.verbose(f"loading page {i+1}/{len(pdf_file.pages)}")
133
- widgets: Optional[ArrayObject] = page.annotations
134
- if not widgets:
135
- continue
136
- for widget in widgets:
137
- choices: Optional[List[str]] = None
138
- if (
139
- PdfAttributes.Subtype not in widget
140
- or widget[PdfAttributes.Subtype] != PdfAttributes.Widget
141
- ):
142
- continue
143
-
144
- if PdfAttributes.T not in widget:
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
- int(widget[PdfAttributes.MaxLen])
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[key]
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[key]).choices = new_widget.choices
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: StrByteType,
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 (StrByteType): The template PDF, provided as either:
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
- reader = PdfReader(input_file)
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
- 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=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
- def _get_widget_name(self, widget: Any) -> Optional[str]:
286
- if PdfAttributes.T not in widget:
287
- return None
288
- key: Optional[str] = widget[PdfAttributes.T]
289
- if (
290
- PdfAttributes.Parent in widget
291
- and PdfAttributes.T in widget[PdfAttributes.Parent].get_object()
292
- and widget[PdfAttributes.Parent].get_object()[PdfAttributes.T] != key
293
- ):
294
- key = f"{self._get_widget_name(widget[PdfAttributes.Parent].get_object())}.{key}"
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
- return key
208
+ # Update the widget!
209
+ field.update()
297
210
 
298
- def _get_field_type(self, annotation: Any) -> Optional[str]:
299
- """
300
- Determine widget type given its annotations.
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
- This function evaluates if readonly is activating by checking Ff (flags) entry
324
- in the annotation dictionary.
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
- Args:
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
- "StrByteType",
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: 1.1.2
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: pypdf
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=Za2kjmAG5y6PfrdsRibsxyoNKcK_36DjRsmSsSZVa1c,172
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=Z_AwWD2_PSvRwJGQy9-czmxnVqPvbAY7y9T7GXtIboo,12733
6
+ pdffiller/pdf.py,sha256=zy-mIC5GQBSK2iKOceRiioMQGTTrp9leFpLSaJBxV50,8417
7
7
  pdffiller/py.typed.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- pdffiller/typing.py,sha256=dW2hGtyaNgTowUl1zsEt3ldhbHM2KfCyHAASb0RZcEs,980
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=YR8aENCGkCRro0jTuuvMg2HCEos9aF81aL4SxP--_2E,2262
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-1.1.2.dist-info/licenses/AUTHORS.rst,sha256=1_hVzMKgmoXvGgrcZC7sIbU_6PvvkB6vwqevAqzrIkQ,205
32
- python_pdffiller-1.1.2.dist-info/licenses/COPYING,sha256=ADPe-bH2wYq8nFf6EPJyovzTJyl3jSPnm09mGI8FSTo,1074
33
- python_pdffiller-1.1.2.dist-info/METADATA,sha256=TOlT-uj2UUWfSM9YE93bhc0-cpOoNbmHBTKVqwFAPIo,4164
34
- python_pdffiller-1.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
- python_pdffiller-1.1.2.dist-info/entry_points.txt,sha256=RESKKpPPdWl0wDET96ntuFoUydALx9j0mxtbt-MEBjU,49
36
- python_pdffiller-1.1.2.dist-info/top_level.txt,sha256=5MGWCBFYlu_Ax-I5PgQkV9Xw7O48maPe9z8Qj_yVPL4,10
37
- python_pdffiller-1.1.2.dist-info/RECORD,,
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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__}")