PyPDFForm 5.5.2__tar.gz → 5.5.4__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-5.5.2 → pypdfform-5.5.4}/PKG-INFO +1 -1
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/__init__.py +1 -1
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/constants.py +13 -1
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/egress.py +3 -3
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/font.py +4 -5
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/image.py +3 -4
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/patterns.py +2 -2
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/template.py +3 -6
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/utils.py +0 -2
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/watermark.py +1 -1
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/base.py +26 -12
- pypdfform-5.5.4/PyPDFForm/lib/widgets/image.py +171 -0
- pypdfform-5.5.4/PyPDFForm/lib/widgets/signature.py +351 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/wrapper.py +9 -17
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/PKG-INFO +1 -1
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/SOURCES.txt +0 -1
- pypdfform-5.5.2/PyPDFForm/lib/assets/bedrock.py +0 -4
- pypdfform-5.5.2/PyPDFForm/lib/widgets/image.py +0 -50
- pypdfform-5.5.2/PyPDFForm/lib/widgets/signature.py +0 -201
- {pypdfform-5.5.2 → pypdfform-5.5.4}/LICENSE +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/__init__.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/common.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/create.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/entry.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/inspect.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/remove.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/root.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/schemas/__init__.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/schemas/create.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/schemas/update.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/update.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/__init__.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/adapter.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/__init__.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/base.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/link.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/stamp.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/text.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/text_markup.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/assets/__init__.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/assets/blank.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/coordinate.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/deprecation.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/filler.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/hooks.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/__init__.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/base.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/checkbox.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/dropdown.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/image.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/radio.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/signature.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/text.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/__init__.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/circle.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/ellipse.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/image.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/line.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/rect.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/text.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/types.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/__init__.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/checkbox.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/dropdown.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/radio.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/text.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/entry_points.txt +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/requires.txt +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/README.md +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/pyproject.toml +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/setup.cfg +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_bulk_create_fields.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_create_widget.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_draw_elements.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_dropdown.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_extract_middleware_attributes.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_font_widths.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_functional.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_generate_appearance_streams.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_js.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_need_appearances.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_paragraph.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_signature.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_use_full_widget_name.py +0 -0
- {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_widget_attr_trigger.py +0 -0
|
@@ -47,6 +47,10 @@ Annots = "/Annots"
|
|
|
47
47
|
Annot = "/Annot"
|
|
48
48
|
Contents = "/Contents"
|
|
49
49
|
Widget = "/Widget"
|
|
50
|
+
XObject = "/XObject"
|
|
51
|
+
Form = "/Form"
|
|
52
|
+
BBox = "/BBox"
|
|
53
|
+
Matrix = "/Matrix"
|
|
50
54
|
T = "/T"
|
|
51
55
|
TU = "/TU"
|
|
52
56
|
Rect = "/Rect"
|
|
@@ -59,6 +63,7 @@ V = "/V"
|
|
|
59
63
|
AP = "/AP"
|
|
60
64
|
I = "/I" # noqa: E741
|
|
61
65
|
N = "/N"
|
|
66
|
+
R = "/R"
|
|
62
67
|
Sig = "/Sig"
|
|
63
68
|
DA = "/DA"
|
|
64
69
|
DR = "/DR"
|
|
@@ -71,6 +76,13 @@ Opt = "/Opt"
|
|
|
71
76
|
AS = "/AS"
|
|
72
77
|
Yes = "/Yes"
|
|
73
78
|
Off = "/Off"
|
|
79
|
+
MK = "/MK"
|
|
80
|
+
BC = "/BC"
|
|
81
|
+
BS = "/BS"
|
|
82
|
+
W = "/W"
|
|
83
|
+
H = "/H"
|
|
84
|
+
TP = "/TP"
|
|
85
|
+
IF = "/IF"
|
|
74
86
|
|
|
75
87
|
# javascript
|
|
76
88
|
A = "/A"
|
|
@@ -144,7 +156,7 @@ DEFAULT_FONT = "Helvetica"
|
|
|
144
156
|
DEFAULT_FONT_SIZE = 12
|
|
145
157
|
DEFAULT_FONT_COLOR = (0, 0, 0)
|
|
146
158
|
|
|
147
|
-
|
|
159
|
+
IMAGE_IMPORT_JAVASCRIPT = "event.target.buttonImportIcon();"
|
|
148
160
|
|
|
149
161
|
COORDINATE_GRID_FONT_SIZE_MARGIN_RATIO = DEFAULT_FONT_SIZE / 100
|
|
150
162
|
UNIQUE_SUFFIX_LENGTH = 20
|
|
@@ -28,7 +28,7 @@ from .constants import (
|
|
|
28
28
|
from .template import get_widget_key
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
@lru_cache(maxsize=
|
|
31
|
+
@lru_cache(maxsize=8)
|
|
32
32
|
def appearance_streams_handler(pdf: bytes, generate_appearance_streams: bool) -> bytes:
|
|
33
33
|
"""
|
|
34
34
|
Handles appearance streams and the /NeedAppearances flag for a PDF form.
|
|
@@ -119,8 +119,8 @@ def rebuild_acroform_fields(
|
|
|
119
119
|
fields.append(field_ref)
|
|
120
120
|
seen_fields.add(field_key)
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
if not seen_fields:
|
|
123
|
+
return pdf
|
|
124
124
|
|
|
125
125
|
if AcroForm not in root:
|
|
126
126
|
root[NameObject(AcroForm)] = DictionaryObject({})
|
|
@@ -66,7 +66,7 @@ from .raw.text import RawText
|
|
|
66
66
|
from .watermark import create_watermarks_and_draw
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
@lru_cache(maxsize=
|
|
69
|
+
@lru_cache(maxsize=8)
|
|
70
70
|
def validate_font(font_name: str, ttf_stream: bytes) -> bool:
|
|
71
71
|
"""
|
|
72
72
|
Validates a TrueType font stream.
|
|
@@ -212,7 +212,7 @@ def temporary_font_registration(
|
|
|
212
212
|
del _fonts[rl_name]
|
|
213
213
|
|
|
214
214
|
|
|
215
|
-
@lru_cache(maxsize=
|
|
215
|
+
@lru_cache(maxsize=8)
|
|
216
216
|
def _get_watermark_with_font(ttf_stream: bytes) -> bytes:
|
|
217
217
|
"""
|
|
218
218
|
Creates a watermark PDF with a single space character using the specified font.
|
|
@@ -235,7 +235,7 @@ def _get_watermark_with_font(ttf_stream: bytes) -> bytes:
|
|
|
235
235
|
)[0]
|
|
236
236
|
|
|
237
237
|
|
|
238
|
-
@lru_cache(maxsize=
|
|
238
|
+
@lru_cache(maxsize=8)
|
|
239
239
|
def _compress_ttf(ttf_stream: bytes) -> bytes:
|
|
240
240
|
"""
|
|
241
241
|
Compresses a TrueType font stream for embedding in a PDF.
|
|
@@ -361,7 +361,7 @@ def register_font_acroform(
|
|
|
361
361
|
return f.read(), new_font_name
|
|
362
362
|
|
|
363
363
|
|
|
364
|
-
@lru_cache(maxsize=
|
|
364
|
+
@lru_cache(maxsize=8)
|
|
365
365
|
def _get_base_font_name(ttf_stream: bytes) -> str:
|
|
366
366
|
"""
|
|
367
367
|
Extracts the base font name from a TrueType font stream.
|
|
@@ -402,7 +402,6 @@ def _get_new_font_name(fonts: dict) -> str:
|
|
|
402
402
|
return f"{FONT_NAME_PREFIX}{n}"
|
|
403
403
|
|
|
404
404
|
|
|
405
|
-
@lru_cache(maxsize=128)
|
|
406
405
|
def get_all_available_fonts(pdf: bytes) -> dict:
|
|
407
406
|
"""
|
|
408
407
|
Retrieves all available fonts from a PDF document's AcroForm.
|
|
@@ -16,7 +16,7 @@ from PIL import Image
|
|
|
16
16
|
from .constants import Rect
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
@lru_cache(maxsize=
|
|
19
|
+
@lru_cache(maxsize=8)
|
|
20
20
|
def rotate_image(image_stream: bytes, rotation: float | int) -> bytes:
|
|
21
21
|
"""
|
|
22
22
|
Rotates an image by a specified angle in degrees.
|
|
@@ -49,13 +49,12 @@ def rotate_image(image_stream: bytes, rotation: float | int) -> bytes:
|
|
|
49
49
|
return result
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
@lru_cache(maxsize=128)
|
|
53
52
|
def get_image_dimensions(image_stream: bytes) -> Tuple[float, float]:
|
|
54
53
|
"""
|
|
55
54
|
Retrieves the width and height of an image from its byte stream.
|
|
56
55
|
|
|
57
|
-
This
|
|
58
|
-
|
|
56
|
+
This function uses the PIL library to open the image from the provided byte
|
|
57
|
+
stream and returns its dimensions (width and height) as a tuple of floats.
|
|
59
58
|
|
|
60
59
|
Args:
|
|
61
60
|
image_stream (bytes): The image data as bytes.
|
|
@@ -24,7 +24,7 @@ from .constants import (
|
|
|
24
24
|
DV,
|
|
25
25
|
FT,
|
|
26
26
|
HIDDEN,
|
|
27
|
-
|
|
27
|
+
IMAGE_IMPORT_JAVASCRIPT,
|
|
28
28
|
JS,
|
|
29
29
|
SLASH,
|
|
30
30
|
TU,
|
|
@@ -61,7 +61,7 @@ WIDGET_TYPE_PATTERNS = [
|
|
|
61
61
|
(
|
|
62
62
|
(
|
|
63
63
|
{Subtype: Widget},
|
|
64
|
-
{A: {JS:
|
|
64
|
+
{A: {JS: IMAGE_IMPORT_JAVASCRIPT}},
|
|
65
65
|
),
|
|
66
66
|
Image,
|
|
67
67
|
),
|
|
@@ -53,7 +53,6 @@ from .patterns import (
|
|
|
53
53
|
from .utils import extract_widget_property, find_pattern_match
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
@lru_cache(maxsize=128)
|
|
57
56
|
def get_metadata(pdf: bytes) -> dict:
|
|
58
57
|
"""
|
|
59
58
|
Retrieves the metadata of a PDF.
|
|
@@ -85,13 +84,12 @@ def get_title(pdf: bytes) -> str | None:
|
|
|
85
84
|
return get_metadata(pdf).get(Title)
|
|
86
85
|
|
|
87
86
|
|
|
88
|
-
@lru_cache(maxsize=128)
|
|
89
87
|
def get_on_open_javascript(pdf: bytes) -> str | None:
|
|
90
88
|
"""
|
|
91
89
|
Retrieves the JavaScript configured to run when a PDF is opened.
|
|
92
90
|
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
Only a document-catalog `/OpenAction` whose action type is JavaScript is
|
|
92
|
+
returned.
|
|
95
93
|
|
|
96
94
|
Args:
|
|
97
95
|
pdf (bytes): The PDF stream to inspect for a document-open action.
|
|
@@ -201,7 +199,7 @@ def build_widgets(
|
|
|
201
199
|
return deepcopy(_build_widget_cache(pdf_stream, use_full_widget_name))
|
|
202
200
|
|
|
203
201
|
|
|
204
|
-
@lru_cache(maxsize=
|
|
202
|
+
@lru_cache(maxsize=8)
|
|
205
203
|
def _build_widget_cache(
|
|
206
204
|
pdf_stream: bytes,
|
|
207
205
|
use_full_widget_name: bool,
|
|
@@ -379,7 +377,6 @@ def _handle_radio_widget(
|
|
|
379
377
|
radio.value = radio.number_of_options - 1
|
|
380
378
|
|
|
381
379
|
|
|
382
|
-
@lru_cache(maxsize=128)
|
|
383
380
|
def get_widgets_by_page(pdf: bytes) -> Dict[int, List[dict]]:
|
|
384
381
|
"""
|
|
385
382
|
Retrieves widgets from a PDF stream, organized by page number.
|
|
@@ -12,7 +12,6 @@ It includes functions for:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
from collections.abc import Callable
|
|
15
|
-
from functools import lru_cache
|
|
16
15
|
from io import BytesIO
|
|
17
16
|
from secrets import choice
|
|
18
17
|
from string import ascii_letters, digits, punctuation
|
|
@@ -30,7 +29,6 @@ from .constants import (
|
|
|
30
29
|
)
|
|
31
30
|
|
|
32
31
|
|
|
33
|
-
@lru_cache(maxsize=128)
|
|
34
32
|
def remove_all_widgets(pdf: bytes) -> bytes:
|
|
35
33
|
"""
|
|
36
34
|
Removes all widgets (form fields) from a PDF, effectively flattening the form.
|
|
@@ -22,7 +22,7 @@ from .constants import Annots
|
|
|
22
22
|
from .patterns import get_widget_key
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
@lru_cache(maxsize=
|
|
25
|
+
@lru_cache(maxsize=8)
|
|
26
26
|
def _get_image_reader(image_stream: bytes) -> ImageReader:
|
|
27
27
|
"""
|
|
28
28
|
Creates a cached ReportLab image reader for an image byte stream.
|
|
@@ -20,7 +20,7 @@ from inspect import signature
|
|
|
20
20
|
from io import BytesIO
|
|
21
21
|
from typing import List, Optional
|
|
22
22
|
|
|
23
|
-
from pypdf import PdfReader
|
|
23
|
+
from pypdf import PageObject, PdfReader
|
|
24
24
|
from reportlab.lib.colors import Color
|
|
25
25
|
from reportlab.pdfgen.canvas import Canvas
|
|
26
26
|
|
|
@@ -107,7 +107,7 @@ class Widget:
|
|
|
107
107
|
self.hook_params.append((each, kwargs.get(each)))
|
|
108
108
|
|
|
109
109
|
@staticmethod
|
|
110
|
-
@lru_cache(maxsize=
|
|
110
|
+
@lru_cache(maxsize=8)
|
|
111
111
|
def _get_default_field_flags(acro_form_class: type, acro_form_func: str) -> tuple:
|
|
112
112
|
"""
|
|
113
113
|
Retrieves the default field flags for a ReportLab AcroForm method.
|
|
@@ -172,6 +172,28 @@ class Widget:
|
|
|
172
172
|
"""
|
|
173
173
|
getattr(canvas.acroForm, self.ACRO_FORM_FUNC)(**self.acro_form_params)
|
|
174
174
|
|
|
175
|
+
@staticmethod
|
|
176
|
+
def create_watermark_canvas(page: PageObject) -> tuple[BytesIO, Canvas]:
|
|
177
|
+
"""
|
|
178
|
+
Creates a PDF canvas sized to a source page.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
page (PageObject): The source PDF page whose media box defines the
|
|
182
|
+
canvas size.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
tuple[BytesIO, Canvas]: The in-memory stream and its PDF canvas.
|
|
186
|
+
"""
|
|
187
|
+
watermark = BytesIO()
|
|
188
|
+
canvas = Canvas(
|
|
189
|
+
watermark,
|
|
190
|
+
pagesize=(
|
|
191
|
+
float(page.mediabox[2]),
|
|
192
|
+
float(page.mediabox[3]),
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
return watermark, canvas
|
|
196
|
+
|
|
175
197
|
@staticmethod
|
|
176
198
|
def bulk_watermarks(widgets: List[Widget], stream: bytes) -> List[bytes]:
|
|
177
199
|
"""
|
|
@@ -209,18 +231,10 @@ class Widget:
|
|
|
209
231
|
if page_num not in widgets_by_page:
|
|
210
232
|
continue
|
|
211
233
|
|
|
234
|
+
page = pdf.pages[page_num - 1]
|
|
212
235
|
# Use a fresh buffer per page to avoid stale trailing bytes
|
|
213
236
|
# when the current page watermark is smaller than a previous page.
|
|
214
|
-
watermark =
|
|
215
|
-
page = pdf.pages[page_num - 1]
|
|
216
|
-
|
|
217
|
-
canvas = Canvas(
|
|
218
|
-
watermark,
|
|
219
|
-
pagesize=(
|
|
220
|
-
float(page.mediabox[2]),
|
|
221
|
-
float(page.mediabox[3]),
|
|
222
|
-
),
|
|
223
|
-
)
|
|
237
|
+
watermark, canvas = Widget.create_watermark_canvas(page)
|
|
224
238
|
|
|
225
239
|
for widget in widgets_by_page[page_num]:
|
|
226
240
|
getattr(widget, "_required_handler")(canvas)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
This module defines the `ImageField` and `ImageWidget` classes, which are used
|
|
4
|
+
to describe and construct image-import form fields.
|
|
5
|
+
|
|
6
|
+
`ImageField` inherits the signature field's sizing properties. `ImageWidget`
|
|
7
|
+
reuses the signature widget's placement and carrier-PDF infrastructure while
|
|
8
|
+
constructing a push-button annotation with an Acrobat JavaScript image-import
|
|
9
|
+
action.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Type
|
|
14
|
+
|
|
15
|
+
from pypdf import PdfWriter
|
|
16
|
+
from pypdf.generic import (
|
|
17
|
+
ArrayObject,
|
|
18
|
+
DictionaryObject,
|
|
19
|
+
FloatObject,
|
|
20
|
+
IndirectObject,
|
|
21
|
+
NameObject,
|
|
22
|
+
NumberObject,
|
|
23
|
+
TextStringObject,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from ..constants import (
|
|
27
|
+
AP,
|
|
28
|
+
BC,
|
|
29
|
+
BS,
|
|
30
|
+
DA,
|
|
31
|
+
FT,
|
|
32
|
+
IF,
|
|
33
|
+
IMAGE_IMPORT_JAVASCRIPT,
|
|
34
|
+
JS,
|
|
35
|
+
MK,
|
|
36
|
+
TP,
|
|
37
|
+
A,
|
|
38
|
+
Action,
|
|
39
|
+
Annot,
|
|
40
|
+
Btn,
|
|
41
|
+
D,
|
|
42
|
+
F,
|
|
43
|
+
Ff,
|
|
44
|
+
JavaScript,
|
|
45
|
+
Matrix,
|
|
46
|
+
N,
|
|
47
|
+
R,
|
|
48
|
+
Rect,
|
|
49
|
+
S,
|
|
50
|
+
Subtype,
|
|
51
|
+
T,
|
|
52
|
+
W,
|
|
53
|
+
Widget,
|
|
54
|
+
)
|
|
55
|
+
from ..constants import Type as PdfType
|
|
56
|
+
from .signature import SignatureField, SignatureWidget
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ImageWidget(SignatureWidget):
|
|
60
|
+
"""
|
|
61
|
+
Represents an image widget in a PDF form.
|
|
62
|
+
|
|
63
|
+
The widget inherits signature-field placement, dimensions, and deferred
|
|
64
|
+
hooks, but constructs a push-button annotation identified by its
|
|
65
|
+
`buttonImportIcon()` JavaScript action.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _build_annotation(out: PdfWriter, widget: SignatureWidget) -> IndirectObject:
|
|
70
|
+
"""
|
|
71
|
+
Constructs an image-import widget annotation owned by a PDF writer.
|
|
72
|
+
|
|
73
|
+
This method creates a border-only Form XObject, reuses it for the normal,
|
|
74
|
+
rollover, and pressed appearances, and builds a push-button `/Btn` widget
|
|
75
|
+
whose JavaScript action invokes `buttonImportIcon()`. It registers the
|
|
76
|
+
appearance and annotation with the same writer and implements the
|
|
77
|
+
annotation-builder callback used by `build_widget_watermarks`.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
out (PdfWriter): The writer that will own the appearance stream and
|
|
81
|
+
widget annotation.
|
|
82
|
+
widget (SignatureWidget): The normalized image widget definition to
|
|
83
|
+
convert into a PDF annotation.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
IndirectObject: The writer-owned indirect reference to the widget
|
|
87
|
+
annotation.
|
|
88
|
+
"""
|
|
89
|
+
width = float(widget.optional_parameters["width"])
|
|
90
|
+
height = float(widget.optional_parameters["height"])
|
|
91
|
+
border_color = (0.1, 0.1, 0.1)
|
|
92
|
+
|
|
93
|
+
appearance_stream = ImageWidget._build_border_appearance(
|
|
94
|
+
width, height, border_color
|
|
95
|
+
)
|
|
96
|
+
appearance_stream[NameObject(Matrix)] = ArrayObject(
|
|
97
|
+
[
|
|
98
|
+
FloatObject(1),
|
|
99
|
+
FloatObject(0),
|
|
100
|
+
FloatObject(0),
|
|
101
|
+
FloatObject(1),
|
|
102
|
+
FloatObject(0),
|
|
103
|
+
FloatObject(0),
|
|
104
|
+
]
|
|
105
|
+
)
|
|
106
|
+
appearance = out._add_object( # type: ignore # noqa: SLF001 # pylint: disable=W0212
|
|
107
|
+
appearance_stream.flate_encode()
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
annotation = DictionaryObject(
|
|
111
|
+
{
|
|
112
|
+
NameObject(FT): NameObject(Btn),
|
|
113
|
+
NameObject(Ff): NumberObject(1 << 16),
|
|
114
|
+
NameObject(PdfType): NameObject(Annot),
|
|
115
|
+
NameObject(Subtype): NameObject(Widget),
|
|
116
|
+
NameObject(F): NumberObject(4),
|
|
117
|
+
NameObject(MK): DictionaryObject(
|
|
118
|
+
{
|
|
119
|
+
NameObject(TP): NumberObject(1),
|
|
120
|
+
NameObject(IF): DictionaryObject(
|
|
121
|
+
{NameObject(S): NameObject(A)}
|
|
122
|
+
),
|
|
123
|
+
NameObject(BC): ArrayObject(
|
|
124
|
+
FloatObject(value) for value in border_color
|
|
125
|
+
),
|
|
126
|
+
}
|
|
127
|
+
),
|
|
128
|
+
NameObject(BS): DictionaryObject(
|
|
129
|
+
{
|
|
130
|
+
NameObject(S): NameObject(S),
|
|
131
|
+
NameObject(W): NumberObject(1),
|
|
132
|
+
}
|
|
133
|
+
),
|
|
134
|
+
NameObject(A): DictionaryObject(
|
|
135
|
+
{
|
|
136
|
+
NameObject(PdfType): NameObject(Action),
|
|
137
|
+
NameObject(S): NameObject(JavaScript),
|
|
138
|
+
NameObject(JS): TextStringObject(IMAGE_IMPORT_JAVASCRIPT),
|
|
139
|
+
}
|
|
140
|
+
),
|
|
141
|
+
NameObject(DA): TextStringObject("/Micr 12 Tf 0 0 0 rg"),
|
|
142
|
+
NameObject(Rect): ImageWidget._build_rectangle(widget, width, height),
|
|
143
|
+
NameObject(AP): DictionaryObject(
|
|
144
|
+
{
|
|
145
|
+
NameObject(N): appearance,
|
|
146
|
+
NameObject(R): appearance,
|
|
147
|
+
NameObject(D): appearance,
|
|
148
|
+
}
|
|
149
|
+
),
|
|
150
|
+
NameObject(T): TextStringObject(widget.name),
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
return out._add_object( # type: ignore # noqa: SLF001 # pylint: disable=W0212
|
|
154
|
+
annotation
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass
|
|
159
|
+
class ImageField(SignatureField):
|
|
160
|
+
"""
|
|
161
|
+
Represents an image field in a PDF document.
|
|
162
|
+
|
|
163
|
+
This dataclass extends `SignatureField` and selects `ImageWidget` as its
|
|
164
|
+
widget implementation. It inherits the optional width and height values;
|
|
165
|
+
when omitted, the widget resolves them to 160 and 90 points.
|
|
166
|
+
|
|
167
|
+
Attributes:
|
|
168
|
+
_widget_class (Type[ImageWidget]): The widget class associated with this field type.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
_widget_class: Type[ImageWidget] = ImageWidget
|