PyPDFForm 4.6.0__tar.gz → 4.6.2__tar.gz

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.
Files changed (72) hide show
  1. {pypdfform-4.6.0 → pypdfform-4.6.2}/PKG-INFO +5 -5
  2. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/__init__.py +1 -1
  3. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/annotations/base.py +24 -5
  4. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/annotations/link.py +3 -4
  5. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/annotations/text.py +3 -3
  6. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/annotations/text_markup.py +22 -17
  7. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/hooks.py +68 -0
  8. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/base.py +17 -1
  9. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/template.py +15 -40
  10. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/utils.py +0 -1
  11. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/wrapper.py +3 -5
  12. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm.egg-info/PKG-INFO +5 -5
  13. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm.egg-info/requires.txt +1 -2
  14. {pypdfform-4.6.0 → pypdfform-4.6.2}/README.md +3 -2
  15. {pypdfform-4.6.0 → pypdfform-4.6.2}/pyproject.toml +1 -2
  16. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_functional.py +0 -12
  17. {pypdfform-4.6.0 → pypdfform-4.6.2}/LICENSE +0 -0
  18. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/adapter.py +0 -0
  19. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/annotations/__init__.py +0 -0
  20. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/ap.py +0 -0
  21. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/assets/__init__.py +0 -0
  22. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/assets/bedrock.py +0 -0
  23. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/assets/blank.py +0 -0
  24. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/constants.py +0 -0
  25. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/coordinate.py +0 -0
  26. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/deprecation.py +0 -0
  27. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/filler.py +0 -0
  28. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/font.py +0 -0
  29. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/image.py +0 -0
  30. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/__init__.py +0 -0
  31. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/checkbox.py +0 -0
  32. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/dropdown.py +0 -0
  33. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/image.py +0 -0
  34. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/radio.py +0 -0
  35. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/signature.py +0 -0
  36. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/text.py +0 -0
  37. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/patterns.py +0 -0
  38. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/__init__.py +0 -0
  39. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/circle.py +0 -0
  40. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/ellipse.py +0 -0
  41. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/image.py +0 -0
  42. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/line.py +0 -0
  43. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/rect.py +0 -0
  44. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/text.py +0 -0
  45. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/types.py +0 -0
  46. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/watermark.py +0 -0
  47. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/__init__.py +0 -0
  48. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/base.py +0 -0
  49. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/checkbox.py +0 -0
  50. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/dropdown.py +0 -0
  51. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/image.py +0 -0
  52. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/radio.py +0 -0
  53. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/signature.py +0 -0
  54. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/text.py +0 -0
  55. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm.egg-info/SOURCES.txt +0 -0
  56. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  57. {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm.egg-info/top_level.txt +0 -0
  58. {pypdfform-4.6.0 → pypdfform-4.6.2}/setup.cfg +0 -0
  59. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_bulk_create_fields.py +0 -0
  60. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_create_widget.py +0 -0
  61. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_draw_elements.py +0 -0
  62. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_dropdown.py +0 -0
  63. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_extract_middleware_attributes.py +0 -0
  64. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_fill_max_length_text_field.py +0 -0
  65. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_font_widths.py +0 -0
  66. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_generate_appearance_streams.py +0 -0
  67. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_js.py +0 -0
  68. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_need_appearances.py +0 -0
  69. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_paragraph.py +0 -0
  70. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_signature.py +0 -0
  71. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_use_full_widget_name.py +0 -0
  72. {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_widget_attr_trigger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 4.6.0
3
+ Version: 4.6.2
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -29,9 +29,8 @@ Requires-Dist: reportlab<5.0.0,>=4.4.6
29
29
  Provides-Extra: dev
30
30
  Requires-Dist: black<27.0.0,>=25.11.0; extra == "dev"
31
31
  Requires-Dist: coverage<8.0.0,>=7.12.0; extra == "dev"
32
- Requires-Dist: isort<8.0.0,>=7.0.0; extra == "dev"
32
+ Requires-Dist: isort<9.0.0,>=7.0.0; extra == "dev"
33
33
  Requires-Dist: jsonschema<5.0.0,>=4.25.1; extra == "dev"
34
- Requires-Dist: mkdocs<2.0.0,>=1.6.1; extra == "dev"
35
34
  Requires-Dist: mkdocs-material<10.0.0,>=9.7.0; extra == "dev"
36
35
  Requires-Dist: pudb<2026.0.0,>=2025.1.3; extra == "dev"
37
36
  Requires-Dist: pylint<5.0.0,>=4.0.3; extra == "dev"
@@ -48,7 +47,7 @@ Dynamic: license-file
48
47
  <p align="center">
49
48
  <a href="https://pypi.org/project/PyPDFForm/"><img src="https://img.shields.io/pypi/v/pypdfform?label=version&color=magenta"></a>
50
49
  <a href="https://chinapandaman.github.io/PyPDFForm/"><img src="https://img.shields.io/github/v/release/chinapandaman/pypdfform?label=docs&color=cyan"></a>
51
- <a href="https://codecov.io/gh/chinapandaman/PyPDFForm"><img src="https://img.shields.io/codecov/c/github/chinapandaman/pypdfform"/></a>
50
+ <a href="https://github.com/chinapandaman/PyPDFForm/actions/workflows/python-package.yml"><img src="https://img.shields.io/badge/coverage-100%25-green"></a>
52
51
  <a href="https://github.com/chinapandaman/PyPDFForm/raw/master/LICENSE"><img src="https://img.shields.io/github/license/chinapandaman/pypdfform?label=license&color=orange"></a>
53
52
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/pypi/pyversions/pypdfform?label=python&color=gold"></a>
54
53
  <a href="https://pepy.tech/projects/pypdfform"><img src="https://static.pepy.tech/badge/pypdfform/month"></a>
@@ -103,7 +102,8 @@ The official documentation can be found on [the GitHub page](https://chinapandam
103
102
 
104
103
  ## Other Resources
105
104
 
106
- [Chicago Python User Group - Dec 14, 2023](https://youtu.be/8t1RdAKwr9w?si=TLgumBNXv9H8szSn)
105
+ * [(WIP) Video Tutorials](https://youtube.com/playlist?list=PLNz_PBu1QA-gzYg5BvyOO98q15u5Xve1L&si=8MWasKEckBzY-NRQ)
106
+ * [Chicago Python User Group - Dec 14, 2023](https://youtu.be/8t1RdAKwr9w?si=TLgumBNXv9H8szSn)
107
107
 
108
108
  ## Star History
109
109
 
@@ -20,7 +20,7 @@ The library supports various PDF form features, including:
20
20
  PyPDFForm aims to simplify PDF form manipulation, making it accessible to developers of all skill levels.
21
21
  """
22
22
 
23
- __version__ = "4.6.0"
23
+ __version__ = "4.6.2"
24
24
 
25
25
  from .annotations import Annotations
26
26
  from .assets.blank import BlankPage
@@ -11,6 +11,11 @@ Classes:
11
11
 
12
12
  from dataclasses import dataclass
13
13
 
14
+ from pypdf.generic import (ArrayObject, DictionaryObject, FloatObject,
15
+ NameObject, TextStringObject)
16
+
17
+ from ..constants import Annot, Contents, Rect, Subtype, Type
18
+
14
19
 
15
20
  @dataclass
16
21
  class Annotation:
@@ -41,12 +46,26 @@ class Annotation:
41
46
  """
42
47
  Gets properties specific to the annotation type.
43
48
 
44
- This method should be implemented by subclasses to return a dictionary
45
- containing PDF properties and their values that are unique to that
46
- specific type of annotation. These properties are used when creating
47
- the annotation's entry in the PDF document.
49
+ This method constructs the base dictionary containing PDF properties
50
+ and their values that are common to all types of annotations.
51
+ These properties are used when creating the annotation's entry in
52
+ the PDF document.
48
53
 
49
54
  Returns:
50
55
  dict: A dictionary of PDF properties specific to the annotation type.
51
56
  """
52
- raise NotImplementedError
57
+ return DictionaryObject(
58
+ {
59
+ NameObject(Type): NameObject(Annot),
60
+ NameObject(Subtype): NameObject(getattr(self, "_annotation_type")),
61
+ NameObject(Rect): ArrayObject(
62
+ [
63
+ FloatObject(self.x),
64
+ FloatObject(self.y),
65
+ FloatObject(self.x + self.width),
66
+ FloatObject(self.y + self.height),
67
+ ]
68
+ ),
69
+ NameObject(Contents): TextStringObject(self.contents),
70
+ }
71
+ )
@@ -37,15 +37,14 @@ class LinkAnnotation(Annotation):
37
37
 
38
38
  def get_specific_properties(self) -> dict:
39
39
  """
40
- Gets properties specific to the link annotation type.
40
+ Gets properties specific to the link annotation.
41
41
 
42
- This method returns a dictionary containing PDF properties and their
43
- values that are unique to a link annotation, perforated with the URI action.
42
+ This method extends the base properties with the URI action.
44
43
 
45
44
  Returns:
46
45
  dict: A dictionary of PDF properties specific to the link annotation.
47
46
  """
48
- result = {}
47
+ result = super().get_specific_properties()
49
48
  if self.uri is not None:
50
49
  result[NameObject(A)] = DictionaryObject(
51
50
  {
@@ -51,13 +51,13 @@ class TextAnnotation(Annotation):
51
51
  """
52
52
  Gets properties specific to the text annotation.
53
53
 
54
- This method includes the title (author) and the icon name for the
55
- text annotation if they are provided.
54
+ This method extends the base properties with the title (author)
55
+ and the icon name for the text annotation if they are provided.
56
56
 
57
57
  Returns:
58
58
  dict: A dictionary of PDF properties specific to the text annotation.
59
59
  """
60
- result = {}
60
+ result = super().get_specific_properties()
61
61
  if self.title is not None:
62
62
  result[NameObject(T)] = TextStringObject(self.title)
63
63
  if self.icon is not None:
@@ -32,26 +32,31 @@ class TextMarkupAnnotation(Annotation):
32
32
  """
33
33
  Gets properties specific to the text markup annotation.
34
34
 
35
- This method calculates the `QuadPoints` for the markup based on the
36
- annotation's position and dimensions.
35
+ This method extends the base properties with the `QuadPoints` for
36
+ the markup based on the annotation's position and dimensions.
37
37
 
38
38
  Returns:
39
- dict: A dictionary containing the `/QuadPoints` property.
39
+ dict: A dictionary of PDF properties specific to the text markup annotation.
40
40
  """
41
- return {
42
- NameObject("/QuadPoints"): ArrayObject(
43
- [
44
- FloatObject(self.x),
45
- FloatObject(self.y),
46
- FloatObject(self.x + self.width),
47
- FloatObject(self.y),
48
- FloatObject(self.x),
49
- FloatObject(self.y + self.height),
50
- FloatObject(self.x + self.width),
51
- FloatObject(self.y + self.height),
52
- ]
53
- )
54
- }
41
+ result = super().get_specific_properties()
42
+ result.update(
43
+ {
44
+ NameObject("/QuadPoints"): ArrayObject(
45
+ [
46
+ FloatObject(self.x),
47
+ FloatObject(self.y),
48
+ FloatObject(self.x + self.width),
49
+ FloatObject(self.y),
50
+ FloatObject(self.x),
51
+ FloatObject(self.y + self.height),
52
+ FloatObject(self.x + self.width),
53
+ FloatObject(self.y + self.height),
54
+ ]
55
+ ),
56
+ }
57
+ )
58
+
59
+ return result
55
60
 
56
61
 
57
62
  @dataclass
@@ -342,6 +342,74 @@ def flatten_field(annot: DictionaryObject, val: bool) -> None:
342
342
  _update_field_flag(annot, READ_ONLY, val)
343
343
 
344
344
 
345
+ def update_field_x(annot: DictionaryObject, val: float) -> None:
346
+ """
347
+ Updates the X-coordinate (horizontal position) of a form field annotation.
348
+
349
+ This function modifies the 'Rect' entry of the annotation dictionary to change
350
+ the starting X-coordinate of the field and adjusts the ending X-coordinate
351
+ accordingly to maintain the field's width.
352
+
353
+ Args:
354
+ annot (DictionaryObject): The annotation dictionary for the form field.
355
+ val (float): The new X-coordinate for the left edge of the field.
356
+ """
357
+ if isinstance(val, float):
358
+ diff = val - annot[Rect][0]
359
+ annot[NameObject(Rect)][0] = FloatObject(val)
360
+ annot[NameObject(Rect)][2] = FloatObject(annot[NameObject(Rect)][2] + diff)
361
+
362
+
363
+ def update_field_y(annot: DictionaryObject, val: float) -> None:
364
+ """
365
+ Updates the Y-coordinate (vertical position) of a form field annotation.
366
+
367
+ This function modifies the 'Rect' entry of the annotation dictionary to change
368
+ the starting Y-coordinate of the field and adjusts the ending Y-coordinate
369
+ accordingly to maintain the field's height.
370
+
371
+ Args:
372
+ annot (DictionaryObject): The annotation dictionary for the form field.
373
+ val (float): The new Y-coordinate for the bottom edge of the field.
374
+ """
375
+ if isinstance(val, float):
376
+ diff = val - annot[Rect][1]
377
+ annot[NameObject(Rect)][1] = FloatObject(val)
378
+ annot[NameObject(Rect)][3] = FloatObject(annot[NameObject(Rect)][3] + diff)
379
+
380
+
381
+ def update_field_width(annot: DictionaryObject, val: float) -> None:
382
+ """
383
+ Updates the width of a form field annotation.
384
+
385
+ This function modifies the 'Rect' entry of the annotation dictionary to set
386
+ the new width of the field, adjusting the rightmost coordinate while keeping
387
+ the leftmost coordinate fixed.
388
+
389
+ Args:
390
+ annot (DictionaryObject): The annotation dictionary for the form field.
391
+ val (float): The new width of the field.
392
+ """
393
+ if isinstance(val, float):
394
+ annot[NameObject(Rect)][2] = FloatObject(annot[Rect][0] + val)
395
+
396
+
397
+ def update_field_height(annot: DictionaryObject, val: float) -> None:
398
+ """
399
+ Updates the height of a form field annotation.
400
+
401
+ This function modifies the 'Rect' entry of the annotation dictionary to set
402
+ the new height of the field, adjusting the topmost coordinate while keeping
403
+ the bottommost coordinate fixed.
404
+
405
+ Args:
406
+ annot (DictionaryObject): The annotation dictionary for the form field.
407
+ val (float): The new height of the field.
408
+ """
409
+ if isinstance(val, float):
410
+ annot[NameObject(Rect)][3] = FloatObject(annot[Rect][1] + val)
411
+
412
+
345
413
  def update_field_tooltip(annot: DictionaryObject, val: str) -> None:
346
414
  """
347
415
  Updates the tooltip (alternate field name) of a form field annotation.
@@ -22,6 +22,10 @@ class Widget:
22
22
  """
23
23
 
24
24
  SET_ATTR_TRIGGER_HOOK_MAP = {
25
+ "x": "update_field_x",
26
+ "y": "update_field_y",
27
+ "width": "update_field_width",
28
+ "height": "update_field_height",
25
29
  "readonly": "flatten_field",
26
30
  "required": "update_field_required",
27
31
  "hidden": "update_field_hidden",
@@ -47,8 +51,11 @@ class Widget:
47
51
  value (Any): The initial value of the widget. Defaults to None.
48
52
  """
49
53
  super().__init__()
54
+ self.attr_set_tracker = {}
55
+
50
56
  self._name = name
51
57
  self._value = value
58
+
52
59
  self.tooltip: Optional[str] = None
53
60
  self.readonly: Optional[bool] = None
54
61
  self.required: Optional[bool] = None
@@ -74,7 +81,8 @@ class Widget:
74
81
  Set an attribute on the widget.
75
82
 
76
83
  This method overrides the default __setattr__ method to
77
- trigger hooks when certain attributes are set.
84
+ trigger hooks when certain attributes are set. It also
85
+ tracks the attributes that have been set.
78
86
 
79
87
  Args:
80
88
  name (str): The name of the attribute.
@@ -82,6 +90,14 @@ class Widget:
82
90
  """
83
91
  if name in self.SET_ATTR_TRIGGER_HOOK_MAP and value is not None:
84
92
  self.hooks_to_trigger.append((self.SET_ATTR_TRIGGER_HOOK_MAP[name], value))
93
+
94
+ if (
95
+ hasattr(self, "attr_set_tracker")
96
+ and name in self.__dict__
97
+ and value is not None
98
+ ):
99
+ self.attr_set_tracker[name] = value
100
+
85
101
  super().__setattr__(name, value)
86
102
 
87
103
  @property
@@ -13,12 +13,11 @@ from io import BytesIO
13
13
  from typing import Dict, List, cast
14
14
 
15
15
  from pypdf import PdfReader, PdfWriter
16
- from pypdf.generic import (ArrayObject, DictionaryObject, FloatObject,
17
- NameObject, TextStringObject)
16
+ from pypdf.generic import ArrayObject, DictionaryObject, NameObject
18
17
 
19
18
  from .annotations import AnnotationTypes
20
19
  from .constants import (COMB, MULTILINE, READ_ONLY, REQUIRED, WIDGET_TYPES,
21
- Annot, Annots, Contents, Rect, Subtype, Type)
20
+ Annots)
22
21
  from .middleware.checkbox import Checkbox
23
22
  from .middleware.dropdown import Dropdown
24
23
  from .middleware.radio import Radio
@@ -149,7 +148,12 @@ def _populate_common_properties(widget: dict, _widget: WIDGET_TYPES) -> None:
149
148
  _widget.__dict__["required"] = check_field_flag(widget, REQUIRED)
150
149
  _widget.__dict__["hidden"] = get_field_hidden(widget)
151
150
 
152
- _widget.x, _widget.y, _widget.width, _widget.height = get_field_rect(widget)
151
+ (
152
+ _widget.__dict__["x"],
153
+ _widget.__dict__["y"],
154
+ _widget.__dict__["width"],
155
+ _widget.__dict__["height"],
156
+ ) = get_field_rect(widget)
153
157
 
154
158
 
155
159
  def _populate_text_properties(widget: dict, _widget: Text) -> None:
@@ -196,10 +200,10 @@ def _handle_radio_widget(
196
200
  field_rect = get_field_rect(widget)
197
201
 
198
202
  if key not in results:
199
- _widget.x = []
200
- _widget.y = []
201
- _widget.width = []
202
- _widget.height = []
203
+ _widget.__dict__["x"] = []
204
+ _widget.__dict__["y"] = []
205
+ _widget.__dict__["width"] = []
206
+ _widget.__dict__["height"] = []
203
207
  results[key] = _widget
204
208
 
205
209
  radio = cast(Radio, results[key])
@@ -211,9 +215,9 @@ def _handle_radio_widget(
211
215
  if isinstance(radio.y, list):
212
216
  radio.y.append(field_rect[1])
213
217
  if isinstance(radio.width, list):
214
- radio.width.append(field_rect[2])
218
+ radio.__dict__["width"].append(field_rect[2])
215
219
  if isinstance(radio.height, list):
216
- radio.height.append(field_rect[3])
220
+ radio.__dict__["height"].append(field_rect[3])
217
221
 
218
222
  if get_radio_value(widget):
219
223
  radio.value = radio.number_of_options - 1
@@ -333,35 +337,6 @@ def _group_annotations_by_page(
333
337
  return result
334
338
 
335
339
 
336
- def _create_annotation_object(annotation: AnnotationTypes) -> DictionaryObject:
337
- """
338
- Creates a PDF dictionary object for an annotation.
339
-
340
- Args:
341
- annotation (AnnotationTypes): The annotation object to convert.
342
-
343
- Returns:
344
- DictionaryObject: The PDF dictionary object representing the annotation.
345
- """
346
- annot = DictionaryObject(
347
- {
348
- NameObject(Type): NameObject(Annot),
349
- NameObject(Subtype): NameObject(getattr(annotation, "_annotation_type")),
350
- NameObject(Rect): ArrayObject(
351
- [
352
- FloatObject(annotation.x),
353
- FloatObject(annotation.y),
354
- FloatObject(annotation.x + annotation.width),
355
- FloatObject(annotation.y + annotation.height),
356
- ]
357
- ),
358
- NameObject(Contents): TextStringObject(annotation.contents),
359
- }
360
- )
361
- annot.update(**annotation.get_specific_properties())
362
- return annot
363
-
364
-
365
340
  def create_annotations(
366
341
  template: bytes,
367
342
  annotations: List[AnnotationTypes],
@@ -393,7 +368,7 @@ def create_annotations(
393
368
 
394
369
  page_annotations = ArrayObject([])
395
370
  for annotation in annotations_by_page[page_num]:
396
- page_annotations.append(_create_annotation_object(annotation))
371
+ page_annotations.append(annotation.get_specific_properties())
397
372
 
398
373
  if Annots in page:
399
374
  page[NameObject(Annots)] += page_annotations
@@ -183,7 +183,6 @@ def merge_two_pdfs(pdf: bytes, other: bytes) -> bytes:
183
183
  output = PdfWriter()
184
184
  output.append(merged_no_widgets)
185
185
 
186
- # TODO: refactor duplicate logic with copy_watermark_widgets
187
186
  widgets_to_copy = {}
188
187
  for i, page in enumerate(pdf_file.pages):
189
188
  widgets_to_copy[i] = []
@@ -199,11 +199,9 @@ class PdfWrapper:
199
199
  # update key preserve old key attrs
200
200
  for k, v in new_widgets.items():
201
201
  if k in self._key_update_tracker:
202
- for name, value in self.widgets[
203
- self._key_update_tracker[k]
204
- ].__dict__.items():
205
- if not name.startswith("_"):
206
- setattr(v, name, value)
202
+ old_widget = self.widgets[self._key_update_tracker[k]]
203
+ for name in old_widget.attr_set_tracker:
204
+ setattr(v, name, getattr(old_widget, name, None))
207
205
  self._key_update_tracker = {}
208
206
 
209
207
  self.widgets = new_widgets
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 4.6.0
3
+ Version: 4.6.2
4
4
  Summary: The Python library for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -29,9 +29,8 @@ Requires-Dist: reportlab<5.0.0,>=4.4.6
29
29
  Provides-Extra: dev
30
30
  Requires-Dist: black<27.0.0,>=25.11.0; extra == "dev"
31
31
  Requires-Dist: coverage<8.0.0,>=7.12.0; extra == "dev"
32
- Requires-Dist: isort<8.0.0,>=7.0.0; extra == "dev"
32
+ Requires-Dist: isort<9.0.0,>=7.0.0; extra == "dev"
33
33
  Requires-Dist: jsonschema<5.0.0,>=4.25.1; extra == "dev"
34
- Requires-Dist: mkdocs<2.0.0,>=1.6.1; extra == "dev"
35
34
  Requires-Dist: mkdocs-material<10.0.0,>=9.7.0; extra == "dev"
36
35
  Requires-Dist: pudb<2026.0.0,>=2025.1.3; extra == "dev"
37
36
  Requires-Dist: pylint<5.0.0,>=4.0.3; extra == "dev"
@@ -48,7 +47,7 @@ Dynamic: license-file
48
47
  <p align="center">
49
48
  <a href="https://pypi.org/project/PyPDFForm/"><img src="https://img.shields.io/pypi/v/pypdfform?label=version&color=magenta"></a>
50
49
  <a href="https://chinapandaman.github.io/PyPDFForm/"><img src="https://img.shields.io/github/v/release/chinapandaman/pypdfform?label=docs&color=cyan"></a>
51
- <a href="https://codecov.io/gh/chinapandaman/PyPDFForm"><img src="https://img.shields.io/codecov/c/github/chinapandaman/pypdfform"/></a>
50
+ <a href="https://github.com/chinapandaman/PyPDFForm/actions/workflows/python-package.yml"><img src="https://img.shields.io/badge/coverage-100%25-green"></a>
52
51
  <a href="https://github.com/chinapandaman/PyPDFForm/raw/master/LICENSE"><img src="https://img.shields.io/github/license/chinapandaman/pypdfform?label=license&color=orange"></a>
53
52
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/pypi/pyversions/pypdfform?label=python&color=gold"></a>
54
53
  <a href="https://pepy.tech/projects/pypdfform"><img src="https://static.pepy.tech/badge/pypdfform/month"></a>
@@ -103,7 +102,8 @@ The official documentation can be found on [the GitHub page](https://chinapandam
103
102
 
104
103
  ## Other Resources
105
104
 
106
- [Chicago Python User Group - Dec 14, 2023](https://youtu.be/8t1RdAKwr9w?si=TLgumBNXv9H8szSn)
105
+ * [(WIP) Video Tutorials](https://youtube.com/playlist?list=PLNz_PBu1QA-gzYg5BvyOO98q15u5Xve1L&si=8MWasKEckBzY-NRQ)
106
+ * [Chicago Python User Group - Dec 14, 2023](https://youtu.be/8t1RdAKwr9w?si=TLgumBNXv9H8szSn)
107
107
 
108
108
  ## Star History
109
109
 
@@ -8,9 +8,8 @@ reportlab<5.0.0,>=4.4.6
8
8
  [dev]
9
9
  black<27.0.0,>=25.11.0
10
10
  coverage<8.0.0,>=7.12.0
11
- isort<8.0.0,>=7.0.0
11
+ isort<9.0.0,>=7.0.0
12
12
  jsonschema<5.0.0,>=4.25.1
13
- mkdocs<2.0.0,>=1.6.1
14
13
  mkdocs-material<10.0.0,>=9.7.0
15
14
  pudb<2026.0.0,>=2025.1.3
16
15
  pylint<5.0.0,>=4.0.3
@@ -5,7 +5,7 @@
5
5
  <p align="center">
6
6
  <a href="https://pypi.org/project/PyPDFForm/"><img src="https://img.shields.io/pypi/v/pypdfform?label=version&color=magenta"></a>
7
7
  <a href="https://chinapandaman.github.io/PyPDFForm/"><img src="https://img.shields.io/github/v/release/chinapandaman/pypdfform?label=docs&color=cyan"></a>
8
- <a href="https://codecov.io/gh/chinapandaman/PyPDFForm"><img src="https://img.shields.io/codecov/c/github/chinapandaman/pypdfform"/></a>
8
+ <a href="https://github.com/chinapandaman/PyPDFForm/actions/workflows/python-package.yml"><img src="https://img.shields.io/badge/coverage-100%25-green"></a>
9
9
  <a href="https://github.com/chinapandaman/PyPDFForm/raw/master/LICENSE"><img src="https://img.shields.io/github/license/chinapandaman/pypdfform?label=license&color=orange"></a>
10
10
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/pypi/pyversions/pypdfform?label=python&color=gold"></a>
11
11
  <a href="https://pepy.tech/projects/pypdfform"><img src="https://static.pepy.tech/badge/pypdfform/month"></a>
@@ -60,7 +60,8 @@ The official documentation can be found on [the GitHub page](https://chinapandam
60
60
 
61
61
  ## Other Resources
62
62
 
63
- [Chicago Python User Group - Dec 14, 2023](https://youtu.be/8t1RdAKwr9w?si=TLgumBNXv9H8szSn)
63
+ * [(WIP) Video Tutorials](https://youtube.com/playlist?list=PLNz_PBu1QA-gzYg5BvyOO98q15u5Xve1L&si=8MWasKEckBzY-NRQ)
64
+ * [Chicago Python User Group - Dec 14, 2023](https://youtu.be/8t1RdAKwr9w?si=TLgumBNXv9H8szSn)
64
65
 
65
66
  ## Star History
66
67
 
@@ -43,9 +43,8 @@ Documentation = "https://chinapandaman.github.io/PyPDFForm/"
43
43
  dev = [
44
44
  "black>=25.11.0,<27.0.0",
45
45
  "coverage>=7.12.0,<8.0.0",
46
- "isort>=7.0.0,<8.0.0",
46
+ "isort>=7.0.0,<9.0.0",
47
47
  "jsonschema>=4.25.1,<5.0.0",
48
- "mkdocs>=1.6.1,<2.0.0",
49
48
  "mkdocs-material>=9.7.0,<10.0.0",
50
49
  "pudb>=2025.1.3,<2026.0.0",
51
50
  "pylint>=4.0.3,<5.0.0",
@@ -7,7 +7,6 @@ import pytest
7
7
  from jsonschema import ValidationError, validate
8
8
 
9
9
  from PyPDFForm import Annotations, BlankPage, Fields, PdfArray, PdfWrapper
10
- from PyPDFForm.annotations.base import Annotation
11
10
  from PyPDFForm.constants import DA, UNIQUE_SUFFIX_LENGTH, T, V
12
11
  from PyPDFForm.deprecation import deprecation_notice
13
12
  from PyPDFForm.middleware.base import Widget
@@ -789,17 +788,6 @@ def test_merge(template_stream):
789
788
  assert field[V] == f"test_3_{int(page / 3 - 0.5)}"
790
789
 
791
790
 
792
- def test_base_annotation_get_specific_properties_not_implemented():
793
- annotation = Annotation(1, 100, 100)
794
- try:
795
- annotation.get_specific_properties()
796
- pytest.fail(
797
- reason="base annotation shouldn't have get_specific_properties implemented."
798
- )
799
- except NotImplementedError:
800
- pass
801
-
802
-
803
791
  def test_annotate(template_stream, pdf_samples, request):
804
792
  expected_path = os.path.join(pdf_samples, "test_annotate.pdf")
805
793
  with open(expected_path, "rb+") as f:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes