-
Notifications
You must be signed in to change notification settings - Fork 1.6k
ENH: Add actions base class #3552
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
j-t-1
wants to merge
201
commits into
py-pdf:main
Choose a base branch
from
j-t-1:actions
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
201 commits
Select commit
Hold shift + click to select a range
d2a57ab
ENH: Add actions base class
j-t-1 388b902
Fix error
j-t-1 695a2c9
Increase code coverage
j-t-1 ef335cc
Fix error
j-t-1 efbb46e
Add to test
j-t-1 82c9e5d
Debug test
j-t-1 5ea4e6c
Debug
j-t-1 7f4077a
Modify actions
j-t-1 dc27124
Revert
j-t-1 52cee63
Fix test
j-t-1 03db2c6
Fix test
j-t-1 a44f4af
Fix coverage
j-t-1 4593f1c
Fix test_page_add_action
j-t-1 486b215
More fixes
j-t-1 77cceb0
Improve tests
j-t-1 84b5215
Fixes
j-t-1 a3fe3c3
Replace single quotes with double quotes
j-t-1 61975f0
Update actions
j-t-1 6e0b128
Fix tests
j-t-1 4c5b1fe
Add assert
j-t-1 e74df20
Replace single quotes with double quotes
j-t-1 2def725
Add test
j-t-1 be4a454
Fix docstring
j-t-1 3215f49
Add coverage
j-t-1 6b2220c
Fix error
j-t-1 c3a0612
Fix error
j-t-1 fd9a071
Fix error
j-t-1 40f00cc
Fix error
j-t-1 b271cd0
Add actions.rst
j-t-1 055a0d1
Add to toctree
j-t-1 9512011
Fix coverage
j-t-1 ae04f7d
Fix coverage
j-t-1 b51c376
Fix test error
j-t-1 5e5a1d9
Merge branch 'main' into actions
j-t-1 5b77d55
Merge branch 'main' into actions
j-t-1 91149f5
Fix coverage
j-t-1 dbaa92f
Fix coverage
j-t-1 bbe9979
Change error type and fix mypy error
j-t-1 2dc05b5
Fix error
j-t-1 0894957
Merge branch 'main' into actions
j-t-1 990f7a1
Add infinite loop mitigation
j-t-1 50d1632
import RESOURCE_ROOT
j-t-1 eafc441
Remove unused import
j-t-1 cfb068a
Add ArrayObject to cycle detection
j-t-1 d76f5d0
Change cycle detection
j-t-1 eaca2be
ENH: Add actions base class
j-t-1 449b2a6
ENH: Add actions base class
j-t-1 e039151
ENH: Add actions base class
j-t-1 4657e44
Fix error
j-t-1 0c4f51a
Increase code coverage
j-t-1 b5a7126
Fix error
j-t-1 b7d8dcc
Add to test
j-t-1 68687e4
Debug test
j-t-1 68c8859
Debug
j-t-1 7495ace
Modify actions
j-t-1 8c44c37
Revert
j-t-1 2a1e285
Fix test
j-t-1 ecdd69c
Fix test
j-t-1 4560c46
Fix coverage
j-t-1 397eda5
Fix test_page_add_action
j-t-1 5050c99
More fixes
j-t-1 dac0a1d
Improve tests
j-t-1 884b534
Fixes
j-t-1 328dc0a
Replace single quotes with double quotes
j-t-1 d774d44
Update actions
j-t-1 a350a48
Fix tests
j-t-1 ad86aa2
Add assert
j-t-1 c3e08dd
Replace single quotes with double quotes
j-t-1 b37338e
Add test
j-t-1 a381dfc
Fix docstring
j-t-1 8b05fec
Add coverage
j-t-1 0ca5987
Fix error
j-t-1 4a4b6d9
Fix error
j-t-1 dd57257
Fix error
j-t-1 dbd97ff
Fix error
j-t-1 0248f1d
Add actions.rst
j-t-1 b558acf
Add to toctree
j-t-1 b9ad57d
Fix coverage
j-t-1 b8f040d
Fix coverage
j-t-1 1ad0939
Fix test error
j-t-1 8530c35
Fix coverage
j-t-1 0e88167
Fix coverage
j-t-1 6d6ec5b
Change error type and fix mypy error
j-t-1 81a9daa
Fix error
j-t-1 cda8f63
Add infinite loop mitigation
j-t-1 ee42260
import RESOURCE_ROOT
j-t-1 3f51390
Remove unused import
j-t-1 14bca27
Add ArrayObject to cycle detection
j-t-1 b955577
Change cycle detection
j-t-1 29f2234
ENH: Add actions base class
j-t-1 a79df96
ENH: Add actions base class
j-t-1 e0951b8
ENH: Add actions base class
j-t-1 4f0d20f
ENH: Add actions base class
j-t-1 afacb0e
ENH: Add actions base class
j-t-1 6179167
ENH: Add actions base class
j-t-1 1770fd2
ENH: Add actions base class
j-t-1 252f9c8
ENH: Add actions base class
j-t-1 5d68369
ENH: Add actions base class
j-t-1 5df6084
ENH: Add actions base class
j-t-1 54491f2
ENH: Add actions base class
j-t-1 7decde9
ENH: Add actions base class
j-t-1 7c35f98
ENH: Add actions base class
j-t-1 114e29b
ENH: Add actions base class
j-t-1 c72d531
ENH: Add actions base class
j-t-1 f9b7028
Merge branch 'main' into actions
j-t-1 3bbb842
ENH: Update test_actions.py
j-t-1 6541b91
ENT: Update test_actions.py
j-t-1 d70790a
ENH: Update test_actions.py
j-t-1 090a8bd
ENH: Update test_actions.py
j-t-1 d8ea299
ENH: Improve tests
j-t-1 236107a
ENH: Update test_actions.py
j-t-1 ddb87dd
ENH: Update
j-t-1 d8da741
ENH: Add actions base class
j-t-1 a658c71
ENH: Add actions base class
j-t-1 0a64847
ENH: Add actions base class
j-t-1 5f1c587
ENH: Add actions base class
j-t-1 df84a93
ENH: Add actions base class
j-t-1 c8b595b
ENH: Add actions base class
j-t-1 1b6eebe
Update pypdf/actions/_actions.py
j-t-1 838af1e
Update tests/test_actions.py
j-t-1 2ef3ea2
ENH: Update
j-t-1 0b06a27
ENH: Update
j-t-1 7518d55
ENH: Update
j-t-1 f666a7b
ENH: Update
j-t-1 1a79bc3
ENH: Update
j-t-1 7b5c9b5
ENH: Update
j-t-1 152c3ea
ENH: Add actions
j-t-1 824d4e2
ENH: Add action base class
j-t-1 ea38b9c
ENH: Add action base class
j-t-1 8855b57
ENH: Add action base class
j-t-1 f1b9127
ENH: Add action base class
j-t-1 2bd082e
ENH: Add action base class
j-t-1 012ea3e
ENH: Add action base class
j-t-1 6a13653
ENH: Add actions base class
j-t-1 670a7cc
ENH: Add actions base
j-t-1 fc1d687
ENH: Add actions base class
j-t-1 3fdcc0d
ENH: Add actions base class
j-t-1 d185997
ENH: Add actions base class
j-t-1 868e6c5
ENH: Fix errros
j-t-1 29b7f8e
ENH: Add actions base class
j-t-1 75bf15c
Merge branch 'main' into actions
j-t-1 e938ade
ENH: Add actions base class
j-t-1 368d964
ENH: Add actions base class
j-t-1 4389848
ENH: Add actions base class
j-t-1 1fd79dd
ENH: Add actions base class
j-t-1 c720eab
ENH: Add actions base class
j-t-1 2c4bc57
ENH: Add actions base class
j-t-1 5f6ee96
ENH: Add actions base class
j-t-1 0bc854b
ENH: Add actions base class
j-t-1 6fdfd70
ENH: Add actions base class
j-t-1 c946f03
ENH: Add actions base class
j-t-1 73cc4ad
ENH: Add actions base class
j-t-1 7253b20
ENH: Add actions base class
j-t-1 65d4fc2
ENH: Add actions base class
j-t-1 d4be0bf
ENH: Add actions base class
j-t-1 165e0ca
ENH: Add actions base class
j-t-1 01a1618
ENH: Add actions base class
j-t-1 6f87edd
ENH: Add actions base class
j-t-1 20f27fc
ENH: Add actions base class
j-t-1 332ea19
ENH: Add actions base class
j-t-1 048d672
ENH: Add actions base class
j-t-1 161e438
ENH: Fix
j-t-1 5f5feab
ENH: Fix
j-t-1 05cb20c
ENH: Add actions base class
j-t-1 ff17a3f
ENH: Fix
j-t-1 669cfd8
ENH: Fix
j-t-1 3979839
ENH: Add actions base class
j-t-1 ba619a4
ENH: Add actions base class
j-t-1 e486125
ENH: Add actions base class
j-t-1 5330235
ENH: Fix
j-t-1 5f03dfd
ENH: Add actions base class
j-t-1 3e7e714
ENH: Add actions base class
j-t-1 c8cae88
ENH: Add actions base class
j-t-1 a0756d2
Merge branch 'main' into actions
stefan6419846 efcd908
ENH: Fix
j-t-1 689e6c9
ENH: Fix
j-t-1 82d8bb6
Apply suggestions from code review
j-t-1 9d02048
ENH: Fix
j-t-1 86a7c40
Update pypdf/actions/_actions.py
j-t-1 c3a4fab
ENH: Add actions base class
j-t-1 2a5bd04
ENH: Add actions base class
j-t-1 c9c13f2
ENH: Add actions base class
j-t-1 552a64f
ENH: Add actions base class
j-t-1 d95ba3e
ENH: Add actions base class
j-t-1 4fee484
ENH: Add actions base class
j-t-1 a6bdf24
Update pypdf/actions/_actions.py
j-t-1 1165401
Update tests/test_actions.py
j-t-1 ebf964d
Update pypdf/actions/_actions.py
j-t-1 3422ecf
Apply suggestions from code review
j-t-1 30bda9e
ENH: Add actions base class
j-t-1 937ff1f
Merge branch 'actions' of https://github.com/j-t-1/pypdf into actions
j-t-1 28485d2
ENH: Add actions base class
j-t-1 7c531ad
ENH: Fix error
j-t-1 7075025
ENH: Fix
j-t-1 01b7c8b
ENH: Fix
j-t-1 2474478
ENH: Fix
j-t-1 8097221
ENH: Fix
j-t-1 f414ed3
ENH: Fix
j-t-1 2d39b9e
ENH: Fix
j-t-1 392ead8
ENH: Fix
j-t-1 dde8004
Merge branch 'main' into actions
j-t-1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| Actions | ||
| ------- | ||
|
|
||
| .. automodule:: pypdf.actions | ||
| :members: | ||
| :undoc-members: | ||
| :show-inheritance: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| """ | ||
| PDF includes a wide variety of standard action types, whose characteristics and | ||
| behaviour are defined by an action dictionary. These are defined in this | ||
| submodule. | ||
|
|
||
| Trigger events are the other component of actions, and are specific to their | ||
| associated object. | ||
| """ | ||
|
|
||
|
|
||
| from ._actions import Action, JavaScript, PageTrigger | ||
|
|
||
| __all__ = [ | ||
| "Action", | ||
| "JavaScript", | ||
| "PageTrigger", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| """Action types""" | ||
| import sys | ||
| from abc import ABC | ||
| from enum import Enum, unique | ||
| from typing import ( | ||
| TYPE_CHECKING, | ||
| cast, | ||
| ) | ||
|
|
||
| from .._utils import logger_warning | ||
| from ..errors import ParseError | ||
| from ..generic import ( | ||
| ArrayObject, | ||
| DictionaryObject, | ||
| NameObject, | ||
| NullObject, | ||
| TextStringObject, | ||
| is_null_or_none, | ||
| ) | ||
|
|
||
| if sys.version_info >= (3, 11): | ||
| from enum import StrEnum | ||
| else: | ||
| class StrEnum(str, Enum): | ||
| def __str__(self) -> str: | ||
| return str(self.value) | ||
|
|
||
| if TYPE_CHECKING: | ||
| from .._page import PageObject | ||
|
|
||
|
|
||
| @unique | ||
| class PageTrigger(StrEnum): | ||
| """Trigger event entries in a page object's additional-actions dictionary.""" | ||
| OPEN = "open" | ||
| """A :py:class:`~pypdf.actions.PageTrigger` object triggering an action when the page is opened.""" | ||
| CLOSE = "close" | ||
| """A :py:class:`~pypdf.actions.PageTrigger` object triggering an action when the page is closed.""" | ||
|
|
||
|
|
||
| class Action(DictionaryObject, ABC): | ||
| """An action dictionary defines the characteristics and behaviour of an action.""" | ||
| def __init__(self) -> None: | ||
| super().__init__() | ||
| self[NameObject("/Type")] = NameObject("/Action") | ||
| # The next action or sequence of actions that shall be performed after the action | ||
| # represented by this dictionary. The value is either a single action dictionary | ||
| # or an array of action dictionaries that shall be performed in order. | ||
| self[NameObject("/Next")] = NullObject() # Optional | ||
|
|
||
| @classmethod | ||
| def _create_new(cls, page: "PageObject", trigger: PageTrigger, action: "Action") -> None: | ||
| """ | ||
| Create a new action and add it to the page. | ||
|
|
||
| Args: | ||
| page: The page to add the action. | ||
| trigger: A :py:class:`~pypdf.actions.PageTrigger` object. | ||
| action: A :py:class:`~pypdf.actions.Action` object. | ||
| """ | ||
| trigger_name = NameObject("/O") if PageTrigger(trigger).value == PageTrigger.OPEN else NameObject("/C") | ||
|
|
||
| if "/AA" not in page: | ||
| # Additional actions key not present | ||
| page[NameObject("/AA")] = DictionaryObject( | ||
| {trigger_name: action} | ||
| ) | ||
| return | ||
|
|
||
| if isinstance(page["/AA"], NullObject): | ||
| page[NameObject("/AA")] = DictionaryObject() | ||
|
|
||
| if not isinstance(page["/AA"].get_object(), DictionaryObject): | ||
| if page.pdf is not None and getattr(page.pdf, "strict", False): | ||
| current_type = type(page["/AA"]) | ||
| raise ParseError( | ||
| f"The AA entry of the page should be a DictionaryObject. " | ||
| f"It currently is a {current_type}." | ||
| ) | ||
| logger_warning( | ||
| "The AA entry of the page should be a DictionaryObject. It currently is a %(type)s.", | ||
| source=__name__, | ||
| type=type(page["/AA"]) | ||
| ) | ||
| return | ||
|
|
||
| additional_actions = cast(DictionaryObject, page["/AA"]) | ||
|
|
||
| if is_null_or_none(additional_actions.get(trigger_name)): | ||
| additional_actions.update({trigger_name: action}) | ||
| return | ||
|
|
||
| # The action dictionary's Next entry allows sequences of actions to be | ||
| # chained together. For example, the effect of clicking a link | ||
| # annotation with the mouse can be to play a sound, jump to a new | ||
| # page, and start up a movie. Note that the Next entry is not | ||
| # restricted to a single action but can contain an array of actions, | ||
| # each of which in turn can have a Next entry of its own. | ||
| # §12.6.2 Action dictionaries ISO 32000-2:2020 | ||
| head = current = additional_actions.get(trigger_name) | ||
| if not isinstance(head, DictionaryObject): | ||
| raise ParseError( | ||
| f"The type in a page object's additional-actions key must be a DictionaryObject: " | ||
| f"received type {type(head)}" | ||
| ) | ||
| current = cast(DictionaryObject, current) | ||
|
|
||
| visited = set() | ||
| while True: | ||
| next_ = current.get("/Next", None) | ||
|
|
||
| if is_null_or_none(next_): | ||
| break | ||
|
|
||
| if not isinstance(next_, (ArrayObject, DictionaryObject)): | ||
| raise TypeError( | ||
| f"An action dictionary’s Next entry must be an Action dictionary " | ||
| f"or an array of Action dictionaries: received type {type(next_)}" | ||
| ) | ||
|
|
||
| id_ = id(next_) | ||
| if id_ in visited: | ||
| logger_warning("Detected cycle in the action tree for %(current)s", source=__name__, current=current) | ||
| break | ||
| visited.add(id_) | ||
|
|
||
| if isinstance(next_, ArrayObject): | ||
| current = next_[-1] | ||
| else: | ||
| current = next_ | ||
|
|
||
| if not is_null_or_none(next_ := current.get("/Next")) and id(next_) in visited: | ||
| logger_warning("Detected cycle in the action tree for %(current)s", source=__name__, current=current) | ||
|
|
||
| current[NameObject("/Next")] = action | ||
| additional_actions.update({trigger_name: head}) | ||
|
|
||
| @classmethod | ||
| def _delete(cls, page: "PageObject", trigger: PageTrigger) -> None: | ||
| """ | ||
| Delete an object on the page. | ||
|
|
||
| Args: | ||
| page: The page to add the action. | ||
| trigger: A :py:class:`~pypdf.actions.PageTrigger` object. | ||
| """ | ||
| if "/AA" not in page: | ||
| return | ||
|
|
||
| trigger_name = NameObject("/O") if PageTrigger(trigger).value == PageTrigger.OPEN else NameObject("/C") | ||
|
|
||
| additional_actions = cast(DictionaryObject, page["/AA"]) | ||
|
|
||
| if trigger_name not in additional_actions: | ||
| return | ||
|
|
||
| del additional_actions[trigger_name] | ||
|
|
||
| if not additional_actions: | ||
| del page["/AA"] | ||
|
|
||
|
|
||
| class JavaScript(Action): | ||
|
stefan6419846 marked this conversation as resolved.
|
||
| """ | ||
| Upon invocation of an ECMAScript action, a PDF processor shall execute a | ||
| script that is written in the ECMAScript programming language. ECMAScript | ||
| extensions described in ISO/DIS 21757-1 shall also be allowed. | ||
| """ | ||
|
|
||
| def __init__(self, js: str) -> None: | ||
|
j-t-1 marked this conversation as resolved.
j-t-1 marked this conversation as resolved.
|
||
| """ | ||
| Initialize JavaScript with a string. | ||
|
|
||
| Args: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does not appear in the docs: https://pypdf--3552.org.readthedocs.build/en/3552/modules/actions.html Please check other cases and correct accordingly. |
||
| js: A text string containing the ECMAScript script to be executed. | ||
| """ | ||
| super().__init__() | ||
| self[NameObject("/S")] = NameObject("/JavaScript") | ||
| self[NameObject("/JS")] = TextStringObject(js) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -78,3 +78,7 @@ | |
| "/Projection", | ||
| "/RichMedia", | ||
| ] | ||
|
|
||
| ActionSubtype: TypeAlias = Literal[ | ||
| "/JavaScript", | ||
| ] | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.