Skip to content
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

ENH: Add PdfWriter.open_destination property #1431

Merged
merged 11 commits into from Nov 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 48 additions & 0 deletions PyPDF2/_writer.py
Expand Up @@ -411,6 +411,54 @@ def insertBlankPage(
deprecate_with_replacement("insertBlankPage", "insert_blank_page")
return self.insert_blank_page(width, height, index)

@property
def open_destination(
self,
) -> Union[None, Destination, TextStringObject, ByteStringObject]:
"""
Property to access the opening destination ("/OpenAction" entry in the
PDF catalog).
it returns `None` if the entry does not exist is not set.

:param destination:.
the property can be set to a Destination, a Page or an string(NamedDest) or
None (to remove "/OpenAction")

(value stored in "/OpenAction" entry in the Pdf Catalog)
"""
if "/OpenAction" not in self._root_object:
return None
oa = self._root_object["/OpenAction"]
if isinstance(oa, (str, bytes)):
return create_string_object(str(oa))
elif isinstance(oa, ArrayObject):
try:
page, typ = oa[0:2] # type: ignore
array = oa[2:]
return Destination("OpenAction", page, typ, *array) # type: ignore
except Exception:
raise Exception(f"Invalid Destination {oa}")
else:
return None

@open_destination.setter
def open_destination(self, dest: Union[None, str, Destination, PageObject]) -> None:
if dest is None:
try:
del self._root_object["/OpenAction"]
except KeyError:
pass
elif isinstance(dest, str):
self._root_object[NameObject("/OpenAction")] = TextStringObject(dest)
elif isinstance(dest, Destination):
self._root_object[NameObject("/OpenAction")] = dest.dest_array
elif isinstance(dest, PageObject):
self._root_object[NameObject("/OpenAction")] = Destination(
"Opening",
dest.indirect_ref if dest.indirect_ref is not None else NullObject(),
TextStringObject("/Fit"),
).dest_array

def add_js(self, javascript: str) -> None:
"""
Add Javascript which will launch upon opening this PDF.
Expand Down
42 changes: 42 additions & 0 deletions tests/test_writer.py
Expand Up @@ -9,6 +9,7 @@
from PyPDF2.generic import (
IndirectObject,
NameObject,
NumberObject,
RectangleObject,
StreamObject,
TextStringObject,
Expand Down Expand Up @@ -813,3 +814,44 @@ def test_write_empty_stream():
with pytest.raises(ValueError) as exc:
writer.write("")
assert exc.value.args[0] == "Output(stream=) is empty."


def test_startup_dest():
pdf_file_writer = PdfWriter()
pdf_file_writer.append_pages_from_reader(PdfReader(RESOURCE_ROOT / "issue-604.pdf"))

assert pdf_file_writer.open_destination is None
pdf_file_writer.open_destination = pdf_file_writer.pages[9]
# checked also using Acrobrat to verify the good page is opened
op = pdf_file_writer._root_object["/OpenAction"]
assert op[0] == pdf_file_writer.pages[9].indirect_ref
assert op[1] == "/Fit"
op = pdf_file_writer.open_destination
assert op.raw_get("/Page") == pdf_file_writer.pages[9].indirect_ref
assert op["/Type"] == "/Fit"
pdf_file_writer.open_destination = op
assert pdf_file_writer.open_destination == op

# irrelevant, just for coverage
pdf_file_writer._root_object[NameObject("/OpenAction")][0] = NumberObject(0)
pdf_file_writer.open_destination
with pytest.raises(Exception) as exc:
del pdf_file_writer._root_object[NameObject("/OpenAction")][0]
pdf_file_writer.open_destination
assert "Invalid Destination" in str(exc.value)

pdf_file_writer.open_destination = "Test"
# checked also using Acrobrat to verify open_destination
op = pdf_file_writer._root_object["/OpenAction"]
assert isinstance(op, TextStringObject)
assert op == "Test"
op = pdf_file_writer.open_destination
assert isinstance(op, TextStringObject)
assert op == "Test"

# irrelevant, this is just for coverage
pdf_file_writer._root_object[NameObject("/OpenAction")] = NumberObject(0)
assert pdf_file_writer.open_destination is None
pdf_file_writer.open_destination = None
assert "/OpenAction" not in pdf_file_writer._root_object
pdf_file_writer.open_destination = None