PyPDFForm 2.4.0__py3-none-any.whl → 2.5.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.

Potentially problematic release.


This version of PyPDFForm might be problematic. Click here for more details.

PyPDFForm/__init__.py CHANGED
@@ -5,7 +5,7 @@ This package provides tools for filling PDF forms, drawing text and images,
5
5
  and manipulating PDF form elements programmatically.
6
6
  """
7
7
 
8
- __version__ = "2.4.0"
8
+ __version__ = "2.5.0"
9
9
 
10
10
  from .wrapper import FormWrapper, PdfWrapper
11
11
 
PyPDFForm/hooks.py ADDED
@@ -0,0 +1,216 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Module containing hook functions for PDF form widget manipulation.
3
+
4
+ This module provides functions to apply various transformations and modifications
5
+ to PDF form widgets through a hook system. It allows dynamic modification of
6
+ widget properties like font sizes and other attributes.
7
+ """
8
+
9
+ import sys
10
+ from io import BytesIO
11
+ from typing import cast
12
+
13
+ from pypdf import PdfReader, PdfWriter
14
+ from pypdf.generic import (ArrayObject, DictionaryObject, FloatObject,
15
+ NameObject, NumberObject, TextStringObject)
16
+
17
+ from .constants import (COMB, DA, FONT_COLOR_IDENTIFIER, FONT_SIZE_IDENTIFIER,
18
+ MULTILINE, Annots, Ff, Parent, Q, Rect)
19
+ from .template import get_widget_key
20
+ from .utils import stream_to_io
21
+
22
+
23
+ def trigger_widget_hooks(
24
+ pdf: bytes,
25
+ widgets: dict,
26
+ use_full_widget_name: bool,
27
+ ) -> bytes:
28
+ """Apply all registered widget hooks to a PDF document.
29
+
30
+ Args:
31
+ pdf: The input PDF document as bytes
32
+ widgets: Dictionary mapping widget names to widget objects
33
+ use_full_widget_name: Whether to use full widget names including parent hierarchy
34
+
35
+ Returns:
36
+ The modified PDF document as bytes
37
+
38
+ Note:
39
+ This function processes all pages and annotations in the PDF, applying
40
+ any hooks registered in the widget objects.
41
+ """
42
+
43
+ pdf_file = PdfReader(stream_to_io(pdf))
44
+ output = PdfWriter()
45
+ output.append(pdf_file)
46
+
47
+ for page in output.pages:
48
+ for annot in page.get(Annots, []):
49
+ annot = cast(DictionaryObject, annot.get_object())
50
+ key = get_widget_key(annot.get_object(), use_full_widget_name)
51
+
52
+ widget = widgets.get(key)
53
+ if widget is None or not widget.hooks_to_trigger:
54
+ continue
55
+
56
+ for hook in widget.hooks_to_trigger:
57
+ getattr(sys.modules[__name__], hook[0])(annot, hook[1])
58
+
59
+ for widget in widgets.values():
60
+ widget.hooks_to_trigger = []
61
+
62
+ with BytesIO() as f:
63
+ output.write(f)
64
+ f.seek(0)
65
+ return f.read()
66
+
67
+
68
+ def update_text_field_font_size(annot: DictionaryObject, val: float) -> None:
69
+ """Update the font size of a text field widget.
70
+
71
+ Args:
72
+ annot: The PDF annotation (widget) dictionary object
73
+ val: The new font size value to apply
74
+
75
+ Note:
76
+ Handles both direct font size specification and inherited font sizes
77
+ from parent objects. Modifies the DA (default appearance) string.
78
+ """
79
+
80
+ if Parent in annot and DA not in annot:
81
+ text_appearance = annot[Parent][DA]
82
+ else:
83
+ text_appearance = annot[DA]
84
+
85
+ text_appearance = text_appearance.split(" ")
86
+ font_size_index = 0
87
+ for i, value in enumerate(text_appearance):
88
+ if value.startswith(FONT_SIZE_IDENTIFIER):
89
+ font_size_index = i - 1
90
+ break
91
+
92
+ text_appearance[font_size_index] = str(val)
93
+ new_text_appearance = " ".join(text_appearance)
94
+
95
+ if Parent in annot and DA not in annot:
96
+ annot[NameObject(Parent)][NameObject(DA)] = TextStringObject(
97
+ new_text_appearance
98
+ )
99
+ else:
100
+ annot[NameObject(DA)] = TextStringObject(new_text_appearance)
101
+
102
+
103
+ def update_text_field_font_color(annot: DictionaryObject, val: tuple) -> None:
104
+ """Update the font color of a text field widget.
105
+
106
+ Args:
107
+ annot: The PDF annotation (widget) dictionary object
108
+ val: Tuple containing RGB color values (e.g. (1, 0, 0) for red)
109
+
110
+ Note:
111
+ Handles both direct font color specification and inherited font colors
112
+ from parent objects. Modifies the DA (default appearance) string to
113
+ include the new color values after the font size identifier.
114
+ """
115
+
116
+ if Parent in annot and DA not in annot:
117
+ text_appearance = annot[Parent][DA]
118
+ else:
119
+ text_appearance = annot[DA]
120
+
121
+ text_appearance = text_appearance.split(" ")
122
+ font_size_identifier_index = 0
123
+ for i, value in enumerate(text_appearance):
124
+ if value == FONT_SIZE_IDENTIFIER:
125
+ font_size_identifier_index = i
126
+ break
127
+
128
+ new_text_appearance = (
129
+ text_appearance[:font_size_identifier_index]
130
+ + [FONT_SIZE_IDENTIFIER]
131
+ + [str(each) for each in val]
132
+ )
133
+ new_text_appearance = " ".join(new_text_appearance) + FONT_COLOR_IDENTIFIER
134
+
135
+ if Parent in annot and DA not in annot:
136
+ annot[NameObject(Parent)][NameObject(DA)] = TextStringObject(
137
+ new_text_appearance
138
+ )
139
+ else:
140
+ annot[NameObject(DA)] = TextStringObject(new_text_appearance)
141
+
142
+
143
+ def update_text_field_alignment(annot: DictionaryObject, val: int) -> None:
144
+ """Update text alignment for text field annotations.
145
+
146
+ Modifies the alignment (Q) field of a text field annotation to set the
147
+ specified text alignment.
148
+
149
+ Args:
150
+ annot: PDF text field annotation dictionary to modify
151
+ val: Alignment value to set (typically 0=left, 1=center, 2=right)
152
+ """
153
+
154
+ annot[NameObject(Q)] = NumberObject(val)
155
+
156
+
157
+ def update_text_field_multiline(annot: DictionaryObject, val: bool) -> None:
158
+ """Update multiline flag for text field annotations.
159
+
160
+ Modifies the field flags (Ff) of a text field annotation to set or
161
+ clear the multiline flag based on the input value.
162
+
163
+ Args:
164
+ annot: PDF text field annotation dictionary to modify
165
+ val: Whether to enable multiline (True) or disable (False)
166
+ """
167
+
168
+ if val:
169
+ annot[NameObject(Ff)] = NumberObject(int(annot[NameObject(Ff)]) | MULTILINE)
170
+
171
+
172
+ def update_text_field_comb(annot: DictionaryObject, val: bool) -> None:
173
+ """Update comb formatting flag for text field annotations.
174
+
175
+ Modifies the field flags (Ff) of a text field annotation to set or
176
+ clear the comb flag which enables/disables comb formatting.
177
+
178
+ Args:
179
+ annot: PDF text field annotation dictionary to modify
180
+ val: Whether to enable comb formatting (True) or disable (False)
181
+ """
182
+
183
+ if val:
184
+ annot[NameObject(Ff)] = NumberObject(int(annot[NameObject(Ff)]) | COMB)
185
+
186
+
187
+ def update_check_radio_size(annot: DictionaryObject, val: float) -> None:
188
+ """Update the size of a checkbox or radio button widget while maintaining center position.
189
+
190
+ Args:
191
+ annot: PDF annotation dictionary containing the widget to modify
192
+ val: New size value (width and height) for the widget
193
+
194
+ Note:
195
+ The widget will be resized symmetrically around its center point,
196
+ maintaining the same center position while changing its dimensions.
197
+ """
198
+
199
+ rect = annot[Rect]
200
+ center_x = (rect[0] + rect[2]) / 2
201
+ center_y = (rect[1] + rect[3]) / 2
202
+ new_rect = [
203
+ FloatObject(center_x - val / 2),
204
+ FloatObject(center_y - val / 2),
205
+ FloatObject(center_x + val / 2),
206
+ FloatObject(center_y + val / 2),
207
+ ]
208
+ annot[NameObject(Rect)] = ArrayObject(new_rect)
209
+
210
+
211
+ # TODO: remove this and switch to hooks
212
+ NON_ACRO_FORM_PARAM_TO_FUNC = {
213
+ ("TextWidget", "alignment"): update_text_field_alignment,
214
+ ("TextWidget", "multiline"): update_text_field_multiline,
215
+ ("TextWidget", "comb"): update_text_field_comb,
216
+ }
@@ -26,6 +26,8 @@ class Widget:
26
26
  - Any widget-specific functionality
27
27
  """
28
28
 
29
+ SET_ATTR_TRIGGER_HOOK_MAP = {}
30
+
29
31
  def __init__(
30
32
  self,
31
33
  name: str,
@@ -48,6 +50,28 @@ class Widget:
48
50
  self.border_style = None
49
51
  self.dash_array = None
50
52
  self.render_widget = None
53
+ self.hooks_to_trigger = []
54
+
55
+ def __setattr__(self, name: str, value: Any) -> None:
56
+ """Sets an attribute on the widget with special handling for hook-triggering attributes.
57
+
58
+ For attributes listed in SET_ATTR_TRIGGER_HOOK_MAP, when set to non-None values,
59
+ adds a hook to hooks_to_trigger list with the mapped hook name and value.
60
+ All other attributes are set normally via the standard object.__setattr__.
61
+
62
+ Args:
63
+ name: Name of the attribute to set
64
+ value: Value to set the attribute to
65
+
66
+ Note:
67
+ The hook triggering only occurs when:
68
+ 1. The attribute is in SET_ATTR_TRIGGER_HOOK_MAP
69
+ 2. The value being set is not None
70
+ """
71
+
72
+ if name in self.SET_ATTR_TRIGGER_HOOK_MAP and value is not None:
73
+ self.hooks_to_trigger.append((self.SET_ATTR_TRIGGER_HOOK_MAP[name], value))
74
+ super().__setattr__(name, value)
51
75
 
52
76
  @property
53
77
  def name(self) -> str:
@@ -25,6 +25,10 @@ class Checkbox(Widget):
25
25
  Inherits from Widget base class and extends it with checkbox-specific features.
26
26
  """
27
27
 
28
+ SET_ATTR_TRIGGER_HOOK_MAP = {
29
+ "size": "update_check_radio_size",
30
+ }
31
+
28
32
  BUTTON_STYLE_MAPPING = {
29
33
  "check": "4",
30
34
  "cross": "5",
@@ -27,6 +27,11 @@ class Text(Widget):
27
27
  Inherits from Widget base class and extends it with text-specific features.
28
28
  """
29
29
 
30
+ SET_ATTR_TRIGGER_HOOK_MAP = {
31
+ "font_size": "update_text_field_font_size",
32
+ "font_color": "update_text_field_font_color",
33
+ }
34
+
30
35
  def __init__(
31
36
  self,
32
37
  name: str,
PyPDFForm/patterns.py CHANGED
@@ -19,9 +19,9 @@ from pypdf.generic import (ArrayObject, DictionaryObject, NameObject,
19
19
  NumberObject, TextStringObject)
20
20
 
21
21
  from .constants import (AP, AS, BC, BG, BS, CA, DA, DV, FT,
22
- IMAGE_FIELD_IDENTIFIER, JS, MK, MULTILINE, READ_ONLY,
23
- TU, A, Btn, Ch, D, Ff, I, N, Off, Opt, Parent, Q, S,
24
- Sig, T, Tx, V, W, Yes)
22
+ IMAGE_FIELD_IDENTIFIER, JS, MK, READ_ONLY, TU, A, Btn,
23
+ Ch, D, Ff, I, N, Off, Opt, Parent, Q, S, Sig, T, Tx, V,
24
+ W, Yes)
25
25
  from .middleware.checkbox import Checkbox
26
26
  from .middleware.dropdown import Dropdown
27
27
  from .middleware.image import Image
@@ -273,38 +273,3 @@ def update_annotation_name(annot: DictionaryObject, val: str) -> None:
273
273
  annot[NameObject(Parent)][NameObject(T)] = TextStringObject(val)
274
274
  else:
275
275
  annot[NameObject(T)] = TextStringObject(val)
276
-
277
-
278
- def update_created_text_field_alignment(annot: DictionaryObject, val: int) -> None:
279
- """Update text alignment for created text field annotations.
280
-
281
- Modifies the alignment (Q) field of a text field annotation created
282
- by the library to set the specified text alignment.
283
-
284
- Args:
285
- annot: PDF text field annotation dictionary to modify
286
- val: Alignment value to set (typically 0=left, 1=center, 2=right)
287
- """
288
-
289
- annot[NameObject(Q)] = NumberObject(val)
290
-
291
-
292
- def update_created_text_field_multiline(annot: DictionaryObject, val: bool) -> None:
293
- """Update multiline flag for created text field annotations.
294
-
295
- Modifies the field flags (Ff) of a text field annotation created by
296
- the library to set or clear the multiline flag based on the input value.
297
-
298
- Args:
299
- annot: PDF text field annotation dictionary to modify
300
- val: Whether to enable multiline (True) or disable (False)
301
- """
302
-
303
- if val:
304
- annot[NameObject(Ff)] = NumberObject(int(annot[NameObject(Ff)]) | MULTILINE)
305
-
306
-
307
- NON_ACRO_FORM_PARAM_TO_FUNC = {
308
- ("TextWidget", "alignment"): update_created_text_field_alignment,
309
- ("TextWidget", "multiline"): update_created_text_field_multiline,
310
- }
PyPDFForm/widgets/base.py CHANGED
@@ -16,7 +16,7 @@ from reportlab.lib.colors import Color
16
16
  from reportlab.pdfgen.canvas import Canvas
17
17
 
18
18
  from ..constants import Annots
19
- from ..patterns import NON_ACRO_FORM_PARAM_TO_FUNC
19
+ from ..hooks import NON_ACRO_FORM_PARAM_TO_FUNC
20
20
  from ..template import get_widget_key
21
21
  from ..utils import stream_to_io
22
22
 
PyPDFForm/widgets/text.py CHANGED
@@ -36,6 +36,6 @@ class TextWidget(Widget):
36
36
  ("max_length", "maxlen"),
37
37
  ]
38
38
  COLOR_PARAMS = ["font_color", "bg_color", "border_color"]
39
- ALLOWED_NON_ACRO_FORM_PARAMS = ["alignment", "multiline"]
39
+ ALLOWED_NON_ACRO_FORM_PARAMS = ["alignment", "multiline", "comb"]
40
40
  NONE_DEFAULTS = ["max_length"]
41
41
  ACRO_FORM_FUNC = "textfield"
PyPDFForm/wrapper.py CHANGED
@@ -26,6 +26,7 @@ from .constants import (DEFAULT_FONT, DEFAULT_FONT_COLOR, DEFAULT_FONT_SIZE,
26
26
  from .coordinate import generate_coordinate_grid
27
27
  from .filler import fill, simple_fill
28
28
  from .font import register_font
29
+ from .hooks import trigger_widget_hooks
29
30
  from .image import rotate_image
30
31
  from .middleware.dropdown import Dropdown
31
32
  from .middleware.text import Text
@@ -84,7 +85,7 @@ class FormWrapper:
84
85
  """
85
86
 
86
87
  super().__init__()
87
- self.stream = fp_or_f_obj_or_stream_to_stream(template)
88
+ self._stream = fp_or_f_obj_or_stream_to_stream(template)
88
89
  self.use_full_widget_name = kwargs.get("use_full_widget_name", False)
89
90
 
90
91
  def read(self) -> bytes:
@@ -98,7 +99,7 @@ class FormWrapper:
98
99
  bytes: The complete PDF document as a byte string
99
100
  """
100
101
 
101
- return self.stream
102
+ return self._stream
102
103
 
103
104
  def fill(
104
105
  self,
@@ -132,8 +133,8 @@ class FormWrapper:
132
133
  """
133
134
 
134
135
  widgets = (
135
- build_widgets(self.stream, self.use_full_widget_name, False)
136
- if self.stream
136
+ build_widgets(self.read(), self.use_full_widget_name, False)
137
+ if self.read()
137
138
  else {}
138
139
  )
139
140
 
@@ -141,7 +142,7 @@ class FormWrapper:
141
142
  if key in widgets:
142
143
  widgets[key].value = value
143
144
 
144
- self.stream = simple_fill(
145
+ self._stream = simple_fill(
145
146
  self.read(),
146
147
  widgets,
147
148
  use_full_widget_name=self.use_full_widget_name,
@@ -177,6 +178,8 @@ class PdfWrapper(FormWrapper):
177
178
  ("use_full_widget_name", False),
178
179
  ("render_widgets", True),
179
180
  ]
181
+ # TODO: remove, always default to True
182
+ TRIGGER_WIDGET_HOOKS = False
180
183
 
181
184
  def __init__(
182
185
  self,
@@ -255,6 +258,31 @@ class PdfWrapper(FormWrapper):
255
258
  value.font_size = getattr(self, "global_font_size")
256
259
  value.font_color = getattr(self, "global_font_color")
257
260
 
261
+ def read(self) -> bytes:
262
+ """Returns the raw bytes of the PDF form data with optional widget hook processing.
263
+
264
+ Extends FormWrapper.read() with additional functionality:
265
+ - Triggers any registered widget hooks if TRIGGER_WIDGET_HOOKS is True
266
+ - Maintains all parent class behavior of returning raw PDF bytes
267
+
268
+ The method first processes any widget hooks that need triggering, then delegates
269
+ to the parent class's read() implementation to return the PDF bytes.
270
+
271
+ Returns:
272
+ bytes: The complete PDF document as a byte string, after any hook processing
273
+ """
274
+
275
+ if self.TRIGGER_WIDGET_HOOKS and any(
276
+ widget.hooks_to_trigger for widget in self.widgets.values()
277
+ ):
278
+ self._stream = trigger_widget_hooks(
279
+ self._stream,
280
+ self.widgets,
281
+ getattr(self, "use_full_widget_name"),
282
+ )
283
+
284
+ return super().read()
285
+
258
286
  @property
259
287
  def sample_data(self) -> dict:
260
288
  """Generates a dictionary of sample values for all form fields.
@@ -287,7 +315,7 @@ class PdfWrapper(FormWrapper):
287
315
  """
288
316
 
289
317
  for each in VERSION_IDENTIFIERS:
290
- if self.stream.startswith(each):
318
+ if self.read().startswith(each):
291
319
  return each.replace(VERSION_IDENTIFIER_PREFIX, b"").decode()
292
320
 
293
321
  return None
@@ -309,7 +337,7 @@ class PdfWrapper(FormWrapper):
309
337
 
310
338
  return [
311
339
  self.__class__(
312
- copy_watermark_widgets(each, self.stream, None, i),
340
+ copy_watermark_widgets(each, self.read(), None, i),
313
341
  **{param: getattr(self, param) for param, _ in self.USER_PARAMS},
314
342
  )
315
343
  for i, each in enumerate(get_page_streams(remove_all_widgets(self.read())))
@@ -328,7 +356,7 @@ class PdfWrapper(FormWrapper):
328
356
  PdfWrapper: Returns self to allow method chaining
329
357
  """
330
358
 
331
- self.stream = self.stream.replace(
359
+ self._stream = self.read().replace(
332
360
  VERSION_IDENTIFIER_PREFIX + bytes(self.version, "utf-8"),
333
361
  VERSION_IDENTIFIER_PREFIX + bytes(version, "utf-8"),
334
362
  1,
@@ -351,10 +379,10 @@ class PdfWrapper(FormWrapper):
351
379
  PdfWrapper: New wrapper containing merged PDF
352
380
  """
353
381
 
354
- if not self.stream:
382
+ if not self.read():
355
383
  return other
356
384
 
357
- if not other.stream:
385
+ if not other.read():
358
386
  return self
359
387
 
360
388
  unique_suffix = generate_unique_suffix()
@@ -364,7 +392,7 @@ class PdfWrapper(FormWrapper):
364
392
 
365
393
  other.commit_widget_key_updates()
366
394
 
367
- return self.__class__(merge_two_pdfs(self.stream, other.stream))
395
+ return self.__class__(merge_two_pdfs(self.read(), other.read()))
368
396
 
369
397
  @property
370
398
  def preview(self) -> bytes:
@@ -381,7 +409,7 @@ class PdfWrapper(FormWrapper):
381
409
 
382
410
  return remove_all_widgets(
383
411
  fill(
384
- self.stream,
412
+ self.read(),
385
413
  {
386
414
  key: preview_widget_to_draw(key, value, True)
387
415
  for key, value in self.widgets.items()
@@ -408,10 +436,10 @@ class PdfWrapper(FormWrapper):
408
436
  PdfWrapper: Returns self to allow method chaining
409
437
  """
410
438
 
411
- self.stream = generate_coordinate_grid(
439
+ self._stream = generate_coordinate_grid(
412
440
  remove_all_widgets(
413
441
  fill(
414
- self.stream,
442
+ self.read(),
415
443
  {
416
444
  key: preview_widget_to_draw(key, value, False)
417
445
  for key, value in self.widgets.items()
@@ -454,15 +482,15 @@ class PdfWrapper(FormWrapper):
454
482
  self.widgets[key] = dropdown_to_text(value)
455
483
 
456
484
  update_text_field_attributes(
457
- self.stream, self.widgets, getattr(self, "use_full_widget_name")
485
+ self.read(), self.widgets, getattr(self, "use_full_widget_name")
458
486
  )
459
487
  if self.read():
460
488
  self.widgets = set_character_x_paddings(
461
- self.stream, self.widgets, getattr(self, "use_full_widget_name")
489
+ self.read(), self.widgets, getattr(self, "use_full_widget_name")
462
490
  )
463
491
 
464
- self.stream = remove_all_widgets(
465
- fill(self.stream, self.widgets, getattr(self, "use_full_widget_name"))
492
+ self._stream = remove_all_widgets(
493
+ fill(self.read(), self.widgets, getattr(self, "use_full_widget_name"))
466
494
  )
467
495
 
468
496
  return self
@@ -527,10 +555,10 @@ class PdfWrapper(FormWrapper):
527
555
  obj = _class(name=name, page_number=page_number, x=x, y=y, **kwargs)
528
556
  watermarks = obj.watermarks(self.read())
529
557
 
530
- self.stream = copy_watermark_widgets(self.read(), watermarks, [name], None)
558
+ self._stream = copy_watermark_widgets(self.read(), watermarks, [name], None)
531
559
  if obj.non_acro_form_params:
532
- self.stream = handle_non_acro_form_params(
533
- self.stream, name, obj.non_acro_form_params
560
+ self._stream = handle_non_acro_form_params(
561
+ self.read(), name, obj.non_acro_form_params
534
562
  )
535
563
 
536
564
  key_to_refresh = ""
@@ -569,7 +597,7 @@ class PdfWrapper(FormWrapper):
569
597
  self._keys_to_update.append((old_key, new_key, index))
570
598
  return self
571
599
 
572
- self.stream = update_widget_keys(
600
+ self._stream = update_widget_keys(
573
601
  self.read(), self.widgets, [old_key], [new_key], [index]
574
602
  )
575
603
  self._init_helper()
@@ -597,7 +625,7 @@ class PdfWrapper(FormWrapper):
597
625
  new_keys = [each[1] for each in self._keys_to_update]
598
626
  indices = [each[2] for each in self._keys_to_update]
599
627
 
600
- self.stream = update_widget_keys(
628
+ self._stream = update_widget_keys(
601
629
  self.read(), self.widgets, old_keys, new_keys, indices
602
630
  )
603
631
  self._init_helper()
@@ -645,7 +673,7 @@ class PdfWrapper(FormWrapper):
645
673
  new_widget.text_lines = text.split(NEW_LINE_SYMBOL)
646
674
 
647
675
  watermarks = create_watermarks_and_draw(
648
- self.stream,
676
+ self.read(),
649
677
  page_number,
650
678
  "text",
651
679
  [
@@ -658,9 +686,9 @@ class PdfWrapper(FormWrapper):
658
686
  )
659
687
 
660
688
  stream_with_widgets = self.read()
661
- self.stream = merge_watermarks_with_pdf(self.stream, watermarks)
662
- self.stream = copy_watermark_widgets(
663
- remove_all_widgets(self.stream), stream_with_widgets, None, None
689
+ self._stream = merge_watermarks_with_pdf(self.read(), watermarks)
690
+ self._stream = copy_watermark_widgets(
691
+ remove_all_widgets(self.read()), stream_with_widgets, None, None
664
692
  )
665
693
 
666
694
  return self
@@ -696,16 +724,16 @@ class PdfWrapper(FormWrapper):
696
724
  image = fp_or_f_obj_or_stream_to_stream(image)
697
725
  image = rotate_image(image, rotation)
698
726
  watermarks = create_watermarks_and_draw(
699
- self.stream,
727
+ self.read(),
700
728
  page_number,
701
729
  "image",
702
730
  [{"stream": image, "x": x, "y": y, "width": width, "height": height}],
703
731
  )
704
732
 
705
733
  stream_with_widgets = self.read()
706
- self.stream = merge_watermarks_with_pdf(self.stream, watermarks)
707
- self.stream = copy_watermark_widgets(
708
- remove_all_widgets(self.stream), stream_with_widgets, None, None
734
+ self._stream = merge_watermarks_with_pdf(self.read(), watermarks)
735
+ self._stream = copy_watermark_widgets(
736
+ remove_all_widgets(self.read()), stream_with_widgets, None, None
709
737
  )
710
738
 
711
739
  return self
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -1,34 +1,35 @@
1
- PyPDFForm/__init__.py,sha256=TrLXIaBX68LQ3KQc6d_tR2c7UnzA3uXZ90KmNZuoiYM,328
1
+ PyPDFForm/__init__.py,sha256=wAEMuODSkOqin_uUjzUY5oitVvnunyq9iTT7k_VrRbM,328
2
2
  PyPDFForm/adapter.py,sha256=_5fP5UR-NzjDDayJmBRO36DgbnbUzNbjZtHZtPvSM14,1909
3
3
  PyPDFForm/constants.py,sha256=3ed0j11cWd9Uo4s-XvZwwJojPSz8aqdmZgaEishWjqE,2342
4
4
  PyPDFForm/coordinate.py,sha256=gQI7z-GsdCO33Qny5kXLBs6Y2TW5KO_mJ2in64fvXcg,16412
5
5
  PyPDFForm/filler.py,sha256=oCXDzz8paw8-MIQLiTTv5goYlfCfz_EcczCj8K4JB3g,14116
6
6
  PyPDFForm/font.py,sha256=eRbDyQFhXUkHzyZvCtru9Ypg_ukfbBAnSM5xNzPb5ss,7280
7
+ PyPDFForm/hooks.py,sha256=e9mjZgGl7t9Spok3Tgwl9Osmlzx4vod-5z5jVF84x-Q,7117
7
8
  PyPDFForm/image.py,sha256=aYk7BC-AHiqt73durGIQ3e6gE5Ggbdr8jmkCUaQdsk8,1627
8
- PyPDFForm/patterns.py,sha256=VH7cgAFHLKEUIfE1yIlifIJ9mc0Eb2Kqo-krk34HGOY,9316
9
+ PyPDFForm/patterns.py,sha256=nkvtwsm1dbHjAH7FBNwMR5YI6_Jj16KYBm1HxB12vOk,8100
9
10
  PyPDFForm/template.py,sha256=N_Pj3SaSKs3sqIuIdtgHPXvxIHLnHeR2M9IwRCWWKoQ,20043
10
11
  PyPDFForm/utils.py,sha256=QxJSG96FHGch863UdBPT8DdTKN9gVzQzA2ezifTm7z4,8575
11
12
  PyPDFForm/watermark.py,sha256=yvtZKcdPAPprBVEWcFZEnZMJcTwVpQ6u72m0jqYHxx8,12357
12
- PyPDFForm/wrapper.py,sha256=jiuO-GBqAlQgWfXgPZB4dWpF7_t4UpdMokn9XBDGdEI,26582
13
+ PyPDFForm/wrapper.py,sha256=ndk6isS8scu9nxGsgcA9MU1-NNzYsqMLtxm3EY4aqSQ,27678
13
14
  PyPDFForm/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- PyPDFForm/middleware/base.py,sha256=Ex--sUtQqpjFYWi3oAMqjuay_HYToHFMwRtdgWusckI,2802
15
- PyPDFForm/middleware/checkbox.py,sha256=gRGhFySPoIpKBseoiOTS3WggoBBik12dXbZ-LJKzIwM,2611
15
+ PyPDFForm/middleware/base.py,sha256=lHIWBs2axKDYZj4rYRHrBHzSqF7yk0o9S8uAFTWspSA,3783
16
+ PyPDFForm/middleware/checkbox.py,sha256=sUk5hclIKYE8N85ZYvYOI_SYK2WVqlqIP7oBYWlv_yY,2695
16
17
  PyPDFForm/middleware/dropdown.py,sha256=McuZl8Pc2IYPkEHRmM1OcEsh9njekySqjqVxRmQKmWU,1773
17
18
  PyPDFForm/middleware/image.py,sha256=HlPUsIktj-NryIkwwdZlilvrd6sZYifs9IuDgTHp7uQ,950
18
19
  PyPDFForm/middleware/radio.py,sha256=M4yqHYzHj0jvOGbjYdqeYnNAlYhTF-h47qxqrjXDOoU,1921
19
20
  PyPDFForm/middleware/signature.py,sha256=0gexCQwHCEOrjrgvUXeJJCGo2plfSEbXlykPJJCqpfA,2380
20
- PyPDFForm/middleware/text.py,sha256=eAxeVwboPPlnDT4aaOi7UpQ_xPmnlzkIbUOtRm1VMd0,2944
21
+ PyPDFForm/middleware/text.py,sha256=VWL2kLXY_UTz1DkUJOgai9VczU3E4yn_Sa5LwUVTgt4,3091
21
22
  PyPDFForm/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- PyPDFForm/widgets/base.py,sha256=nzRmZFRXjc4fKySMwVQs6YzPxnhnxcgFWnMUvOySWz4,5330
23
+ PyPDFForm/widgets/base.py,sha256=vrWsnrFumgZScSMzRtqxapIkFm3yvasV_xIs2lmI2a0,5327
23
24
  PyPDFForm/widgets/bedrock.py,sha256=j6beU04kaQzpAIFZHI5VJLaDT5RVAAa6LzkU1luJpN8,137660
24
25
  PyPDFForm/widgets/checkbox.py,sha256=_1I5yh1211RgRUyWzd3NNYpI9JchqJNSJWaZAhl2uOo,1248
25
26
  PyPDFForm/widgets/dropdown.py,sha256=zszIT5MI6ggBRUEn7oGBKK0pKmDC9LQw3RnqaKG8ocQ,1764
26
27
  PyPDFForm/widgets/image.py,sha256=6y8Ysmk49USr_qWOXD6KGL6cch516cUIlrxoj0pJy9Q,797
27
28
  PyPDFForm/widgets/radio.py,sha256=ipadJyHbgftDUvjGk15kapzgHPN3HjdF_iB_7amXR6o,2737
28
29
  PyPDFForm/widgets/signature.py,sha256=FdXzja3RTuyU9iyA5Y1yPb4Gsfe4rYlM4gcwekIuwog,4997
29
- PyPDFForm/widgets/text.py,sha256=HP2cPEUAzK5QL3kDfMz7gQcC3svCpmYuyFItBjlrBpI,1233
30
- pypdfform-2.4.0.dist-info/licenses/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
31
- pypdfform-2.4.0.dist-info/METADATA,sha256=6g0a3hKc0j1JYVkG7kEVK0iBdZc6bKMVyf2LSEt6VgA,4688
32
- pypdfform-2.4.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
33
- pypdfform-2.4.0.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
34
- pypdfform-2.4.0.dist-info/RECORD,,
30
+ PyPDFForm/widgets/text.py,sha256=O9aTZLnpB_k-ZWx6jNbfKbVLq-O1zjwN3reBMwTNG2M,1241
31
+ pypdfform-2.5.0.dist-info/licenses/LICENSE,sha256=43awmYkI6opyTpg19me731iO1WfXZwViqb67oWtCsFY,1065
32
+ pypdfform-2.5.0.dist-info/METADATA,sha256=r860fmyW62b7Vl9lIJiIVllhtQOKrunEnL9DQb0Bu-0,4688
33
+ pypdfform-2.5.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
34
+ pypdfform-2.5.0.dist-info/top_level.txt,sha256=GQQKuWqPUjT9YZqwK95NlAQzxjwoQrsxQ8ureM8lWOY,10
35
+ pypdfform-2.5.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5