Properly add page numbers and total number of pages to PDF using ReportLab

Question:

I am trying to create a document that has page numbering of the format “Page x of y”.
I have tried the NumberedCanvas approach (http://code.activestate.com/recipes/576832/ and also from the forums https://groups.google.com/forum/#!topic/reportlab-users/9RJWbrgrklI) but that conflicts with my clickable Table of Contents (https://www.reportlab.com/snippets/13/).

I understood from this post http://two.pairlist.net/pipermail/reportlab-users/2002-May/000020.html that it may be possible using forms, but examples on this are very scarce and uninformative.
Does anybody have any idea on how this can be implemented using forms (or fixing the NumberedCanvas approach?)

Asked By: Yotam Alon

||

Answers:

So I ended up rendering the document twice. Takes a couple more seconds, but works very well!

class MyDocTemplate(BaseDocTemplate):
    def generate(self, story):
        self.multiBuild(story[:])
        # Decrease by one because canvas is at the page after the last
        self.page_count = self.canv.getPageNumber() - 1
        self.multiBuild(story[:])


class MyPageTemplate(PageTemplate):
    def onPage(self, canvas, doc):
        if getattr(doc, 'page_count', None) is not None:
            canvas.drawString(x, y, canvas.getPageNumber(), doc.page_count)

Not 100% happy, but sometimes you take what you can get!

Make sure you do not pass the same objects to the first and second multiBuild, as report lab adds some properties to them during the first build, that will cause errors in the second build.

I am slightly concerned that at some point the canvas object might be destroyed or reset after the build, so if any future people want to use this, be careful.

Answered By: Yotam Alon

This page (http://www.blog.pythonlibrary.org/2013/08/12/reportlab-how-to-add-page-numbers/) explains a good way to do it. I made some changes to make better use of inheritance.

It creates a new Class that inherits from ReportLab Canvas class.

This is my modified code:

from reportlab.lib.units import mm
from reportlab.pdfgen.canvas import Canvas


class NumberedPageCanvas(Canvas):
    """
    http://code.activestate.com/recipes/546511-page-x-of-y-with-reportlab/
    http://code.activestate.com/recipes/576832/
    
Reportlab: How to Add Page Numbers
""" def __init__(self, *args, **kwargs): """Constructor""" super().__init__(*args, **kwargs) self.pages = [] def showPage(self): """ On a page break, add information to the list """ self.pages.append(dict(self.__dict__)) self._startPage() def save(self): """ Add the page number to each page (page x of y) """ page_count = len(self.pages) for page in self.pages: self.__dict__.update(page) self.draw_page_number(page_count) super().showPage() super().save() def draw_page_number(self, page_count): """ Add the page number """ page = "Page %s of %s" % (self._pageNumber, page_count) self.setFont("Helvetica", 9) self.drawRightString(179 * mm, -280 * mm, page)

To use it just change Canvas for NumberedCanvas when creating a new file. When the file is saved the numbers are added.

Answered By: Martin

The method mentioned in the answer above, that overrides the canvas class, ends up breaking page referencing (such as the use of bookmarkPage()) in the resulting pdf. This is likely because the call to self.__doc.addPage() (found in in canvas.showPage()) are being called way too late by doing it the way described.

All references in the document will point to page 1, regardless of when You call bookmarkPage().

However, references can be fixed by increasing self._doc.pageCounter in the new showPage method;

def showPage(self):
        """
        On a page break, add information to the list
        """
        self.pages.append(dict(self.__dict__))
        self._doc.pageCounter += 1
        self._startPage()
  
Answered By: Frank E