python-pdffiller 1.1.1__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.1"
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,100 +158,64 @@ class Pdf:
244
158
  Returns:
245
159
  Pdf: The `Pdf` object, allowing for method chaining.
246
160
  """
247
- reader = PdfReader(input_file)
248
- output = PdfFillerOutput()
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
249
166
 
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")
272
- with open(output_file, "wb") as f:
273
- writer.write(f)
167
+ output = PdfFillerOutput()
274
168
 
275
- 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]
276
175
 
277
- def _get_widget_name(self, widget: Any) -> Optional[str]:
278
- if PdfAttributes.T not in widget:
279
- return None
280
- key: Optional[str] = widget[PdfAttributes.T]
281
- if (
282
- PdfAttributes.Parent in widget
283
- and PdfAttributes.T in widget[PdfAttributes.Parent].get_object()
284
- and widget[PdfAttributes.Parent].get_object()[PdfAttributes.T] != key
285
- ):
286
- 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
287
207
 
288
- return key
208
+ # Update the widget!
209
+ field.update()
289
210
 
290
- def _get_field_type(self, annotation: Any) -> Optional[str]:
291
- """
292
- Determine widget type given its annotations.
293
- """
294
- ft = annotation[PdfAttributes.FT] if PdfAttributes.FT in annotation else None
295
- ff = annotation[PdfAttributes.Ff] if PdfAttributes.Ff in annotation else None
296
-
297
- if ft == PdfAttributes.Tx:
298
- return "text"
299
- if ft == PdfAttributes.Ch:
300
- if ff and int(ff) & 1 << 17: # test 18th bit
301
- return "combo"
302
- return "list"
303
- if ft == PdfAttributes.Btn:
304
- if ff and int(ff) & 1 << 15: # test 16th bit
305
- return "radio"
306
- return "checkbox"
307
-
308
- return None
309
-
310
- @staticmethod
311
- def is_readonly(widget_type: str, annot: DictionaryObject) -> bool:
312
- """
313
- Determines whether readonly flag is set or not.
211
+ try:
212
+ if flatten:
213
+ output.info("remove all annotations")
214
+ document.bake(annots=False)
314
215
 
315
- This function evaluates if readonly is activating by checking Ff (flags) entry
316
- 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")
317
220
 
318
- Args:
319
- widget_type (str): The widget type
320
- annot (DictionaryObject): The annotation dictionary.
321
- Returns:
322
- True if read-only is set, else False
323
- """
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
330
- )
331
- )
332
- & PdfAttributes.READ_ONLY
333
- == PdfAttributes.READ_ONLY
334
- )
335
- return (
336
- int(annot.get(NameObject(PdfAttributes.Ff), 0)) & PdfAttributes.READ_ONLY
337
- == PdfAttributes.READ_ONLY
338
- )
339
-
340
- return (
341
- int(annot.get(NameObject(PdfAttributes.Ff), 0)) & PdfAttributes.READ_ONLY
342
- == PdfAttributes.READ_ONLY
343
- )
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.1
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,17 +26,17 @@ 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
33
33
 
34
- pypdffiller
35
- ===========
34
+ pdffiller
35
+ =========
36
36
 
37
37
  |Test| |PyPI| |Python| |Code Style| |Pre-Commit| |License|
38
38
 
39
- ``pypdffiller`` is a free and open source pure-Python 3 library for PDF form processing. It contains the essential
39
+ ``python-pdffiller`` is a free and open source pure-Python 3 library for PDF form processing. It contains the essential
40
40
  functionalities needed to interact with PDF forms:
41
41
 
42
42
  - Inspect what data a PDF form needs to be filled with.
@@ -45,9 +45,9 @@ functionalities needed to interact with PDF forms:
45
45
  Installation
46
46
  ------------
47
47
 
48
- As of first version, ``pypdffiller`` is compatible with Python 3.9+.
48
+ As of first version, ``python-pdffiller`` is compatible with Python 3.9+.
49
49
 
50
- Use ``pip`` to install the latest stable version of ``pypdffiller``:
50
+ Use ``pip`` to install the latest stable version of ``python-pdffiller``:
51
51
 
52
52
  .. code-block:: console
53
53
 
@@ -72,7 +72,7 @@ https://github.com/sismicfr/pypdffiller/issues.
72
72
  Documentation
73
73
  -------------
74
74
 
75
- The full documentation for CLI and API is available at https://pypdffiller.readthedocs.org/en/stable/.
75
+ The full documentation for CLI and API is available at https://sismicfr.github.io/pypdffiller/
76
76
 
77
77
  Build the docs
78
78
  ~~~~~~~~~~~~~~
@@ -106,23 +106,19 @@ We use ``tox`` to manage our environment and build the executable:
106
106
  Contributing
107
107
  ------------
108
108
 
109
- For guidelines for contributing to ``pypdffiller``, refer to `CONTRIBUTING.rst <https://github.com/sismicfr/pypdffiller/blob/main/CONTRIBUTING.rst>`_.
109
+ For guidelines for contributing to ``python-pdffiller``, refer to `CONTRIBUTING.rst <https://github.com/sismicfr/pypdffiller/blob/main/CONTRIBUTING.rst>`_.
110
110
 
111
111
 
112
112
  .. |Test| image:: https://github.com/sismicfr/pypdffiller/workflows/Test/badge.svg
113
113
  :target: https://github.com/sismicfr/pypdffiller/actions
114
114
  :alt: Test
115
115
 
116
- .. |PyPI| image:: https://img.shields.io/pypi/v/pypdffiller?label=PyPI&logo=pypi
117
- :target: https://badge.fury.io/py/pypdffiller
116
+ .. |PyPI| image:: https://img.shields.io/pypi/v/python-pdffiller?label=PyPI&logo=pypi
117
+ :target: https://badge.fury.io/py/python-pdffiller
118
118
  :alt: PyPI
119
119
 
120
- .. |Read the Docs| image:: https://img.shields.io/readthedocs/pypdffiller?label=Documentation&logo=Read%20the%20Docs
121
- :target: https://sismicfr.github.io/pypdffiller
122
- :alt: Docs
123
-
124
- .. |Python| image:: https://img.shields.io/pypi/pyversions/pypdffiller.svg?label=Python&logo=Python
125
- :target: https://pypi.python.org/pypi/pypdffiller
120
+ .. |Python| image:: https://img.shields.io/pypi/pyversions/python-pdffiller.svg?label=Python&logo=Python
121
+ :target: https://pypi.python.org/pypi/python-pdffiller
126
122
  :alt: Python
127
123
 
128
124
  .. |Code Style| image:: https://img.shields.io/badge/code%20style-black-000000.svg?label=Code%20Style
@@ -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=vef-epYlxNmC71LS6tXOTkDXOOxxiF0dTMJwJJdSnOM,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=VpixUDvzgXYxNX6A5LkMPiLTBKq75yejf_lz-xbWD_E,12287
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=rz4kPXJ0JgQhF3896OKizFdnEoPe989NruBA75HMnBQ,6960
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.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,,
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(
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__}")