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.
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PKG-INFO +5 -5
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/__init__.py +1 -1
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/annotations/base.py +24 -5
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/annotations/link.py +3 -4
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/annotations/text.py +3 -3
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/annotations/text_markup.py +22 -17
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/hooks.py +68 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/base.py +17 -1
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/template.py +15 -40
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/utils.py +0 -1
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/wrapper.py +3 -5
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm.egg-info/PKG-INFO +5 -5
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm.egg-info/requires.txt +1 -2
- {pypdfform-4.6.0 → pypdfform-4.6.2}/README.md +3 -2
- {pypdfform-4.6.0 → pypdfform-4.6.2}/pyproject.toml +1 -2
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_functional.py +0 -12
- {pypdfform-4.6.0 → pypdfform-4.6.2}/LICENSE +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/adapter.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/annotations/__init__.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/ap.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/assets/__init__.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/assets/bedrock.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/assets/blank.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/constants.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/coordinate.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/deprecation.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/filler.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/font.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/image.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/__init__.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/checkbox.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/dropdown.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/image.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/radio.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/signature.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/middleware/text.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/patterns.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/__init__.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/circle.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/ellipse.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/image.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/line.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/rect.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/raw/text.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/types.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/watermark.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/__init__.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/base.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/checkbox.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/dropdown.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/image.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/radio.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/signature.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm/widgets/text.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm.egg-info/SOURCES.txt +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/setup.cfg +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_bulk_create_fields.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_create_widget.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_draw_elements.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_dropdown.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_extract_middleware_attributes.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_font_widths.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_generate_appearance_streams.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_js.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_need_appearances.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_paragraph.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_signature.py +0 -0
- {pypdfform-4.6.0 → pypdfform-4.6.2}/tests/test_use_full_widget_name.py +0 -0
- {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.
|
|
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<
|
|
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://
|
|
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
|
-
[
|
|
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.
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
the
|
|
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
|
-
|
|
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
|
|
40
|
+
Gets properties specific to the link annotation.
|
|
41
41
|
|
|
42
|
-
This method
|
|
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
|
|
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
|
|
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
|
|
39
|
+
dict: A dictionary of PDF properties specific to the text markup annotation.
|
|
40
40
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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.
|
|
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<
|
|
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://
|
|
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
|
-
[
|
|
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<
|
|
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://
|
|
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
|
-
[
|
|
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,<
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|