PyPDFForm 6.0.2__tar.gz → 6.1.1__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-6.0.2 → pypdfform-6.1.1}/PKG-INFO +2 -2
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/__init__.py +2 -3
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/cli/common.py +16 -2
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/cli/create.py +8 -2
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/cli/update.py +18 -7
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/adapter.py +17 -9
- pypdfform-6.1.1/PyPDFForm/lib/annotations/link.py +98 -0
- pypdfform-6.1.1/PyPDFForm/lib/clone.py +643 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/constants.py +27 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/deprecation.py +47 -2
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/middleware/base.py +53 -1
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/middleware/checkbox.py +2 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/middleware/dropdown.py +2 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/middleware/text.py +12 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/raw/image.py +4 -3
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/template.py +51 -8
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/utils.py +108 -26
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/watermark.py +52 -83
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/wrapper.py +154 -26
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm.egg-info/PKG-INFO +2 -2
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm.egg-info/SOURCES.txt +1 -1
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm.egg-info/requires.txt +1 -1
- {pypdfform-6.0.2 → pypdfform-6.1.1}/pyproject.toml +2 -1
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_functional.py +894 -4
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_signature.py +14 -3
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_widget_attr_trigger.py +61 -0
- pypdfform-6.0.2/PyPDFForm/lib/annotations/link.py +0 -68
- pypdfform-6.0.2/PyPDFForm/lib/types.py +0 -64
- {pypdfform-6.0.2 → pypdfform-6.1.1}/LICENSE +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/cli/__init__.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/cli/entry.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/cli/inspect.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/cli/remove.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/cli/root.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/cli/schemas/__init__.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/cli/schemas/create.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/cli/schemas/update.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/__init__.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/annotations/__init__.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/annotations/base.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/annotations/stamp.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/annotations/text.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/annotations/text_markup.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/assets/__init__.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/assets/blank.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/coordinate.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/egress.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/filler.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/font.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/hooks.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/image.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/middleware/__init__.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/middleware/image.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/middleware/radio.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/middleware/signature.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/patterns.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/raw/__init__.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/raw/circle.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/raw/ellipse.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/raw/line.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/raw/rect.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/raw/text.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/widgets/__init__.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/widgets/base.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/widgets/checkbox.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/widgets/dropdown.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/widgets/image.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/widgets/radio.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/widgets/signature.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm/lib/widgets/text.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm.egg-info/dependency_links.txt +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm.egg-info/entry_points.txt +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/PyPDFForm.egg-info/top_level.txt +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/README.md +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/setup.cfg +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_bulk_create_fields.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_create_widget.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_draw_elements.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_dropdown.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_extract_middleware_attributes.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_fill_max_length_text_field.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_font_related.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_generate_appearance_streams.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_js.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_need_appearances.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_paragraph.py +0 -0
- {pypdfform-6.0.2 → pypdfform-6.1.1}/tests/test_use_full_widget_name.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyPDFForm
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.1.1
|
|
4
4
|
Summary: The Python library & CLI for PDF forms.
|
|
5
5
|
Author: Jinge Li
|
|
6
6
|
License-Expression: MIT
|
|
@@ -28,7 +28,7 @@ Requires-Dist: cryptography<51.0.0,>=50.0.0
|
|
|
28
28
|
Requires-Dist: fonttools<5.0.0,>=4.63.0
|
|
29
29
|
Requires-Dist: pikepdf<11.0.0,>=10.7.2
|
|
30
30
|
Requires-Dist: pillow<14.0.0,>=12.2.0
|
|
31
|
-
Requires-Dist: pypdf<7.0.0,>=6.
|
|
31
|
+
Requires-Dist: pypdf<7.0.0,>=6.19.0
|
|
32
32
|
Requires-Dist: reportlab<6.0.0,>=4.5.1
|
|
33
33
|
Provides-Extra: cli
|
|
34
34
|
Requires-Dist: typer<1.0.0,>=0.26.1; extra == "cli"
|
|
@@ -16,15 +16,14 @@ PyPDFForm aims to make PDF form automation straightforward whether it is used
|
|
|
16
16
|
from Python code or from the command line.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
__version__ = "6.
|
|
19
|
+
__version__ = "6.1.1"
|
|
20
20
|
|
|
21
21
|
from .lib.annotations import Annotations
|
|
22
22
|
from .lib.assets.blank import BlankPage
|
|
23
23
|
from .lib.middleware import Widgets
|
|
24
24
|
from .lib.raw import RawElements
|
|
25
|
-
from .lib.types import PdfArray
|
|
26
25
|
from .lib.widgets import Fields
|
|
27
|
-
from .lib.wrapper import PdfWrapper
|
|
26
|
+
from .lib.wrapper import PdfArray, PdfWrapper
|
|
28
27
|
|
|
29
28
|
__all__ = [
|
|
30
29
|
"PdfWrapper",
|
|
@@ -312,6 +312,7 @@ def create_elements_from_file(
|
|
|
312
312
|
options_param_hint: str,
|
|
313
313
|
output: Path | None = None,
|
|
314
314
|
element_type: str | None = None,
|
|
315
|
+
input_errors: tuple[type[Exception], ...] = (),
|
|
315
316
|
) -> None:
|
|
316
317
|
"""
|
|
317
318
|
Creates PDF elements from a data file or command-line options.
|
|
@@ -321,7 +322,8 @@ def create_elements_from_file(
|
|
|
321
322
|
from the additional command-line options stored in ``ctx.args``. The data
|
|
322
323
|
file takes precedence when both input forms are supplied. Each definition
|
|
323
324
|
is constructed after optional font registration and passed to `method_name`
|
|
324
|
-
on `PdfWrapper`.
|
|
325
|
+
on `PdfWrapper`. Errors of the `input_errors` types raised by that method
|
|
326
|
+
are reported against the input form that was used, without writing output.
|
|
325
327
|
|
|
326
328
|
Args:
|
|
327
329
|
pdf (Path): The path to the input PDF file.
|
|
@@ -342,6 +344,9 @@ def create_elements_from_file(
|
|
|
342
344
|
omitted, the input PDF is overwritten. Defaults to None.
|
|
343
345
|
element_type (str, optional): Element type selected for command-line
|
|
344
346
|
input. Defaults to None.
|
|
347
|
+
input_errors (tuple[type[Exception], ...], optional): Exception types
|
|
348
|
+
raised by `method_name` for invalid element definitions. Defaults to
|
|
349
|
+
an empty tuple.
|
|
345
350
|
"""
|
|
346
351
|
if data is not None:
|
|
347
352
|
input_data = load_data_file(data, schema, file_param_hint)
|
|
@@ -370,4 +375,13 @@ def create_elements_from_file(
|
|
|
370
375
|
handle_font_registration(obj, each, registered_font)
|
|
371
376
|
ungrouped_input.append(element_map[k](**each))
|
|
372
377
|
|
|
373
|
-
|
|
378
|
+
try:
|
|
379
|
+
result = getattr(obj, method_name)(ungrouped_input)
|
|
380
|
+
except input_errors as exc:
|
|
381
|
+
cli_bad_parameter(
|
|
382
|
+
str(exc),
|
|
383
|
+
param_hint=file_param_hint if data is not None else options_param_hint,
|
|
384
|
+
cause=exc,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
result.write(output or pdf)
|
|
@@ -126,7 +126,11 @@ def extract(
|
|
|
126
126
|
The command validates that the requested end page does not precede the
|
|
127
127
|
start page, converts the 1-based CLI page numbers into the slice expected
|
|
128
128
|
by `PdfWrapper.pages`, and writes the extracted pages to the required
|
|
129
|
-
output path.
|
|
129
|
+
output path. Each page is extracted separately, so links and other GoTo
|
|
130
|
+
actions to other pages are removed, and fields shared with other pages keep
|
|
131
|
+
only their widgets on each page. A `ValueError` from `PdfWrapper.pages`
|
|
132
|
+
for other references to another page's content is not converted, so the
|
|
133
|
+
command exits with an error before writing the output.
|
|
130
134
|
|
|
131
135
|
Args:
|
|
132
136
|
ctx (typer.Context): Typer context containing global `PdfWrapper`
|
|
@@ -332,7 +336,8 @@ def annotation(
|
|
|
332
336
|
The command maps input groups such as `text`, `link`, and `highlight` to
|
|
333
337
|
annotation classes, validates the input against the CLI annotation schema,
|
|
334
338
|
creates the corresponding annotation objects, and calls
|
|
335
|
-
`PdfWrapper.annotate` before writing the modified PDF. A
|
|
339
|
+
`PdfWrapper.annotate` before writing the modified PDF. A link to a page
|
|
340
|
+
outside the PDF is reported as a parameter error. A data file can
|
|
336
341
|
define multiple annotations grouped by type. Without `--file`, `--type` and
|
|
337
342
|
the annotation options define one annotation. The data file takes
|
|
338
343
|
precedence when both input forms are supplied.
|
|
@@ -368,6 +373,7 @@ def annotation(
|
|
|
368
373
|
options_param_hint="annotation options",
|
|
369
374
|
output=output,
|
|
370
375
|
element_type=element_type,
|
|
376
|
+
input_errors=(ValueError,),
|
|
371
377
|
)
|
|
372
378
|
|
|
373
379
|
|
|
@@ -297,9 +297,10 @@ def field(
|
|
|
297
297
|
|
|
298
298
|
The command validates the input against the field update schema, loads the
|
|
299
299
|
PDF with the global CLI options stored in `ctx.obj`, resolves each field
|
|
300
|
-
name,
|
|
301
|
-
|
|
302
|
-
|
|
300
|
+
name, checks that each field type supports its requested properties,
|
|
301
|
+
registers any referenced fonts once per invocation, assigns the requested
|
|
302
|
+
widget properties, and writes the modified PDF to the requested output path
|
|
303
|
+
or back to the input file. A data file can update multiple
|
|
303
304
|
fields. Without `--file`, `--field` and the property options update one
|
|
304
305
|
field. The data file takes precedence when both input forms are supplied.
|
|
305
306
|
|
|
@@ -315,8 +316,9 @@ def field(
|
|
|
315
316
|
Defaults to None.
|
|
316
317
|
|
|
317
318
|
Raises:
|
|
318
|
-
typer.BadParameter: Raised when the input file is invalid
|
|
319
|
-
field does not exist
|
|
319
|
+
typer.BadParameter: Raised when the input file is invalid, a requested
|
|
320
|
+
field does not exist, or a field type does not support a requested
|
|
321
|
+
property.
|
|
320
322
|
"""
|
|
321
323
|
if data is not None:
|
|
322
324
|
input_data = load_data_file(data, FIELD_SCHEMA, "--file")
|
|
@@ -337,12 +339,21 @@ def field(
|
|
|
337
339
|
field_param_hint = "--field"
|
|
338
340
|
|
|
339
341
|
obj = PdfWrapper(str(pdf), **ctx.obj)
|
|
340
|
-
|
|
342
|
+
field_widgets = {}
|
|
341
343
|
for k, each in input_data.items():
|
|
342
344
|
field_widget = get_widget(obj, k, field_param_hint)
|
|
345
|
+
for param in each:
|
|
346
|
+
try:
|
|
347
|
+
field_widget.check_property_supported(param)
|
|
348
|
+
except AttributeError as exc:
|
|
349
|
+
cli_bad_parameter(str(exc), param_hint=field_param_hint, cause=exc)
|
|
350
|
+
field_widgets[k] = field_widget
|
|
351
|
+
|
|
352
|
+
registered_font = {}
|
|
353
|
+
for k, each in input_data.items():
|
|
343
354
|
handle_font_registration(obj, each, registered_font)
|
|
344
355
|
for param, v in each.items():
|
|
345
|
-
setattr(
|
|
356
|
+
setattr(field_widgets[k], param, v)
|
|
346
357
|
|
|
347
358
|
obj.write(output or pdf)
|
|
348
359
|
|
|
@@ -10,6 +10,7 @@ forms. The module ensures that the input is properly converted into a byte
|
|
|
10
10
|
stream before further processing.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
from os import PathLike
|
|
13
14
|
from os.path import isfile
|
|
14
15
|
from typing import Any, BinaryIO
|
|
15
16
|
|
|
@@ -32,7 +33,7 @@ def readable(obj: Any) -> bool:
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
def fp_or_f_obj_or_stream_to_stream(
|
|
35
|
-
fp_or_f_obj_or_stream: bytes | str | BinaryIO,
|
|
36
|
+
fp_or_f_obj_or_stream: bytes | str | PathLike | BinaryIO,
|
|
36
37
|
) -> bytes:
|
|
37
38
|
"""
|
|
38
39
|
Adapt a file path, file object, or stream to a byte stream.
|
|
@@ -40,30 +41,37 @@ def fp_or_f_obj_or_stream_to_stream(
|
|
|
40
41
|
This function takes a file path, a file object, or a byte stream and adapts it to a consistent byte stream.
|
|
41
42
|
It handles different input types, including:
|
|
42
43
|
- byte streams (bytes)
|
|
43
|
-
- file paths (str)
|
|
44
|
+
- file paths (str or PathLike)
|
|
44
45
|
- file-like objects with a read() method (BinaryIO)
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
Paths are opened directly, so a missing path raises ``FileNotFoundError``.
|
|
47
|
+
Empty bytes and empty readable objects are returned as ``b""``.
|
|
47
48
|
|
|
48
49
|
Args:
|
|
49
|
-
fp_or_f_obj_or_stream (bytes | str | BinaryIO): The input to adapt.
|
|
50
|
-
It can be a byte stream, a file path
|
|
50
|
+
fp_or_f_obj_or_stream (bytes | str | PathLike | BinaryIO): The input to adapt.
|
|
51
|
+
It can be a byte stream, a file path, or a file object.
|
|
51
52
|
|
|
52
53
|
Returns:
|
|
53
54
|
bytes: The byte stream representation of the input.
|
|
54
|
-
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
OSError: If a file path cannot be opened, such as ``FileNotFoundError``
|
|
58
|
+
for a missing path.
|
|
59
|
+
TypeError: If the input is not bytes, a file path, or a readable object.
|
|
55
60
|
"""
|
|
56
61
|
# not cached to handle writing to the same disk file
|
|
57
|
-
result = b""
|
|
58
62
|
if isinstance(fp_or_f_obj_or_stream, bytes):
|
|
59
63
|
result = fp_or_f_obj_or_stream
|
|
60
64
|
|
|
61
65
|
elif readable(fp_or_f_obj_or_stream):
|
|
62
66
|
result = fp_or_f_obj_or_stream.read()
|
|
63
67
|
|
|
64
|
-
elif isinstance(fp_or_f_obj_or_stream, str
|
|
68
|
+
elif isinstance(fp_or_f_obj_or_stream, (str, PathLike)):
|
|
65
69
|
with open(fp_or_f_obj_or_stream, "rb") as _file:
|
|
66
70
|
result = _file.read()
|
|
71
|
+
|
|
72
|
+
else:
|
|
73
|
+
msg = f"Expected bytes, a file path, or a readable binary stream, got {type(fp_or_f_obj_or_stream).__name__}."
|
|
74
|
+
raise TypeError(msg)
|
|
67
75
|
return result
|
|
68
76
|
|
|
69
77
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
This module defines the class for link annotations in PyPDFForm.
|
|
4
|
+
|
|
5
|
+
It provides a structure for representing and interacting with PDF link
|
|
6
|
+
annotations, which allow users to click and navigate to a URI or an internal page.
|
|
7
|
+
|
|
8
|
+
Classes:
|
|
9
|
+
- `LinkAnnotation`: A dataclass representing the properties of a PDF link annotation.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Optional, Sequence
|
|
14
|
+
|
|
15
|
+
from pypdf.generic import (
|
|
16
|
+
ArrayObject,
|
|
17
|
+
DictionaryObject,
|
|
18
|
+
IndirectObject,
|
|
19
|
+
NameObject,
|
|
20
|
+
TextStringObject,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from ..constants import A, Link, S
|
|
24
|
+
from .base import Annotation
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class LinkAnnotation(Annotation):
|
|
29
|
+
"""
|
|
30
|
+
A dataclass representing the properties of a PDF link annotation.
|
|
31
|
+
|
|
32
|
+
This class extends the `Annotation` base class to specifically handle
|
|
33
|
+
link annotations, including the target URI or an internal page.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
uri (str): The URI that the link annotation points to. Takes precedence
|
|
37
|
+
over `page` when both are set. Defaults to None.
|
|
38
|
+
page (int): The 1-based page number in the same PDF that the link
|
|
39
|
+
annotation points to. Defaults to None.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
_annotation_type: str = Link
|
|
43
|
+
|
|
44
|
+
uri: Optional[str] = None
|
|
45
|
+
page: Optional[int] = None
|
|
46
|
+
|
|
47
|
+
def get_specific_properties(self) -> dict:
|
|
48
|
+
"""
|
|
49
|
+
Gets properties specific to the link annotation.
|
|
50
|
+
|
|
51
|
+
This method extends the base properties with a URI action. A link to
|
|
52
|
+
an internal page needs the pages of the target PDF, so its destination
|
|
53
|
+
is built separately by `get_destination`.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
dict: A dictionary of PDF properties specific to the link annotation.
|
|
57
|
+
"""
|
|
58
|
+
result = super().get_specific_properties()
|
|
59
|
+
if self.uri is not None:
|
|
60
|
+
result[NameObject(A)] = DictionaryObject(
|
|
61
|
+
{
|
|
62
|
+
NameObject(S): NameObject("/URI"),
|
|
63
|
+
NameObject("/URI"): TextStringObject(self.uri),
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
def get_destination(
|
|
70
|
+
self, page_references: Sequence[IndirectObject]
|
|
71
|
+
) -> Optional[ArrayObject]:
|
|
72
|
+
"""
|
|
73
|
+
Gets the destination of a link to an internal page.
|
|
74
|
+
|
|
75
|
+
A destination in the same PDF must refer to the target page object
|
|
76
|
+
rather than a page index, which is reserved for destinations in other
|
|
77
|
+
PDFs. The 1-based `page` is resolved against the page references of the
|
|
78
|
+
PDF that will contain the link.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
page_references (Sequence[IndirectObject]): References to the pages
|
|
82
|
+
of the PDF that will contain the link, in page order.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Optional[ArrayObject]: The destination that fits the target page in
|
|
86
|
+
the window, or None if the link has a URI or no page.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: If `page` is outside the pages of the PDF.
|
|
90
|
+
"""
|
|
91
|
+
if self.uri is not None or self.page is None:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
if not 1 <= self.page <= len(page_references):
|
|
95
|
+
msg = f"Link page {self.page} must satisfy 1 <= page <= {len(page_references)}."
|
|
96
|
+
raise ValueError(msg)
|
|
97
|
+
|
|
98
|
+
return ArrayObject([page_references[self.page - 1], NameObject("/Fit")])
|