Seite 1 von 1

ReportLab - PDF mit Bookmarks + Seitenzahlen

Verfasst: Mittwoch 15. Januar 2025, 11:28
von kcdominik
Hallo zusammen,

mit ReportLab versuche ich, ein PDF-Dokument mit Bookmarks und Seitenzahlen zu erzeugen, leider bekomme ich es nicht zum Laufen. Entweder die Bookmarks funktionieren oder die Seitenzahlen, beides jedoch nicht in Kombination. Hier mein aktueller Code als einfaches lauffähiges Beispiel:

Code: Alles auswählen

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Flowable
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.units import mm


class NumberedBookmarkCanvas(canvas.Canvas):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._saved_page_states = []
        self._bookmarks = []
        self._current_page = 0

    def showPage(self):
        # Speichern des aktuellen Seitenstatus und wechseln zur neuen Seite
        self._saved_page_states.append(dict(self.__dict__))
        self._current_page += 1
        self._startPage()

    def save(self):
        num_pages = len(self._saved_page_states)
        for state in self._saved_page_states:
            self.__dict__.update(state)
            self.draw_page_number(num_pages)
            canvas.Canvas.showPage(self)

        self.add_bookmarks()
        canvas.Canvas.save(self)

    def draw_page_number(self, page_count):
        self.setFont("Helvetica", 8)
        self.drawRightString(190*mm, 10*mm, f"Page {self._pageNumber} / {page_count}")

    def add_bookmarks(self):
        for title, name in self._bookmarks:
            self.addOutlineEntry(title, name, level=0, closed=False)

    def add_bookmark(self, title, name):
        self._bookmarks.append((title, name))


class LinkFlowable(Flowable):
    def __init__(self, text, target):
        super().__init__()
        self.text = text
        self.target = target
        self.width = 0
        self.height = 0

    def wrap(self, availWidth, availHeight):
        from reportlab.pdfbase.pdfmetrics import stringWidth
        self.width = stringWidth(self.text, "Helvetica", 10)
        self.height = 10
        return self.width, self.height

    def draw(self):
        self.canv.setFont("Helvetica", 10)
        self.canv.drawString(100, 0, self.text)

        self.canv.linkRect(
            "",
            self.target,
            (100, 0, 100 + self.width, 10),
            relative=1
        )


class BookmarkFlowable(Flowable):
    def __init__(self, name, title, level=0):
        super().__init__()
        self.name = name
        self.title = title
        self.level = level

    def wrap(self, availWidth, availHeight):
        return 0, 0

    def draw(self):
        self.canv.bookmarkPage(self.name)
        self.canv.add_bookmark(self.title, self.name)


def create_pdf_with_multibuild(filename):
    doc = SimpleDocTemplate(filename, pagesize=A4)
    story = []
    styles = getSampleStyleSheet()

    def prepare_story():
        
        # Titelseite
        story.append(Paragraph("Titelseite:", styles["Normal"]))
        story.append(Spacer(1, 12))
        story.append(PageBreak())

        # Kapitel 1
        story.append(Paragraph("Kapitel 1: Einleitung", styles["Heading1"]))
        story.append(BookmarkFlowable("chapter1", "Kapitel 1: Einleitung"))
        story.append(LinkFlowable("Gehe zu Kapitel 2", "chapter2"))
        story.append(Spacer(1, 12))
        story.append(LinkFlowable("Gehe zu Kapitel 3", "chapter3"))
        story.append(Spacer(1, 12))

        # Kapitel 2
        story.append(PageBreak())
        story.append(BookmarkFlowable("chapter2", "Kapitel 2: Details"))
        story.append(Paragraph("Kapitel 2: Details", styles["Heading1"]))
        story.append(Spacer(1, 12))
        story.append(Paragraph("Hier ist der Inhalt von Kapitel 2.", styles["Normal"]))

        # Kapitel 3
        story.append(PageBreak())
        story.append(BookmarkFlowable("chapter3", "Kapitel 3: Weitere Details"))
        story.append(Paragraph("Kapitel 3: Weitere Details", styles["Heading1"]))
        story.append(Spacer(1, 12))
        story.append(Paragraph("Hier ist der Inhalt von Kapitel 3.", styles["Normal"]))

    prepare_story()

    doc.multiBuild(story, canvasmaker=NumberedBookmarkCanvas)


create_pdf_with_multibuild("interactive_links_and_bookmarks_with_numbered_canvas.pdf")
Aktuelles Verhalten: die Seite wird korrekt erzeugt, die Seitenzahlen sind korrekt vorhanden, die Bookmarks sind angelegt, jedoch auf den Klick auf egal welches Bookmark gelange ich immer auf die erste Seite des Dokuments.

Bitte helft mir :) !

Vielen Dank schonmal,
Dominik

Re: ReportLab - PDF mit Bookmarks + Seitenzahlen

Verfasst: Mittwoch 15. Januar 2025, 20:26
von noisefloor
Zum Einfügen von Seitenzahlen siehe https://www.blog.pythonlibrary.org/2013 ... e-numbers/. Das sollte nach wie vor so funktionieren. Zu Bookmarks kann ich leider nichts sagen.

Gruß, noisefloor

Re: ReportLab - PDF mit Bookmarks + Seitenzahlen

Verfasst: Freitag 17. Januar 2025, 09:11
von kcdominik
Danke für die Antwort. Die Seitenzahlen funktionieren eigentlich ja, genauso wie die Bookmarks, aber eben immer nur eines von beiden. Sobald ich beide Funtkionalitäten zusammen einbaue, komme ich bei einem Klick auf ein Bookmark immer auf die erste Seite im PDF.

Grüße,
Dominik

Re: ReportLab - PDF mit Bookmarks + Seitenzahlen

Verfasst: Freitag 24. Januar 2025, 14:27
von ImmortalDawn
Hallo,

ich bin momentan auch daran eine PDF mit Verweisen erstellen zu wollen und habe mich zu Übungszwecken mal mit dem Problem beschäftigt :D Ich konnte der Sache noch nicht endgültig auf den Grund gehen aber bisher sieht es nach einem Problem mit der Logik bzw. dem Ablauf aus.

Ich habe mal ein paar print-Befehle in der draw-Methode des BookmarkFlowables hinzugefügt:

Code: Alles auswählen

def draw(self):
        dest = self.canv.bookmarkPage(self.name, fit="XYZ")
        print("Drawing Floawble:")
        print("-> Bookmark Results:",dest.name, dest.page.name)	
        print("-> Current Canvas Page:", self.canv._pageNumber)	# returns current page number currectly (Canvas)
        print("-> Current Document Page:", self.canv._doc.thisPageRef().name) # this will be referenced as destination for bookmarks; points at 'Page1' (Document)
        print("-> Document Pages:", self.canv._doc.Pages.pages)	# currently empty
        self.canv.add_bookmark(self.title, self.name)
Dem Quellcode nach wird beim Aufruf von bookmarkPage(key) zunächst eine neue Referenz (nur mit Name, noch kein Ziel) angelegt und dieser dann die aktuelle Dokumentenseite als Ziel zugewiesen. Da das Dokument zu dem Zeitpunkt scheinbar noch keine Seiten hat bzw. die aktuelle Seite noch auf "Page1" steht, wird "Page1" auch als Ziel eingetragen. Ich vermute, dass die Seiten erst mit Canvas.save() in das Dokument übernommen werden.
Demnach wäre eine mögliche Lösung, die Bookmarks wie die Seitenzahlen mit späteren Iterationen des multiBuild() einfügen zu lassen, ggf. indem man bestimmte Flowables überprüft, wie es beim Inhaltsverzeichnis gemacht wird. Die Idee mit dem BookmarkFlowable finde ich interessant, scheint aber leider so nicht vorgesehen zu sein.