Reportlab: How to add a footer to a pdf file

15,627

First of all, Reportlab is awesome. Best library I've found to generate pdfs.

Please install reportlab before trying the examples:

pip install reportlab

In order to create a footnote you need to render a document with multibuild and use a canvasmaker to add the footer.

First, let's create a simple pdf file with two pages:

from reportlab.platypus import (SimpleDocTemplate, Paragraph, PageBreak)
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import LETTER


if __name__ == '__main__':

    # Content
    styles = getSampleStyleSheet()
    elements = []
    elements.append(Paragraph("Hello", styles["Normal"]))
    elements.append(Paragraph("World", styles["Normal"]))
    elements.append(PageBreak())
    elements.append(Paragraph("You are in page 2", styles["Normal"]))

    # Build
    doc = SimpleDocTemplate("my_file.pdf", pagesize=LETTER)
    doc.build(elements)

Check that the pdf file is created correctly.

Now let's add a canvas class to draw the footer that shows a line and page numbers and change build to multibuild in the last line:

from reportlab.pdfgen import canvas
from reportlab.platypus import (SimpleDocTemplate, Paragraph, PageBreak)
from reportlab.lib.styles import getSampleStyleSheet
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)
            self.draw_canvas(page_count)
            canvas.Canvas.showPage(self)
        canvas.Canvas.save(self)

    def draw_canvas(self, page_count):
        page = "Page %s of %s" % (self._pageNumber, 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('Times-Roman', 10)
        self.drawString(LETTER[0]-x, 65, page)
        self.restoreState()


if __name__ == '__main__':

    # Content
    styles = getSampleStyleSheet()
    elements = []
    elements.append(Paragraph("Hello", styles["Normal"]))
    elements.append(Paragraph("World", styles["Normal"]))
    elements.append(PageBreak())
    elements.append(Paragraph("You are in page 2", styles["Normal"]))

    # Build
    doc = SimpleDocTemplate("my_file.pdf", pagesize=LETTER)
    doc.multiBuild(elements, canvasmaker=FooterCanvas)

In multibuild you can also specify a different canvas for the first page if you will:

doc.multiBuild(Elements, onFirstPage=myFirstPage, onLaterPages=myLaterPages)

Hope this helps.

EDIT

The goal now is to add a footer to an existing pdf file. Unfortunately, this can't be done alone with Reportlab (at least the open source version, I think the proffesional version have this feature).

Firt, we need to add to the recipe a little of pdfrw

pip install pdfrw

Now we can add a footer to an existing pdf doing this: opening the original pdf, extracting the pages, and "drawing" the pages along the footer to a new pdf, one page at a time:

from reportlab.pdfgen.canvas import Canvas
from pdfrw import PdfReader
from pdfrw.toreportlab import makerl
from pdfrw.buildxobj import pagexobj

input_file = "my_file.pdf"
output_file = "my_file_with_footer.pdf"

# Get pages
reader = PdfReader(input_file)
pages = [pagexobj(p) for p in reader.pages]


# Compose new pdf
canvas = Canvas(output_file)

for page_num, page in enumerate(pages, start=1):

    # Add page
    canvas.setPageSize((page.BBox[2], page.BBox[3]))
    canvas.doForm(makerl(canvas, page))

    # Draw footer
    footer_text = "Page %s of %s" % (page_num, len(pages))
    x = 128
    canvas.saveState()
    canvas.setStrokeColorRGB(0, 0, 0)
    canvas.setLineWidth(0.5)
    canvas.line(66, 78, page.BBox[2] - 66, 78)
    canvas.setFont('Times-Roman', 10)
    canvas.drawString(page.BBox[2]-x, 65, footer_text)
    canvas.restoreState()

    canvas.showPage()

canvas.save()

DISCLAIMER: Tested on Linux using as input file a pdf file generated by Reportlab. It would probably not work in an arbitrary pdf file.

Share:
15,627
Lynob
Author by

Lynob

Updated on June 10, 2022

Comments

  • Lynob
    Lynob about 2 years

    I already asked this question but there's no answer yet, so I want to take a look at Reportlab which seems to be actively developed and better than fpdf python library.

    I've already seen this question The answer given seems more or less the same as this blog post. Anyhow, the script shown is full of errors, but I don't want to vote it down, the OP accepted it, seemed to solve his problem, who am I to argue, I'd rather ask another question.

    First of all you need to import

    from reportlab.pdfgen.canvas import Canvas
    

    and it's not canvas it's Canvas and then you can't just do

    Canvas.restoreState() or Canvas.saveState()

    Maybe there are more errors, I'd rather use another example. I spent the entire night yesterday trying to make that snippet work and couldn't.

    Is there another method to create footer? A specific method I want to ask about is this, the guy do a for loop, on row[0] he writes something, row[1] something

    I counted on a LibreOffice document, which is A4, using Liberation Serif 12, there are 49 rows. Assuming that on average there are 45 rows, depending on the font size, and the footer font size is 8, can't one just insert the footer like this x=row[45] and then increment x based on the page number? wouldn't that be a far easier solution?

    If I can detect that the end of file, or the last line on which text is inserted, then I can do it, I think.

    If you refer to my other question, you would notice that I convert powerpoint, excel and word to pdf and then insert a footer.

    If there is a library that works on linux and windows covert powerpoint and excel to word, then add the footer then convert to pdf, it would be great, since I think it's easier to work with documents then PDFs

  • Lynob
    Lynob over 9 years
    Thanks for your answer, I will accept your answer tomorrow, but want to ask you something, is the page footer being inserted as soon as you call pagebreak? if so, it won't work for me because I'm using an existing file, please refer to my earlier question, link above, I mean there's no way for me to know when the page ends, since I'm not inserting manually, I'm using a for loop for the entire file, unless there's a function that detects the pages in a pdf
  • Lynob
    Lynob over 9 years
    I'll try to use OnlaterPages
  • Lynob
    Lynob over 9 years
    how to load each page of the file in elements[] because I'm not writing it by hand
  • Juan Fco. Roco
    Juan Fco. Roco over 9 years
    Page footers are generated after rendering the document. That's why I use multiBuild. I use page breaks just to generate easily a two pages doc. What kind of file are you using? A text file?
  • Lynob
    Lynob over 9 years
    Pdf files, i have pdf existing pdf files, i want to add footers to them, PDF files that i didn't create
  • Juan Fco. Roco
    Juan Fco. Roco over 9 years
    I edited my answer with a little program that does that
  • Anonymous12332313
    Anonymous12332313 over 2 years
    This worked perfectly for me, thank you!
  • Anonymous12332313
    Anonymous12332313 over 2 years
    A problem I found with using pdfrw to read the pdf and then reportlab to write over it is that <a> links don't work. Any idea on how to fix that?