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
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:
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
- Reportlab – All About Fonts
- A Simple Step-by-Step Reportlab Tutorial
- Reportlab: How to Add Page Numbers
- Reportlab Tables – Creating Tables in PDFs with Python
- Reportlab: Mixing Fixed Content and Flowables
You just need to import landscape from reportlab.lib.pagesizes: http://stackoverflow.com/questions/5913682/reportlab-how-to-switch-between-portrait-and-landscape
Thank you! 😀
Pingback: Reportlab: How to Create Custom Flowables - The Mouse Vs. The Python
Pingback: wxPython: Creating a Simple RSS Reader - The Mouse Vs. The Python