Source code for pdfnaut.objects.actions

from __future__ import annotations

from typing import Annotated, Literal, cast

from typing_extensions import Self

from pdfnaut.common.dictmodels import dictmodel, field
from pdfnaut.cos.helpers import is_null_like
from pdfnaut.cos.objects.base import PdfName
from pdfnaut.cos.objects.containers import PdfArray, PdfDictionary
from pdfnaut.objects.destinations import Destination, DestType, NamedDestination

ActionKind = Literal[
    "GoTo",
    "GoToR",
    "GoToE",
    "GoToDPart",
    "Launch",
    "Thread",
    "URI",
    "Sound",
    "Movie",
    "Hide",
    "Named",
    "SubmitForm",
    "ResetForm",
    "ImportData",
    "SetOCGState",
    "Rendition",
    "Trans",
    "GoTo3DView",
    "JavaScript",
    "RichMediaExecute",
]


[docs] def action_into(mapping: PdfDictionary) -> Action: """Converts a dictionary ``mapping`` into a corresponding :class:`Action` subclass.""" subtype_name = cast(PdfName, mapping["S"]) subtype = cast(ActionKind, subtype_name.value.decode()) if subtype == "GoTo": return GoToAction.from_dict(mapping) elif subtype == "URI": return URIAction.from_dict(mapping) return Action.from_dict(mapping)
[docs] @dictmodel class Action(PdfDictionary): """An action instructs the PDF reader to perform an action such as opening an application, going to a page in the document, or playing a sound, when activating an annotation or outline item. See ISO 32000-2:2020 § 12.6 "Actions" for details. """ subtype: Annotated[ActionKind, "name"] = field("S") """The type of action. Refer to ISO 32000-2:2020 "Table 201 - Action types" for available types. """ @classmethod def from_dict(cls, mapping: PdfDictionary) -> Self: subtype = cast(PdfName, mapping["S"]).value.decode() action = cls(subtype=cast(ActionKind, subtype)) action.data = mapping.data return action
[docs] def __init__( self, subtype: ActionKind, next_action: list[Action] | Action | None = None ) -> None: super().__init__() self.subtype = subtype self.next_action = next_action
@property def next_action(self) -> list[Action] | Action | None: """The next action or sequence of actions that shall be performed after this action.""" next_seq = self.get("Next") if is_null_like(next_seq): return if isinstance(next_seq, PdfArray): next_seq = cast(PdfArray[PdfDictionary], next_seq) return [action_into(act) for act in next_seq] next_seq = cast(PdfDictionary, next_seq) return action_into(next_seq) @next_action.setter def next_action(self, action: list[Action] | Action | None = None) -> None: if action is None: self.pop("Next", None) elif isinstance(action, Action): self["Next"] = PdfDictionary(action.data) else: self["Next"] = PdfArray([PdfDictionary(act.data) for act in action])
[docs] @dictmodel class GoToAction(Action): """A go-to action changes the view to a specified destination. See ISO 32000-2:2020 § 12.6.4.2 "Go-To actions" for details. """ @classmethod def from_dict(cls, mapping: PdfDictionary) -> Self: action = cls(Destination()) action.data = mapping.data return action
[docs] def __init__( self, destination: DestType, next_action: list[Action] | Action | None = None ) -> None: super().__init__("GoTo", next_action) self.destination = destination
@property def destination(self) -> DestType: """The destination to jump to.""" dest = self["D"] if isinstance(dest, PdfArray): return Destination(dest) return cast(NamedDestination, self["D"]) @destination.setter def destination(self, dest: DestType) -> None: if isinstance(dest, Destination): self["D"] = PdfArray(dest.data) else: self["D"] = dest def __repr__(self) -> str: return f"{self.__class__.__name__}(destination={self.destination!r})"
[docs] @dictmodel class URIAction(Action): """A URI action causes a URI or uniform resource identifier to be resolved. See ISO 32000-2:2020 § 12.6.4.8 "URI actions" for details. """ uri: str = field("URI") """The uniform resource identifier (URI) to resolve.""" is_map: bool = False """Whether to track the mouse position when the URI is resolved.""" @classmethod def from_dict(cls, mapping: PdfDictionary) -> Self: action = cls("") action.data = mapping.data return action
[docs] def __init__( self, uri: str, is_map: bool = False, next_action: list[Action] | Action | None = None ) -> None: super().__init__("URI", next_action) self.uri = uri self.is_map = is_map