Skip to content
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions pypdf/_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
from .generic import (
ArrayObject,
ContentStream,
DecodedStreamObject,
DictionaryObject,
EncodedStreamObject,
FloatObject,
Expand All @@ -78,6 +79,7 @@
PdfObject,
RectangleObject,
StreamObject,
TextStringObject,
is_null_or_none,
)

Expand Down Expand Up @@ -2149,6 +2151,48 @@ def annotations(self, value: Optional[ArrayObject]) -> None:
else:
self[NameObject("/Annots")] = value

def add_action(
self,
event: Literal["O", "C"] = "O",
action_type: Literal["JavaScript"] = "JavaScript",
action: str = ""
) -> None:
r"""
Add action which will launch on the open or close event of this
page.

Args:
event: "/O" or "/C", for open or close action respectively.
action_type: "JavaScript" is currently the only available action type.
action: Your JavaScript.

>>> output.add_action("/O", "JavaScript", 'app.alert("This is page " + this.pageNum);')
# Example: This will display the page number when the page is opened.
>>> output.add_action("/C", "JavaScript", 'app.alert("This is page " + this.pageNum);')
# Example: This will display the page number when the page is closed.

Note that this will replace any existing open or close event on this page.
Currently only an open or close event can be added, not both.
"""
if event not in {"/O", "/C"}:
raise ValueError('event must be "/O" or "/C"')

if action_type != "JavaScript":
raise ValueError('Currently the only action_type supported is "JavaScript"')

additional_actions = DictionaryObject()
self[NameObject("/AA")] = additional_actions
additional_actions[NameObject(event)] = DictionaryObject(
{
NameObject("/Type"): NameObject("/Action"),
NameObject("/S"): NameObject("/JavaScript"),
NameObject("/JS"): TextStringObject(f"{action}"),
}
)

action_object = DecodedStreamObject()
action_object.set_data(action.encode())


class _VirtualList(Sequence[PageObject]):
def __init__(
Expand Down
25 changes: 25 additions & 0 deletions tests/test_javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pytest

from pypdf import PdfReader, PdfWriter
from pypdf.generic import NameObject

# Configure path environment
TESTS_ROOT = Path(__file__).parent.resolve()
Expand Down Expand Up @@ -49,3 +50,27 @@ def get_javascript_name() -> Any:
assert (
first_js != second_js
), "add_js should add to the previous script in the catalog."


def test_page_add_js(pdf_file_writer):
page = pdf_file_writer.pages[0]

with pytest.raises(ValueError) as exc:
page.add_action("/xyzzy", "JavaScript", 'app.alert("This is page " + this.pageNum);')
assert (
exc.value.args[0] == 'event must be "/O" or "/C"'
)

with pytest.raises(ValueError) as exc:
page.add_action("/O", "xyzzy", 'app.alert("This is page " + this.pageNum);')
assert (
exc.value.args[0] == 'Currently the only action_type supported is "JavaScript"'
)

page.add_action("/O", "JavaScript", 'app.alert("This is page " + this.pageNum);')
expected = {"/O": {"/Type": "/Action", "/S": "/JavaScript", "/JS": 'app.alert("This is page " + this.pageNum);'}}
assert page[NameObject("/AA")] == expected

page.add_action("/C", "JavaScript", 'app.alert("This is page " + this.pageNum);')
expected = {"/C": {"/Type": "/Action", "/S": "/JavaScript", "/JS": 'app.alert("This is page " + this.pageNum);'}}
assert page[NameObject("/AA")] == expected
Loading