diff --git a/PyPDF2/_writer.py b/PyPDF2/_writer.py index 49085c1f4..12539900d 100644 --- a/PyPDF2/_writer.py +++ b/PyPDF2/_writer.py @@ -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. diff --git a/tests/test_writer.py b/tests/test_writer.py index 79bfd6f46..9ab514672 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -9,6 +9,7 @@ from PyPDF2.generic import ( IndirectObject, NameObject, + NumberObject, RectangleObject, StreamObject, TextStringObject, @@ -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