Reportlab: How to add a footer to a pdf file

Lynob picture Lynob · Feb 2, 2015 · Viewed 13.4k times · Source

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

Answer

Juan Fco. Roco picture Juan Fco. Roco · Feb 2, 2015

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.