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.
Files changed (88) hide show
  1. {pypdfform-5.5.2 → pypdfform-5.5.4}/PKG-INFO +1 -1
  2. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/__init__.py +1 -1
  3. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/constants.py +13 -1
  4. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/egress.py +3 -3
  5. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/font.py +4 -5
  6. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/image.py +3 -4
  7. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/patterns.py +2 -2
  8. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/template.py +3 -6
  9. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/utils.py +0 -2
  10. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/watermark.py +1 -1
  11. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/base.py +26 -12
  12. pypdfform-5.5.4/PyPDFForm/lib/widgets/image.py +171 -0
  13. pypdfform-5.5.4/PyPDFForm/lib/widgets/signature.py +351 -0
  14. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/wrapper.py +9 -17
  15. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/PKG-INFO +1 -1
  16. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/SOURCES.txt +0 -1
  17. pypdfform-5.5.2/PyPDFForm/lib/assets/bedrock.py +0 -4
  18. pypdfform-5.5.2/PyPDFForm/lib/widgets/image.py +0 -50
  19. pypdfform-5.5.2/PyPDFForm/lib/widgets/signature.py +0 -201
  20. {pypdfform-5.5.2 → pypdfform-5.5.4}/LICENSE +0 -0
  21. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/__init__.py +0 -0
  22. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/common.py +0 -0
  23. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/create.py +0 -0
  24. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/entry.py +0 -0
  25. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/inspect.py +0 -0
  26. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/remove.py +0 -0
  27. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/root.py +0 -0
  28. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/schemas/__init__.py +0 -0
  29. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/schemas/create.py +0 -0
  30. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/schemas/update.py +0 -0
  31. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/cli/update.py +0 -0
  32. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/__init__.py +0 -0
  33. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/adapter.py +0 -0
  34. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/__init__.py +0 -0
  35. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/base.py +0 -0
  36. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/link.py +0 -0
  37. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/stamp.py +0 -0
  38. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/text.py +0 -0
  39. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/annotations/text_markup.py +0 -0
  40. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/assets/__init__.py +0 -0
  41. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/assets/blank.py +0 -0
  42. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/coordinate.py +0 -0
  43. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/deprecation.py +0 -0
  44. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/filler.py +0 -0
  45. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/hooks.py +0 -0
  46. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/__init__.py +0 -0
  47. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/base.py +0 -0
  48. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/checkbox.py +0 -0
  49. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/dropdown.py +0 -0
  50. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/image.py +0 -0
  51. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/radio.py +0 -0
  52. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/signature.py +0 -0
  53. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/middleware/text.py +0 -0
  54. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/__init__.py +0 -0
  55. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/circle.py +0 -0
  56. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/ellipse.py +0 -0
  57. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/image.py +0 -0
  58. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/line.py +0 -0
  59. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/rect.py +0 -0
  60. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/raw/text.py +0 -0
  61. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/types.py +0 -0
  62. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/__init__.py +0 -0
  63. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/checkbox.py +0 -0
  64. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/dropdown.py +0 -0
  65. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/radio.py +0 -0
  66. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm/lib/widgets/text.py +0 -0
  67. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/dependency_links.txt +0 -0
  68. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/entry_points.txt +0 -0
  69. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/requires.txt +0 -0
  70. {pypdfform-5.5.2 → pypdfform-5.5.4}/PyPDFForm.egg-info/top_level.txt +0 -0
  71. {pypdfform-5.5.2 → pypdfform-5.5.4}/README.md +0 -0
  72. {pypdfform-5.5.2 → pypdfform-5.5.4}/pyproject.toml +0 -0
  73. {pypdfform-5.5.2 → pypdfform-5.5.4}/setup.cfg +0 -0
  74. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_bulk_create_fields.py +0 -0
  75. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_create_widget.py +0 -0
  76. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_draw_elements.py +0 -0
  77. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_dropdown.py +0 -0
  78. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_extract_middleware_attributes.py +0 -0
  79. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_fill_max_length_text_field.py +0 -0
  80. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_font_widths.py +0 -0
  81. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_functional.py +0 -0
  82. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_generate_appearance_streams.py +0 -0
  83. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_js.py +0 -0
  84. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_need_appearances.py +0 -0
  85. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_paragraph.py +0 -0
  86. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_signature.py +0 -0
  87. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_use_full_widget_name.py +0 -0
  88. {pypdfform-5.5.2 → pypdfform-5.5.4}/tests/test_widget_attr_trigger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPDFForm
3
- Version: 5.5.2
3
+ Version: 5.5.4
4
4
  Summary: The Python library & CLI for PDF forms.
5
5
  Author: Jinge Li
6
6
  License-Expression: MIT
@@ -18,7 +18,7 @@ from Python code or from the command line.
18
18
 
19
19
  import logging
20
20
 
21
- __version__ = "5.5.2"
21
+ __version__ = "5.5.4"
22
22
 
23
23
  from .lib.annotations import Annotations
24
24
  from .lib.assets.blank import BlankPage
@@ -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
- IMAGE_FIELD_IDENTIFIER = "event.target.buttonImportIcon();"
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=128)
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
- # if not seen_fields:
123
- # return pdf
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=128)
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=128)
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=128)
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=128)
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=128)
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 cached function uses the PIL library to open the image from the provided
58
- byte stream and returns its dimensions (width and height) as a tuple of floats.
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
- IMAGE_FIELD_IDENTIFIER,
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: IMAGE_FIELD_IDENTIFIER}},
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
- Results are cached by PDF stream. Only a document-catalog `/OpenAction`
94
- whose action type is JavaScript is returned.
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=128)
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=128)
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=128)
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 = BytesIO()
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