Reportlab: How to add a footer to a pdf file
Asked Answered
R

2

13

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

Retouch answered 2/2, 2015 at 15:29 Comment(1)
Until someone answers, I'l try to use os.system("libreoffice --headless --invisible --convert-to doc or somethingRetouch
H
26

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.

Hammertoe answered 2/2, 2015 at 17:53 Comment(10)
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 pdfRetouch
I'll try to use OnlaterPagesRetouch
how to load each page of the file in elements[] because I'm not writing it by handRetouch
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?Hammertoe
Pdf files, i have pdf existing pdf files, i want to add footers to them, PDF files that i didn't createRetouch
I edited my answer with a little program that does thatHammertoe
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?Mckinney
@Mckinney I'm facing the same problem as yours. Were you able to find the solution or any other alternative?Uncrowned
@Uncrowned I did not find a solution using pdfrw and reportlab. For a while I was using pypi.org/project/numbering2pdf, however I had to stop using it because it was incompatible with some other libraries I needed for my Flask app. It isn't very customizable, but it works. It is open source, so if you need to customize it perhaps you can edit the source code to suit your needs. Most importantly, iirc it writes bytes onto the pdf which means that the <a> links still work. What is your use? Are you making pdfs or just editing existing pdfs?Mckinney
Thanks for getting back @Anonymous12332313! My real use case is to convert an html to pdf using python, for which I explored a ton of alternatives. But each one had some shortcomings, like not rendering css properly, not supporting javascript, not able to do line breaks properly, etc. I finally ended up using pyppeteer library to convert the html to pdf, and just wanted a pdf post-processor to overlay the page numbers on it. Even numbering2pdf wasn't compatible with my setup. So, I'm finally resorting to use PDFtk CLI tool - which seems to handle <a> links properly.Uncrowned
S
0

There isn't a method that solves this problem, but it is pretty easy to write your own footer method

def footer(canvas: Canvas):
    canvas.saveState()
    canvas.setFont("Helvetica", 10)
    canvas.drawString(1 * cm, 1 * cm, "PDF Export")
    canvas.drawString(18 * cm, 1 * cm, "Page %d" % canvas.getPageNumber())
    canvas.restoreState()

Please note that the position here is calculated for an A4 Page. You might have to modify it when using a different page size.

Salmon answered 22/1, 2024 at 11:54 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.