aspose-pdf-foss-for-python 0.1.0__py3-none-any.whl
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.
- aspose_pdf/__init__.py +367 -0
- aspose_pdf/_compat_surface.py +142 -0
- aspose_pdf/_export_files.py +108 -0
- aspose_pdf/_pending_signatures.py +222 -0
- aspose_pdf/_version.py +17 -0
- aspose_pdf/actions.py +273 -0
- aspose_pdf/annotations/__init__.py +1614 -0
- aspose_pdf/annotations/pdf3d.py +70 -0
- aspose_pdf/attachments.py +62 -0
- aspose_pdf/auto_links.py +375 -0
- aspose_pdf/block_extraction.py +223 -0
- aspose_pdf/cgm.py +7 -0
- aspose_pdf/clustering/__init__.py +13 -0
- aspose_pdf/clustering/hierarchical_agglomerative_clustering.py +151 -0
- aspose_pdf/color.py +241 -0
- aspose_pdf/destinations.py +164 -0
- aspose_pdf/document.py +5884 -0
- aspose_pdf/document_fonts.py +150 -0
- aspose_pdf/drawing.py +13 -0
- aspose_pdf/engine/__init__.py +1 -0
- aspose_pdf/engine/agl.py +243 -0
- aspose_pdf/engine/annotation_cleanup.py +407 -0
- aspose_pdf/engine/annotation_import.py +503 -0
- aspose_pdf/engine/annotation_json.py +313 -0
- aspose_pdf/engine/annotation_xfdf.py +405 -0
- aspose_pdf/engine/annotation_xfdf_import.py +389 -0
- aspose_pdf/engine/appearance.py +1361 -0
- aspose_pdf/engine/attachment_zip.py +306 -0
- aspose_pdf/engine/attachment_zip_import.py +387 -0
- aspose_pdf/engine/auto_crop.py +101 -0
- aspose_pdf/engine/auto_tag.py +1451 -0
- aspose_pdf/engine/blank_pages.py +198 -0
- aspose_pdf/engine/blank_split.py +73 -0
- aspose_pdf/engine/bookmark_json.py +714 -0
- aspose_pdf/engine/bookmark_split.py +167 -0
- aspose_pdf/engine/ccitt.py +457 -0
- aspose_pdf/engine/ccitt_tables.py +231 -0
- aspose_pdf/engine/cert_chain.py +306 -0
- aspose_pdf/engine/cff_outlines.py +928 -0
- aspose_pdf/engine/cms.py +562 -0
- aspose_pdf/engine/comparison_html.py +435 -0
- aspose_pdf/engine/conformance.py +2748 -0
- aspose_pdf/engine/contact_sheet.py +215 -0
- aspose_pdf/engine/content_authoring.py +1347 -0
- aspose_pdf/engine/content_bounds.py +67 -0
- aspose_pdf/engine/content_isolation.py +180 -0
- aspose_pdf/engine/content_stream_parser.py +2318 -0
- aspose_pdf/engine/cos.py +416 -0
- aspose_pdf/engine/data/__init__.py +81 -0
- aspose_pdf/engine/data/agl/LICENSE-ADOBE-AGL.txt +28 -0
- aspose_pdf/engine/data/agl/LICENSE-REPORTLAB.txt +31 -0
- aspose_pdf/engine/data/agl/README.md +44 -0
- aspose_pdf/engine/data/agl/glyphlist.json.zlib +0 -0
- aspose_pdf/engine/data/cmaps/LICENSE-ADOBE-CMAP.txt +28 -0
- aspose_pdf/engine/data/cmaps/LICENSE-ADOBE-MAPPING.txt +28 -0
- aspose_pdf/engine/data/cmaps/README.md +51 -0
- aspose_pdf/engine/data/cmaps/predefined_cmaps_CNS1.json.zlib +0 -0
- aspose_pdf/engine/data/cmaps/predefined_cmaps_GB1.json.zlib +0 -0
- aspose_pdf/engine/data/cmaps/predefined_cmaps_Japan1.json.zlib +0 -0
- aspose_pdf/engine/data/cmaps/predefined_cmaps_Korea1.json.zlib +0 -0
- aspose_pdf/engine/data/cmaps/predefined_cmaps_index.json.zlib +0 -0
- aspose_pdf/engine/data/fonts/LICENSE-DEJAVU.txt +187 -0
- aspose_pdf/engine/data/fonts/LICENSE-OFL.txt +102 -0
- aspose_pdf/engine/data/fonts/README.md +47 -0
- aspose_pdf/engine/data/fonts/dingbats.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/mono-bold.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/mono-bolditalic.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/mono-italic.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/mono-regular.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/sans-bold.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/sans-bolditalic.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/sans-italic.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/sans-regular.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/serif-bold.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/serif-bolditalic.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/serif-italic.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/serif-regular.ttf.zlib +0 -0
- aspose_pdf/engine/data/fonts/symbol.ttf.zlib +0 -0
- aspose_pdf/engine/data/number.py +78 -0
- aspose_pdf/engine/data/types.py +156 -0
- aspose_pdf/engine/data/xmp.py +1458 -0
- aspose_pdf/engine/dct.py +854 -0
- aspose_pdf/engine/dss.py +658 -0
- aspose_pdf/engine/duplicate_pages.py +67 -0
- aspose_pdf/engine/encryption.py +967 -0
- aspose_pdf/engine/field_appearance.py +310 -0
- aspose_pdf/engine/file_output.py +98 -0
- aspose_pdf/engine/filters.py +958 -0
- aspose_pdf/engine/flatten.py +388 -0
- aspose_pdf/engine/font_authoring.py +609 -0
- aspose_pdf/engine/font_inventory.py +211 -0
- aspose_pdf/engine/font_resolver.py +829 -0
- aspose_pdf/engine/font_subset.py +545 -0
- aspose_pdf/engine/font_subset_cff.py +963 -0
- aspose_pdf/engine/font_subset_type1.py +114 -0
- aspose_pdf/engine/font_variations.py +159 -0
- aspose_pdf/engine/form_batch.py +134 -0
- aspose_pdf/engine/form_csv.py +267 -0
- aspose_pdf/engine/form_csv_export.py +131 -0
- aspose_pdf/engine/form_data.py +364 -0
- aspose_pdf/engine/form_field_edit.py +306 -0
- aspose_pdf/engine/form_fields.py +253 -0
- aspose_pdf/engine/form_json.py +89 -0
- aspose_pdf/engine/form_schema.py +246 -0
- aspose_pdf/engine/form_validation.py +395 -0
- aspose_pdf/engine/glyph_outlines.py +465 -0
- aspose_pdf/engine/graphics_absorb.py +1061 -0
- aspose_pdf/engine/grid_split.py +80 -0
- aspose_pdf/engine/gvar.py +467 -0
- aspose_pdf/engine/headings.py +273 -0
- aspose_pdf/engine/image_export.py +875 -0
- aspose_pdf/engine/image_import.py +219 -0
- aspose_pdf/engine/image_resample.py +116 -0
- aspose_pdf/engine/image_resolution.py +376 -0
- aspose_pdf/engine/image_zip.py +339 -0
- aspose_pdf/engine/imposition.py +233 -0
- aspose_pdf/engine/incremental_update.py +477 -0
- aspose_pdf/engine/inline_image.py +337 -0
- aspose_pdf/engine/interleave.py +188 -0
- aspose_pdf/engine/io.py +53 -0
- aspose_pdf/engine/jbig2.py +186 -0
- aspose_pdf/engine/jpeg2000.py +2124 -0
- aspose_pdf/engine/jpeg_encoder.py +706 -0
- aspose_pdf/engine/jpx.py +211 -0
- aspose_pdf/engine/link_extraction.py +378 -0
- aspose_pdf/engine/link_urls.py +252 -0
- aspose_pdf/engine/link_validation.py +555 -0
- aspose_pdf/engine/lists.py +307 -0
- aspose_pdf/engine/logging.py +18 -0
- aspose_pdf/engine/merge_bookmarks.py +180 -0
- aspose_pdf/engine/optional_content.py +1323 -0
- aspose_pdf/engine/page_count_split.py +60 -0
- aspose_pdf/engine/page_labels.py +295 -0
- aspose_pdf/engine/page_zip.py +219 -0
- aspose_pdf/engine/parser_exceptions.py +125 -0
- aspose_pdf/engine/pdf_matrix.py +90 -0
- aspose_pdf/engine/pdf_parser_cos.py +1611 -0
- aspose_pdf/engine/pdf_writer_cos.py +544 -0
- aspose_pdf/engine/predefined_cmaps.py +635 -0
- aspose_pdf/engine/primitives/__init__.py +5 -0
- aspose_pdf/engine/primitives/matrix.py +31 -0
- aspose_pdf/engine/pubsec.py +898 -0
- aspose_pdf/engine/rasterized_copy.py +171 -0
- aspose_pdf/engine/rasterizer.py +5483 -0
- aspose_pdf/engine/redaction.py +140 -0
- aspose_pdf/engine/revocation.py +495 -0
- aspose_pdf/engine/rich_text.py +415 -0
- aspose_pdf/engine/sanitize.py +412 -0
- aspose_pdf/engine/sfnt.py +247 -0
- aspose_pdf/engine/shading.py +2317 -0
- aspose_pdf/engine/sign_field.py +689 -0
- aspose_pdf/engine/signature_appearance.py +302 -0
- aspose_pdf/engine/signature_profiles.py +206 -0
- aspose_pdf/engine/signature_revisions.py +476 -0
- aspose_pdf/engine/signature_validator.py +313 -0
- aspose_pdf/engine/signed_changes.py +208 -0
- aspose_pdf/engine/signing.py +288 -0
- aspose_pdf/engine/simple_pdf.py +21416 -0
- aspose_pdf/engine/spread_split.py +330 -0
- aspose_pdf/engine/stamps.py +414 -0
- aspose_pdf/engine/std_font_data.py +201 -0
- aspose_pdf/engine/std_fonts.py +97 -0
- aspose_pdf/engine/std_metrics.py +364 -0
- aspose_pdf/engine/stream_output.py +22 -0
- aspose_pdf/engine/stroke.py +228 -0
- aspose_pdf/engine/svg_export.py +1162 -0
- aspose_pdf/engine/symbol_encodings.py +260 -0
- aspose_pdf/engine/tables.py +420 -0
- aspose_pdf/engine/text_blocks.py +467 -0
- aspose_pdf/engine/text_comparison.py +146 -0
- aspose_pdf/engine/text_edit.py +1936 -0
- aspose_pdf/engine/text_export.py +758 -0
- aspose_pdf/engine/text_faces.py +275 -0
- aspose_pdf/engine/text_highlight.py +197 -0
- aspose_pdf/engine/text_layout.py +574 -0
- aspose_pdf/engine/text_locate.py +423 -0
- aspose_pdf/engine/text_metrics.py +86 -0
- aspose_pdf/engine/text_outlines.py +150 -0
- aspose_pdf/engine/text_region.py +476 -0
- aspose_pdf/engine/text_selection.py +53 -0
- aspose_pdf/engine/text_split.py +141 -0
- aspose_pdf/engine/tiff_import.py +130 -0
- aspose_pdf/engine/timestamp.py +327 -0
- aspose_pdf/engine/toc.py +407 -0
- aspose_pdf/engine/type1_outlines.py +471 -0
- aspose_pdf/engine/viewer_preferences.py +275 -0
- aspose_pdf/engine/visual_comparison.py +318 -0
- aspose_pdf/engine/webp_encoder.py +107 -0
- aspose_pdf/engine/woff.py +302 -0
- aspose_pdf/engine/woff2.py +674 -0
- aspose_pdf/engine/word_extraction.py +235 -0
- aspose_pdf/exceptions.py +148 -0
- aspose_pdf/facades.py +1569 -0
- aspose_pdf/font_registry.py +149 -0
- aspose_pdf/font_repository.py +311 -0
- aspose_pdf/font_substitution.py +115 -0
- aspose_pdf/forms.py +1688 -0
- aspose_pdf/generated/__init__.py +3 -0
- aspose_pdf/generated/document.py +14 -0
- aspose_pdf/generated/forms.py +12 -0
- aspose_pdf/generated/pdfa.py +12 -0
- aspose_pdf/generated/security.py +33 -0
- aspose_pdf/geometry.py +149 -0
- aspose_pdf/graphics.py +256 -0
- aspose_pdf/html.py +117 -0
- aspose_pdf/image_resolution.py +45 -0
- aspose_pdf/images.py +524 -0
- aspose_pdf/interactive.py +435 -0
- aspose_pdf/latex.py +14 -0
- aspose_pdf/layers.py +482 -0
- aspose_pdf/license_management.py +65 -0
- aspose_pdf/link_extraction.py +52 -0
- aspose_pdf/link_validation.py +29 -0
- aspose_pdf/lists.py +365 -0
- aspose_pdf/load_limits.py +173 -0
- aspose_pdf/load_options.py +63 -0
- aspose_pdf/logical_structure.py +34 -0
- aspose_pdf/lowcode.py +1598 -0
- aspose_pdf/markdown.py +38 -0
- aspose_pdf/ofd.py +7 -0
- aspose_pdf/optimization.py +51 -0
- aspose_pdf/outlines.py +271 -0
- aspose_pdf/page_labels.py +169 -0
- aspose_pdf/page_ranges.py +90 -0
- aspose_pdf/page_size.py +220 -0
- aspose_pdf/pages.py +1883 -0
- aspose_pdf/paths.py +306 -0
- aspose_pdf/pdf_consts.py +19 -0
- aspose_pdf/pdfa.py +285 -0
- aspose_pdf/pdfua.py +194 -0
- aspose_pdf/pdfx.py +267 -0
- aspose_pdf/permissions.py +340 -0
- aspose_pdf/presentation.py +170 -0
- aspose_pdf/primitives.py +22 -0
- aspose_pdf/printing.py +41 -0
- aspose_pdf/py.typed +0 -0
- aspose_pdf/recipients.py +78 -0
- aspose_pdf/sanitization.py +90 -0
- aspose_pdf/save_format.py +25 -0
- aspose_pdf/save_options.py +25 -0
- aspose_pdf/security/__init__.py +160 -0
- aspose_pdf/security/impl/__init__.py +1 -0
- aspose_pdf/security/impl/digests/__init__.py +1 -0
- aspose_pdf/signature.py +385 -0
- aspose_pdf/signature_appearance.py +263 -0
- aspose_pdf/stamps.py +563 -0
- aspose_pdf/svg.py +41 -0
- aspose_pdf/table_extraction.py +156 -0
- aspose_pdf/tables.py +272 -0
- aspose_pdf/tagged.py +254 -0
- aspose_pdf/text.py +762 -0
- aspose_pdf/text_block.py +286 -0
- aspose_pdf/text_comparison.py +31 -0
- aspose_pdf/text_layout.py +100 -0
- aspose_pdf/utils.py +74 -0
- aspose_pdf/validation.py +286 -0
- aspose_pdf/viewer_preferences.py +375 -0
- aspose_pdf/visual_comparison.py +53 -0
- aspose_pdf/visualization.py +118 -0
- aspose_pdf/xmp.py +75 -0
- aspose_pdf_foss_for_python-0.1.0.dist-info/METADATA +2233 -0
- aspose_pdf_foss_for_python-0.1.0.dist-info/RECORD +265 -0
- aspose_pdf_foss_for_python-0.1.0.dist-info/WHEEL +5 -0
- aspose_pdf_foss_for_python-0.1.0.dist-info/licenses/LICENSE +21 -0
- aspose_pdf_foss_for_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""What ``Document.sign``, ``add_ltv`` and ``add_document_timestamp`` leave for ``save``.
|
|
2
|
+
|
|
3
|
+
A signature covers bytes, so it can only be made once the document has been
|
|
4
|
+
serialized: each call records what to do, and ``Document.save`` does it, in call
|
|
5
|
+
order, to the bytes it is about to write -- each as an incremental revision, so
|
|
6
|
+
an earlier signature in the same save stays valid under a later one. The
|
|
7
|
+
arguments are checked when the call is made, so a mistake surfaces there rather
|
|
8
|
+
than halfway through a save that may already have asked a timestamp authority
|
|
9
|
+
for a token.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Iterable
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from cryptography import x509
|
|
19
|
+
from cryptography.hazmat.primitives import serialization
|
|
20
|
+
from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa
|
|
21
|
+
from cryptography.x509 import ocsp
|
|
22
|
+
from cryptography.x509.oid import NameOID
|
|
23
|
+
|
|
24
|
+
from aspose_pdf.engine.dss import DssMaterial, add_document_timestamp, enable_ltv
|
|
25
|
+
from aspose_pdf.engine.sign_field import sign_field
|
|
26
|
+
from aspose_pdf.exceptions import PdfValidationException
|
|
27
|
+
from aspose_pdf.validation import CertificationLevel
|
|
28
|
+
|
|
29
|
+
_KEY_TYPES = (
|
|
30
|
+
rsa.RSAPrivateKey,
|
|
31
|
+
ec.EllipticCurvePrivateKey,
|
|
32
|
+
ed25519.Ed25519PrivateKey,
|
|
33
|
+
ed448.Ed448PrivateKey,
|
|
34
|
+
)
|
|
35
|
+
# PKCS#7 (``adbe.pkcs7.detached``) goes through ``cryptography``'s builder,
|
|
36
|
+
# which signs with RSA and EC keys only; the CAdES builder also takes Ed25519.
|
|
37
|
+
_PKCS7_KEY_TYPES = (rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _public_key_der(key: Any) -> bytes:
|
|
41
|
+
return key.public_bytes(
|
|
42
|
+
serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _check_pair(certificate: Any, private_key: Any, role: str) -> None:
|
|
47
|
+
if not isinstance(certificate, x509.Certificate):
|
|
48
|
+
raise TypeError(f"The {role} certificate must be a cryptography x509.Certificate")
|
|
49
|
+
if not isinstance(private_key, _KEY_TYPES):
|
|
50
|
+
raise TypeError(f"The {role} private key must be an RSA, EC or Ed25519 private key")
|
|
51
|
+
# A key that is not the certificate's makes a signature that no validator
|
|
52
|
+
# accepts; nothing further along notices, so it is caught here.
|
|
53
|
+
if _public_key_der(private_key.public_key()) != _public_key_der(certificate.public_key()):
|
|
54
|
+
raise PdfValidationException(f"The {role} private key does not belong to its certificate")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def certificate_common_name(certificate: Any) -> str | None:
|
|
58
|
+
"""The name to show for *certificate*'s holder, or ``None``.
|
|
59
|
+
|
|
60
|
+
A visible signature says who signed, and the certificate is the one place
|
|
61
|
+
that already knows. The subject's **common name** is what every other
|
|
62
|
+
producer shows; an organization certificate that has none falls back to
|
|
63
|
+
its organization name, and a certificate with neither is left for the
|
|
64
|
+
caller's own ``signer_name``.
|
|
65
|
+
"""
|
|
66
|
+
for oid in (NameOID.COMMON_NAME, NameOID.ORGANIZATION_NAME):
|
|
67
|
+
for attribute in certificate.subject.get_attributes_for_oid(oid):
|
|
68
|
+
value = attribute.value
|
|
69
|
+
if isinstance(value, bytes): # a non-UTF-8 directory string
|
|
70
|
+
value = value.decode("utf-8", "replace")
|
|
71
|
+
text = str(value).strip()
|
|
72
|
+
if text:
|
|
73
|
+
return text
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def check_signer(certificate: Any, private_key: Any, *, pades: bool) -> None:
|
|
78
|
+
"""Refuse a certificate and key that could not make a verifiable signature."""
|
|
79
|
+
_check_pair(certificate, private_key, "signer")
|
|
80
|
+
if isinstance(private_key, ed448.Ed448PrivateKey):
|
|
81
|
+
# RFC 8419: Ed448 in CMS digests with SHAKE256, not produced here.
|
|
82
|
+
raise PdfValidationException(
|
|
83
|
+
"Ed448 keys are not supported for signing: an Ed448 signature "
|
|
84
|
+
"digests with SHAKE256 (RFC 8419), which this signer does not produce"
|
|
85
|
+
)
|
|
86
|
+
if not pades and not isinstance(private_key, _PKCS7_KEY_TYPES):
|
|
87
|
+
raise PdfValidationException(
|
|
88
|
+
f"A {type(private_key).__name__} signs only with pades=True: the "
|
|
89
|
+
"adbe.pkcs7.detached encoder takes RSA and EC keys"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def check_timestamp_source(url: Any, authority: Any) -> tuple[Any, Any] | None:
|
|
94
|
+
"""Validate a timestamp URL or local authority; return the authority as a pair."""
|
|
95
|
+
if url is not None and authority is not None:
|
|
96
|
+
raise PdfValidationException(
|
|
97
|
+
"Give a timestamp_url or a timestamp_authority, not both"
|
|
98
|
+
)
|
|
99
|
+
if url is not None and (not isinstance(url, str) or not url):
|
|
100
|
+
raise TypeError("timestamp_url must be a non-empty string")
|
|
101
|
+
if authority is None:
|
|
102
|
+
return None
|
|
103
|
+
try:
|
|
104
|
+
certificate, private_key = authority
|
|
105
|
+
except (TypeError, ValueError):
|
|
106
|
+
raise TypeError(
|
|
107
|
+
"timestamp_authority must be a (certificate, private_key) pair"
|
|
108
|
+
) from None
|
|
109
|
+
_check_pair(certificate, private_key, "timestamp authority")
|
|
110
|
+
# The local authority signs its tokens with RSA PKCS#1 v1.5.
|
|
111
|
+
if not isinstance(private_key, rsa.RSAPrivateKey):
|
|
112
|
+
raise PdfValidationException("A local timestamp authority needs an RSA key")
|
|
113
|
+
return certificate, private_key
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def certification_level(value: Any) -> int | None:
|
|
117
|
+
"""The DocMDP ``/P`` a ``certify`` argument asks for, or ``None``."""
|
|
118
|
+
if value is None or value is CertificationLevel.NOT_CERTIFIED:
|
|
119
|
+
return None
|
|
120
|
+
if isinstance(value, CertificationLevel):
|
|
121
|
+
return value.value
|
|
122
|
+
if isinstance(value, bool) or not isinstance(value, int) or value not in (1, 2, 3):
|
|
123
|
+
raise PdfValidationException(
|
|
124
|
+
"certify must be a CertificationLevel or a DocMDP level 1, 2 or 3"
|
|
125
|
+
)
|
|
126
|
+
return value
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def der_items(values: Iterable[Any], kind: str) -> list[bytes]:
|
|
130
|
+
"""DER bytes for each certificate, CRL or OCSP response in *values*."""
|
|
131
|
+
if isinstance(values, (bytes, bytearray)):
|
|
132
|
+
raise TypeError(f"{kind} must be a sequence of items, not one bytes value")
|
|
133
|
+
out: list[bytes] = []
|
|
134
|
+
for value in values:
|
|
135
|
+
if isinstance(value, (bytes, bytearray)):
|
|
136
|
+
out.append(bytes(value))
|
|
137
|
+
elif isinstance(
|
|
138
|
+
value,
|
|
139
|
+
(x509.Certificate, x509.CertificateRevocationList, ocsp.OCSPResponse),
|
|
140
|
+
):
|
|
141
|
+
out.append(value.public_bytes(serialization.Encoding.DER))
|
|
142
|
+
else:
|
|
143
|
+
raise TypeError(
|
|
144
|
+
f"{kind} items must be DER bytes or cryptography objects, "
|
|
145
|
+
f"not {type(value).__name__}"
|
|
146
|
+
)
|
|
147
|
+
return out
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class PendingSignature:
|
|
152
|
+
"""A signature to put into field *field* when the document is saved."""
|
|
153
|
+
|
|
154
|
+
field: str
|
|
155
|
+
certificate: Any
|
|
156
|
+
private_key: Any
|
|
157
|
+
extra_certificates: tuple[Any, ...]
|
|
158
|
+
reason: str | None
|
|
159
|
+
location: str | None
|
|
160
|
+
contact: str | None
|
|
161
|
+
signer_name: str | None
|
|
162
|
+
pades: bool
|
|
163
|
+
timestamp_url: str | None
|
|
164
|
+
timestamp_authority: tuple[Any, Any] | None
|
|
165
|
+
timestamp_timeout: float
|
|
166
|
+
certify: int | None
|
|
167
|
+
#: How the signature is drawn, or ``None`` for an invisible one. It is not
|
|
168
|
+
#: used here: the appearance is written into the widget before the document
|
|
169
|
+
#: is serialized, so the signature covers it (see
|
|
170
|
+
#: ``Document._flush_signature_appearances``).
|
|
171
|
+
appearance: Any = None
|
|
172
|
+
|
|
173
|
+
def apply(self, data: bytes, *, encryption: Any, limits: Any) -> bytes:
|
|
174
|
+
return sign_field(
|
|
175
|
+
data,
|
|
176
|
+
self.field,
|
|
177
|
+
self.certificate,
|
|
178
|
+
self.private_key,
|
|
179
|
+
pades=self.pades,
|
|
180
|
+
reason=self.reason,
|
|
181
|
+
location=self.location,
|
|
182
|
+
contact=self.contact,
|
|
183
|
+
signer_name=self.signer_name,
|
|
184
|
+
extra_certs=list(self.extra_certificates) or None,
|
|
185
|
+
tsa=self.timestamp_authority,
|
|
186
|
+
timestamp_url=self.timestamp_url,
|
|
187
|
+
timestamp_timeout=self.timestamp_timeout,
|
|
188
|
+
certify_permissions=self.certify,
|
|
189
|
+
limits=limits,
|
|
190
|
+
encryption=encryption,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@dataclass(frozen=True)
|
|
195
|
+
class PendingLtv:
|
|
196
|
+
"""A ``/DSS`` to add, or extend, when the document is saved."""
|
|
197
|
+
|
|
198
|
+
material: DssMaterial
|
|
199
|
+
|
|
200
|
+
def apply(self, data: bytes, *, encryption: Any, limits: Any) -> bytes:
|
|
201
|
+
return enable_ltv(data, extra=self.material, encryption=encryption)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass(frozen=True)
|
|
205
|
+
class PendingDocumentTimestamp:
|
|
206
|
+
"""A document timestamp (``ETSI.RFC3161``) to append when the document is saved."""
|
|
207
|
+
|
|
208
|
+
timestamp_url: str | None
|
|
209
|
+
timestamp_authority: tuple[Any, Any] | None
|
|
210
|
+
timeout: float
|
|
211
|
+
|
|
212
|
+
def apply(self, data: bytes, *, encryption: Any, limits: Any) -> bytes:
|
|
213
|
+
return add_document_timestamp(
|
|
214
|
+
data,
|
|
215
|
+
tsa=self.timestamp_authority,
|
|
216
|
+
timestamp_url=self.timestamp_url,
|
|
217
|
+
timeout=self.timeout,
|
|
218
|
+
encryption=encryption,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
PendingOperation = PendingSignature | PendingLtv | PendingDocumentTimestamp
|
aspose_pdf/_version.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Version helpers for the ``aspose_pdf`` package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
6
|
+
|
|
7
|
+
__release_version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _detect_installed_version() -> str:
|
|
11
|
+
try:
|
|
12
|
+
return version("aspose-pdf-foss-for-python")
|
|
13
|
+
except PackageNotFoundError:
|
|
14
|
+
return __release_version__
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__version__ = _detect_installed_version()
|
aspose_pdf/actions.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""What a document, a page or a field does when something happens to it.
|
|
2
|
+
|
|
3
|
+
ISO 32000-1 12.6.3: an *additional-actions* dictionary (``/AA``) maps a trigger
|
|
4
|
+
to an action. The triggers are different for each kind of owner, and naming one
|
|
5
|
+
that the owner has no trigger for is a mistake nothing would ever report -- a
|
|
6
|
+
viewer simply never fires it -- so each collection knows its own::
|
|
7
|
+
|
|
8
|
+
field.actions["validate"] = JavaScriptAction("if (event.value < 0) ...")
|
|
9
|
+
page.actions["open"] = JavaScriptAction("app.alert('page one')")
|
|
10
|
+
document.actions["will_print"] = JavaScriptAction("...")
|
|
11
|
+
|
|
12
|
+
The actions themselves are the ones in :mod:`aspose_pdf.interactive`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import Iterator
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from aspose_pdf.exceptions import PdfValidationException
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"DOCUMENT_TRIGGERS",
|
|
24
|
+
"FIELD_TRIGGERS",
|
|
25
|
+
"PAGE_TRIGGERS",
|
|
26
|
+
"ActionCollection",
|
|
27
|
+
"JavaScriptCollection",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
#: A form field's triggers (Table 196), and the widget's own (Table 195).
|
|
31
|
+
#: ``/K``, ``/F``, ``/V`` and ``/C`` belong on the field, the rest on its widget,
|
|
32
|
+
#: which is where a viewer looks for each -- the collection writes whichever
|
|
33
|
+
#: dictionary the trigger belongs to.
|
|
34
|
+
FIELD_TRIGGERS: dict[str, str] = {
|
|
35
|
+
"keystroke": "K",
|
|
36
|
+
"format": "F",
|
|
37
|
+
"validate": "V",
|
|
38
|
+
"calculate": "C",
|
|
39
|
+
"enter": "E",
|
|
40
|
+
"exit": "X",
|
|
41
|
+
"mouse_down": "D",
|
|
42
|
+
"mouse_up": "U",
|
|
43
|
+
"focus": "Fo",
|
|
44
|
+
"blur": "Bl",
|
|
45
|
+
"page_open": "PO",
|
|
46
|
+
"page_close": "PC",
|
|
47
|
+
"page_visible": "PV",
|
|
48
|
+
"page_invisible": "PI",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#: The triggers that belong on the *field* rather than on its widget.
|
|
52
|
+
FIELD_ONLY_TRIGGERS = frozenset({"keystroke", "format", "validate", "calculate"})
|
|
53
|
+
|
|
54
|
+
#: A page's triggers (Table 194).
|
|
55
|
+
PAGE_TRIGGERS: dict[str, str] = {"open": "O", "close": "C"}
|
|
56
|
+
|
|
57
|
+
#: The document's triggers (Table 197), on the catalog.
|
|
58
|
+
DOCUMENT_TRIGGERS: dict[str, str] = {
|
|
59
|
+
"will_close": "WC",
|
|
60
|
+
"will_save": "WS",
|
|
61
|
+
"did_save": "DS",
|
|
62
|
+
"will_print": "WP",
|
|
63
|
+
"did_print": "DP",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ActionCollection:
|
|
68
|
+
"""A mapping of trigger name to :class:`~aspose_pdf.interactive.Action`.
|
|
69
|
+
|
|
70
|
+
Reads go to the document each time, so the collection is never a stale copy.
|
|
71
|
+
Assigning ``None``, or deleting a key, removes the trigger; an action this API
|
|
72
|
+
has no class for is read as ``None`` and left in the file as it is, the way
|
|
73
|
+
every other typed view in the package treats one.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
__slots__ = ("_read", "_triggers", "_what", "_write")
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
triggers: dict[str, str],
|
|
81
|
+
read: Any,
|
|
82
|
+
write: Any,
|
|
83
|
+
what: str,
|
|
84
|
+
) -> None:
|
|
85
|
+
self._triggers = triggers
|
|
86
|
+
self._read = read
|
|
87
|
+
self._write = write
|
|
88
|
+
self._what = what
|
|
89
|
+
|
|
90
|
+
def _key(self, trigger: str) -> str:
|
|
91
|
+
"""The ``/AA`` entry *trigger* names, or a refusal listing the ones there are."""
|
|
92
|
+
if not isinstance(trigger, str):
|
|
93
|
+
raise PdfValidationException(f"A {self._what} trigger is named by a string.")
|
|
94
|
+
key = trigger.strip().lower().replace("-", "_").replace(" ", "_")
|
|
95
|
+
if key in self._triggers:
|
|
96
|
+
return self._triggers[key]
|
|
97
|
+
# The PDF key itself is accepted, so code that knows the standard can say
|
|
98
|
+
# what it means in the standard's own terms.
|
|
99
|
+
if trigger in self._triggers.values():
|
|
100
|
+
return trigger
|
|
101
|
+
raise PdfValidationException(
|
|
102
|
+
f"{trigger!r} is not a {self._what} trigger; use one of: "
|
|
103
|
+
+ ", ".join(sorted(self._triggers))
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def _name_of(self, key: str) -> str:
|
|
107
|
+
"""The friendly name of a ``/AA`` entry, or the entry itself."""
|
|
108
|
+
for name, entry in self._triggers.items():
|
|
109
|
+
if entry == key:
|
|
110
|
+
return name
|
|
111
|
+
return key
|
|
112
|
+
|
|
113
|
+
def __len__(self) -> int:
|
|
114
|
+
return len(self._read())
|
|
115
|
+
|
|
116
|
+
def __iter__(self) -> Iterator[str]:
|
|
117
|
+
return iter(sorted(self._name_of(key) for key in self._read()))
|
|
118
|
+
|
|
119
|
+
def __contains__(self, trigger: object) -> bool:
|
|
120
|
+
if not isinstance(trigger, str):
|
|
121
|
+
return False
|
|
122
|
+
try:
|
|
123
|
+
return self._key(trigger) in self._read()
|
|
124
|
+
except PdfValidationException:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
def __getitem__(self, trigger: str) -> Any:
|
|
128
|
+
key = self._key(trigger)
|
|
129
|
+
actions = self._read()
|
|
130
|
+
if key not in actions:
|
|
131
|
+
raise KeyError(trigger)
|
|
132
|
+
return actions[key]
|
|
133
|
+
|
|
134
|
+
def get(self, trigger: str, default: Any = None) -> Any:
|
|
135
|
+
"""The action *trigger* fires, or *default* when it has none.
|
|
136
|
+
|
|
137
|
+
A trigger that *is* set but whose action this API has no class for reads
|
|
138
|
+
as ``None`` rather than as *default*: the action is there, and it is only
|
|
139
|
+
this typed view that cannot describe it.
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
return self[trigger]
|
|
143
|
+
except KeyError:
|
|
144
|
+
return default
|
|
145
|
+
|
|
146
|
+
def __setitem__(self, trigger: str, action: Any) -> None:
|
|
147
|
+
self._write(self._key(trigger), action)
|
|
148
|
+
|
|
149
|
+
def __delitem__(self, trigger: str) -> None:
|
|
150
|
+
key = self._key(trigger)
|
|
151
|
+
if key not in self._read():
|
|
152
|
+
raise KeyError(trigger)
|
|
153
|
+
self._write(key, None)
|
|
154
|
+
|
|
155
|
+
def set(self, trigger: str, action: Any) -> None:
|
|
156
|
+
"""Set *trigger* to fire *action*; ``None`` removes it."""
|
|
157
|
+
self[trigger] = action
|
|
158
|
+
|
|
159
|
+
def remove(self, trigger: str) -> bool:
|
|
160
|
+
"""Remove *trigger*; ``True`` when it had an action."""
|
|
161
|
+
key = self._key(trigger)
|
|
162
|
+
if key not in self._read():
|
|
163
|
+
return False
|
|
164
|
+
self._write(key, None)
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
def clear(self) -> None:
|
|
168
|
+
"""Remove every trigger."""
|
|
169
|
+
for key in list(self._read()):
|
|
170
|
+
self._write(key, None)
|
|
171
|
+
|
|
172
|
+
def keys(self) -> list[str]:
|
|
173
|
+
return list(self)
|
|
174
|
+
|
|
175
|
+
def values(self) -> list[Any]:
|
|
176
|
+
return [self[name] for name in self]
|
|
177
|
+
|
|
178
|
+
def items(self) -> list[tuple[str, Any]]:
|
|
179
|
+
return [(name, self[name]) for name in self]
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def triggers(self) -> tuple[str, ...]:
|
|
183
|
+
"""Every trigger this owner has, whether it is set or not."""
|
|
184
|
+
return tuple(sorted(self._triggers))
|
|
185
|
+
|
|
186
|
+
def __repr__(self) -> str:
|
|
187
|
+
return f"ActionCollection({self._what}, {self.keys()!r})"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class JavaScriptCollection:
|
|
191
|
+
"""The document's own scripts, as a mapping of name to source.
|
|
192
|
+
|
|
193
|
+
ISO 32000-1 12.6.4.17: these live in the catalog's ``/Names /JavaScript`` name
|
|
194
|
+
tree, and a viewer runs them when the document opens -- in **key order**, so
|
|
195
|
+
the names are what decides which runs first. This is where the functions a
|
|
196
|
+
field's ``/AA`` script calls are defined.
|
|
197
|
+
|
|
198
|
+
Reads go to the document each time. A name is encoded with latin-1, the one
|
|
199
|
+
codec that round-trips a name tree's byte keys unchanged.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
__slots__ = ("_document",)
|
|
203
|
+
|
|
204
|
+
def __init__(self, document: Any) -> None:
|
|
205
|
+
self._document = document
|
|
206
|
+
|
|
207
|
+
def _engine(self) -> Any:
|
|
208
|
+
self._document._ensure_not_disposed()
|
|
209
|
+
engine = self._document._engine_pdf
|
|
210
|
+
if engine is None:
|
|
211
|
+
from aspose_pdf.exceptions import AsposePdfException
|
|
212
|
+
|
|
213
|
+
raise AsposePdfException("No document loaded")
|
|
214
|
+
return engine
|
|
215
|
+
|
|
216
|
+
def _scripts(self) -> dict[str, str]:
|
|
217
|
+
return self._engine().document_javascript()
|
|
218
|
+
|
|
219
|
+
def __len__(self) -> int:
|
|
220
|
+
return len(self._scripts())
|
|
221
|
+
|
|
222
|
+
def __iter__(self) -> Iterator[str]:
|
|
223
|
+
"""The names, in the order a viewer runs them: key order."""
|
|
224
|
+
return iter(sorted(self._scripts()))
|
|
225
|
+
|
|
226
|
+
def __contains__(self, name: object) -> bool:
|
|
227
|
+
return isinstance(name, str) and name in self._scripts()
|
|
228
|
+
|
|
229
|
+
def __getitem__(self, name: str) -> str:
|
|
230
|
+
scripts = self._scripts()
|
|
231
|
+
if name not in scripts:
|
|
232
|
+
raise KeyError(name)
|
|
233
|
+
return scripts[name]
|
|
234
|
+
|
|
235
|
+
def get(self, name: str, default: Any = None) -> Any:
|
|
236
|
+
"""The script *name* holds, or *default*."""
|
|
237
|
+
return self._scripts().get(name, default)
|
|
238
|
+
|
|
239
|
+
def __setitem__(self, name: str, script: str) -> None:
|
|
240
|
+
self.add(name, script)
|
|
241
|
+
|
|
242
|
+
def add(self, name: str, script: str) -> None:
|
|
243
|
+
"""Define (or replace) the script *name*."""
|
|
244
|
+
if not isinstance(script, str):
|
|
245
|
+
raise PdfValidationException("A document script is source text.")
|
|
246
|
+
self._engine().set_document_javascript(name, script)
|
|
247
|
+
|
|
248
|
+
def __delitem__(self, name: str) -> None:
|
|
249
|
+
if not self.remove(name):
|
|
250
|
+
raise KeyError(name)
|
|
251
|
+
|
|
252
|
+
def remove(self, name: str) -> bool:
|
|
253
|
+
"""Remove the script *name*; ``True`` when there was one."""
|
|
254
|
+
if name not in self._scripts():
|
|
255
|
+
return False
|
|
256
|
+
self._engine().set_document_javascript(name, None)
|
|
257
|
+
return True
|
|
258
|
+
|
|
259
|
+
def clear(self) -> None:
|
|
260
|
+
"""Remove every document-level script."""
|
|
261
|
+
self._engine().clear_document_javascript()
|
|
262
|
+
|
|
263
|
+
def keys(self) -> list[str]:
|
|
264
|
+
return list(self)
|
|
265
|
+
|
|
266
|
+
def values(self) -> list[str]:
|
|
267
|
+
return [self[name] for name in self]
|
|
268
|
+
|
|
269
|
+
def items(self) -> list[tuple[str, str]]:
|
|
270
|
+
return [(name, self[name]) for name in self]
|
|
271
|
+
|
|
272
|
+
def __repr__(self) -> str:
|
|
273
|
+
return f"JavaScriptCollection({self.keys()!r})"
|