A multiline(paragraph) footer and header in reportlab

You can use arbitrary drawing commands in the onPage function, so you can just draw a paragraph (see section 5.3 in the reportlab user guide) from your function.

Here is a complete example:

from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Paragraph

styles = getSampleStyleSheet()
styleN = styles['Normal']
styleH = styles['Heading1']

def footer(canvas, doc):
    canvas.saveState()
    P = Paragraph("This is a multi-line footer.  It goes on every page.  " * 5,
                  styleN)
    w, h = P.wrap(doc.width, doc.bottomMargin)
    P.drawOn(canvas, doc.leftMargin, h)
    canvas.restoreState()

doc = BaseDocTemplate('test.pdf', pagesize=letter)
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height,
               id='normal')
template = PageTemplate(id='test', frames=frame, onPage=footer)
doc.addPageTemplates([template])

text = []
for i in range(111):
    text.append(Paragraph("This is line %d." % i,
                          styleN))
doc.build(text)

Jochen’s answer is great, but I found it incomplete. It works for footers, but not for headers as Reportlab will draw all the flowables on top of the header. You need to be sure the size of the Frame you create excludes the space taken up by the header so flowabls are not printed on top of the header.

Using jochen’s code, here is a complete example for headers:

from reportlab.lib.pagesizes import letter, cm
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Paragraph
from functools import partial

styles = getSampleStyleSheet()
styleN = styles['Normal']
styleH = styles['Heading1']

def header(canvas, doc, content):
    canvas.saveState()
    w, h = content.wrap(doc.width, doc.topMargin)
    content.drawOn(canvas, doc.leftMargin, doc.height + doc.topMargin - h)
    canvas.restoreState()

doc = BaseDocTemplate('test.pdf', pagesize=letter)
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height-2*cm, id='normal')
header_content = Paragraph("This is a multi-line header.  It goes on every page.  " * 8, styleN)
template = PageTemplate(id='test', frames=frame, onPage=partial(header, content=header_content))
doc.addPageTemplates([template])

text = []
for i in range(111):
    text.append(Paragraph("This is line %d." % i, styleN))
doc.build(text)

Pay attention to the decleration of the Frame, it subtracts 2 cm from the height of the frame to allow room for the header. The flowables will be printed within the frame so you can change the size of the frame to allow for various sizes of headers.

I also find that I usually need to pass variables into the header, so I used a partial function assigned to onPage so that the content of the header can be passed in. This way you can have a variable header based on the content of the page.

Additional Approach for adding the header or footer on all pages: there are arguments for the build method to do this.

Do not use the frame and template in the answer by jochen. In the last line, use

doc.build(text, onFirstPage=footer, onLaterPages=footer)

the rest of the approach will be the same as from jochen.

Leave a Comment