From 8ea92655db7e40bd0ece67d40416df0cbba065ea Mon Sep 17 00:00:00 2001 From: Khalid Abdulla Date: Sun, 24 Apr 2022 12:19:11 +0100 Subject: [PATCH] MAINT: Make PdfFileMerger.addBookmark() behave life PdfFileWriters' (#339) People stumbled over this inconsistency: * #40 * https://stackoverflow.com/a/42991101/562769 This was also tested with: https://stackoverflow.com/questions/42941742/pypdf2-nested-bookmarks-with-same-name-not-working/42991101#comment73249244_42991101 --- PyPDF2/merger.py | 70 ++++++++++++++++++++++++++++++++------------ Tests/test_merger.py | 2 +- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/PyPDF2/merger.py b/PyPDF2/merger.py index 4a2271d68..6e74c52f8 100644 --- a/PyPDF2/merger.py +++ b/PyPDF2/merger.py @@ -498,7 +498,8 @@ def findBookmark(self, bookmark, root=None): return None - def addBookmark(self, title, pagenum, parent=None): + + def addBookmark(self, title, pagenum, parent=None, color=None, bold=False, italic=False, fit='/Fit', *args): """ Add a bookmark to this PDF file. @@ -506,28 +507,61 @@ def addBookmark(self, title, pagenum, parent=None): :param int pagenum: Page number this bookmark will point to. :param parent: A reference to a parent bookmark to create nested bookmarks. + :param tuple color: Color of the bookmark as a red, green, blue tuple + from 0.0 to 1.0 + :param bool bold: Bookmark is bold + :param bool italic: Bookmark is italic + :param str fit: The fit of the destination page. See + :meth:`addLink()` for details. """ - if parent is None: - iloc = [len(self.bookmarks)-1] - elif isinstance(parent, list): - iloc = parent + if len(self.output.getObject(self.output._pages)['/Kids']) > 0: + pageRef = self.output.getObject(self.output._pages)['/Kids'][pagenum] else: - iloc = self.findBookmark(parent) + pageRef = self.output.getObject(self.output._pages) - dest = Bookmark(TextStringObject(title), NumberObject(pagenum), NameObject('/FitH'), NumberObject(826)) + action = DictionaryObject() + zoomArgs = [] + for a in args: + if a is not None: + zoomArgs.append(NumberObject(a)) + else: + zoomArgs.append(NullObject()) + dest = Destination(NameObject("/"+title + " bookmark"), pageRef, NameObject(fit), *zoomArgs) + destArray = dest.getDestArray() + action.update({ + NameObject('/D') : destArray, + NameObject('/S') : NameObject('/GoTo') + }) + actionRef = self.output._addObject(action) + + outlineRef = self.output.getOutlineRoot() if parent is None: - self.bookmarks.append(dest) - else: - bmparent = self.bookmarks - for i in iloc[:-1]: - bmparent = bmparent[i] - npos = iloc[-1]+1 - if npos < len(bmparent) and isinstance(bmparent[npos], list): - bmparent[npos].append(dest) - else: - bmparent.insert(npos, [dest]) - return dest + parent = outlineRef + + bookmark = TreeObject() + + bookmark.update({ + NameObject('/A'): actionRef, + NameObject('/Title'): createStringObject(title), + }) + + if color is not None: + bookmark.update({NameObject('/C'): ArrayObject([FloatObject(c) for c in color])}) + + format = 0 + if italic: + format += 1 + if bold: + format += 2 + if format: + bookmark.update({NameObject('/F'): NumberObject(format)}) + + bookmarkRef = self.output._addObject(bookmark) + parent = parent.getObject() + parent.addChild(bookmarkRef, self.output) + + return bookmarkRef def addNamedDestination(self, title, pagenum): """ diff --git a/Tests/test_merger.py b/Tests/test_merger.py index f31616608..9546c920a 100644 --- a/Tests/test_merger.py +++ b/Tests/test_merger.py @@ -51,6 +51,7 @@ def test_merge(): # Check if bookmarks are correct pdfr = PyPDF2.PdfFileReader(tmp_path) assert [el.title for el in pdfr.getOutlines() if isinstance(el, Destination)] == [ + "A bookmark", "Foo", "Bar", "Baz", @@ -61,7 +62,6 @@ def test_merge(): "Bar", "Baz", "True", - "A bookmark", ] # Clean up