The following ReportLab code generates a starting template that I am (almost) pleased with. The missing piece of the puzzle is to add clickable "Back To Contents" text at the left hand side of the footer, on every page.
Any footer wizards out there?
from datetime import datetime
from reportlab.pdfgen import canvas
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
from reportlab.platypus.frames import Frame
from reportlab.lib.units import cm
from reportlab.platypus import Paragraph, Spacer, PageBreak
from reportlab.platypus.tableofcontents import TableOfContents
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors
from reportlab.lib.pagesizes import LETTER
class FooterCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
canvas.Canvas.__init__(self, *args, **kwargs)
self.pages = []
def showPage(self):
self.pages.append(dict(self.__dict__))
self._startPage()
def save(self):
page_count = len(self.pages)
for page in self.pages:
self.__dict__.update(page)
if (self._pageNumber > 1):
self.draw_canvas(page_count)
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
def draw_canvas(self, page_count):
page = f"Page {self._pageNumber} of {page_count}"
x = 128
self.saveState()
self.setStrokeColorRGB(0, 0, 0)
self.setLineWidth(0.5)
self.line(66, 78, LETTER[0] - 66, 78)
self.setFont('Helvetica', 10)
self.drawString(LETTER[0]-x, 65, page)
self.restoreState()
class MyDocTemplate(BaseDocTemplate):
def __init__(self, filename, **kw):
self.allowSplitting = 0
BaseDocTemplate.__init__(self, filename, **kw)
template = PageTemplate('normal', [Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm, id='F1')])
self.addPageTemplates(template)
def afterFlowable(self, flowable):
"Registers TOC entries."
if flowable.__class__.__name__ == 'Paragraph':
text = flowable.getPlainText()
style = flowable.style.name
if style == 'Heading1':
self.notify('TOCEntry', (0, text, self.page))
if style == 'Heading2':
key = 'h2-%s' % self.seq.nextf('heading2')
self.canv.bookmarkPage(key)
self.notify('TOCEntry', (1, text, self.page, key))
if style == 'Heading3':
key = 'h3-%s' % self.seq.nextf('heading3')
self.canv.bookmarkPage(key)
self.notify('TOCEntry', (2, text, self.page, key))
timestamp_now = datetime.now().strftime("%Y-%m-%dT%H_%M_%S")
date_now = datetime.now().strftime("%d.%m.%Y")
doc = MyDocTemplate(
filename="mydoc.pdf",
author="Person A"
)
# Define styles
styles = getSampleStyleSheet()
content = []
# Add title to the PDF
title = Paragraph(text="My Document", style=styles["Title"])
content.append(title)
content.append(Spacer(width=0 * cm, height=15 * cm))
content.append(Paragraph(text="Authors", style=styles['h4']))
for author in ["Person A", "Person B", "Person C"]:
author_para = Paragraph(author, styles["Italic"])
content.append(author_para)
content.append(Spacer(width=0 * cm, height=4 * cm))
date_para = Paragraph(
text=f"Document Compiled on {date_now}",
style=ParagraphStyle(name='centered_date', parent=styles["h4"], alignment=1)
)
content.append(date_para)
# Add a table of contents
content.append(PageBreak())
toc = TableOfContents()
toc.levelStyles = [
toc.getLevelStyle(0),
ParagraphStyle(name='blue_hyperlinks', parent=toc.getLevelStyle(1), textColor=colors.blue),
ParagraphStyle(name='blue_hyperlinks', parent=toc.getLevelStyle(2), textColor=colors.blue)
]
content.append(toc)
content.append(PageBreak())
content.append(Paragraph(text="This is an h1 heading", style=styles['Heading1']))
content.append(Paragraph(text="This is an h2 heading", style=styles['Heading2']))
content.append(PageBreak())
content.append(Paragraph(text="This is an h3 heading", style=styles['Heading3']))
content.append(PageBreak())
content.append(Paragraph(text="This is an h2 heading", style=styles['Heading2']))
content.append(Paragraph(text="This is an h3 heading", style=styles['Heading3']))
doc.multiBuild(story=content, canvasmaker=FooterCanvas)