Reportlab: How to Create Landscape Pages

The other day I had an interesting task I needed to complete with Reportlab. I needed to create a PDF in landscape orientation that had to be rotated 90 degrees when I saved it. To make it easier to lay out the document, I created a class with a flag that allows me to save it in landscape orientation or flip it into portrait. In this article, we'll take a look at my code to see what it takes. If you'd like to follow along, I would recommend downloading a copy of Reportlab and pyPdf (or pyPdf2).

Reportlab Page Orientation

reportlab_landscape

There are at least two ways to tell Reportlab to use a landscape orientation. The first one is a convenience function called landscape that you can import from reportlab.lib.pagesizes. You would use it like this:

from reportlab.lib.pagesizes import landscape, letter
from reportlab.pdfgen import canvas

self.c = canvas
self.c.setPageSize( landscape(letter) )

The other way to set landscape is just set the page size explicitly:

from reportlab.lib.pagesizes import landscape
from reportlab.pdfgen import canvas
from reportlab.lib.units import inch

self.c = canvas
self.c.setPageSize( (11*inch, 8.5*inch) )

You could make this more generic by doing something like this though:

from reportlab.lib.pagesizes import landscape
from reportlab.pdfgen import canvas
from reportlab.lib.units import inch

width, height = letter

self.c = canvas
self.c.setPageSize( (height, width) )

This might make more sense, especially if you wanted to use other popular page sizes, like A4. Now let's take a moment and look at a full-fledged example:

import pyPdf
import StringIO

from reportlab.lib import utils
from reportlab.lib.pagesizes import landscape, letter
from reportlab.platypus import (Image, SimpleDocTemplate, 
                                Paragraph, Spacer)
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch, mm

########################################################################
class LandscapeMaker(object):
    """
    Demo to show how to work with Reportlab in Landscape orientation
    """

    #----------------------------------------------------------------------
    def __init__(self, rotate=False):
        """Constructor"""
        self.logo_path = "snakehead.jpg"
        self.pdf_file = "rotated.pdf"
        self.rotate = rotate
        self.story = [Spacer(0, 0.1*inch)]
        self.styles = getSampleStyleSheet()
        
        self.width, self.height = letter
        
    #----------------------------------------------------------------------
    def coord(self, x, y, unit=1):
        """
        Helper class to help position flowables in Canvas objects
        (http://stackoverflow.com/questions/4726011/wrap-text-in-a-table-reportlab)
        """
        x, y = x * unit, self.height -  y * unit
        return x, y
        
    #----------------------------------------------------------------------
    def create_pdf(self, canvas, doc):
        """
        Create the PDF
        """
        self.c = canvas
        self.c.setPageSize( landscape(letter) )
        
        # add a logo and set size
        logo = self.scaleImage(self.logo_path, maxSize=90)
        logo.wrapOn(self.c, self.width, self.height)
        logo.drawOn(self.c, *self.coord(10, 113, mm))
        
        # draw a box around the logo
        self.c.setLineWidth(2)
        self.c.rect(20, 460, width=270, height=100)
        
        ptext = "Python is amazing!!!"
        p = Paragraph(ptext, style=self.styles["Normal"])
        p.wrapOn(self.c, self.width, self.height)
        p.drawOn(self.c, *self.coord(45, 101, mm))
    
    #----------------------------------------------------------------------
    def save(self):
        """
        Save the PDF
        """
        if not self.rotate:
            self.doc = SimpleDocTemplate(self.pdf_file, pagesize=letter,
                                         leftMargin=0.8*inch)
        else:
            fileObj = StringIO.StringIO()
            self.doc = SimpleDocTemplate(fileObj, pagesize=letter,
                                         leftMargin=0.8*inch)
        
        self.doc.build(self.story, 
                       onFirstPage=self.create_pdf)
        
        if self.rotate:
            fileObj.seek(0)
            pdf = pyPdf.PdfFileReader(fileObj)
            output = pyPdf.PdfFileWriter()
            for page in range(pdf.getNumPages()):
                pdf_page = pdf.getPage(page)
                pdf_page.rotateClockwise(90)
                output.addPage(pdf_page)
        
            output.write(file(self.pdf_file, "wb"))
        
    #----------------------------------------------------------------------
    def scaleImage(self, img_path, maxSize=None):
        """
        Scales the image
        """
        img = utils.ImageReader(img_path)
        img.fp.close()
        
        if not maxSize:
            maxSize = 125
        
        iw, ih = img.getSize()
    
        if iw > ih:
            newW = maxSize
            newH = maxSize * ih / iw
        else:
            newH = maxSize
            newW = maxSize * iw / ih
        
        return Image(img_path, newW, newH)
        
#----------------------------------------------------------------------
if __name__ == "__main__":
    pdf = LandscapeMaker()
    pdf.save()
    print "PDF created!"

If you run the code above (and you have a logo to use), you will see something very similar to the screenshot at the beginning of the article. This makes laying out the document easier because text and images are horizontal. Let's spend a few minutes parsing the code. In the init, we set up a few items, such as the logo, the PDF file's name, whether to rotate or not and a few other items. The coord method is something I found on StackOverflow that helps position flowables easier. The create_pdf method is where most of the magic is. It calls the landscape function that we imported. This function also draws the logo, the rectangle and the words on the document.

The next method is the save method. If we don't do the rotation, we create a SimpleDocTemplate, pass it the PDF file name and build the document. On the other hand, if we do turn on rotation, then we create a file object using Python's StringIO library so that we can manipulate the PDF in memory. Basically we write the data to memory, then we seek to the beginning of the faux file so we can read it with pyPdf. Next we create a pyPdf writer object. Finally we loop through the PDF that's in memory page by page and rotate each page before writing it out.

The last method is just a handy method I was given from the wxPython group that I use for scaling images. A lot of the time you will find yourself with images that are too large for your purposes and you will need to scale them down to fit. That's all this method does.

Once you've got everything where you want it, you can change the code at the end to the following:

#----------------------------------------------------------------------
if __name__ == "__main__":
    pdf = LandscapeMaker(rotate=True)
    pdf.save()
    print "PDF created!"

This will cause the script to do the rotation and the output should look something like this:

reportlab_landscape_rotated

Wrapping Up

Creating and editing PDFs in the landscape orientation is actually pretty easy to do in Python and Reportlab. At this point, you should be able to do it with aplomb! Good luck and happy coding!

Related Articles

Copyright © 2024 Mouse Vs Python | Powered by Pythonlibrary