December 2009
November 2009
October 2009
September 2009
June 2009
April 2009
March 2009
February 2009
January 2009
December 2008
November 2008
October 2008
July 2008
June 2008
October 2007
September 2007
We often want to dump a lot of data in programming. Tables are a fair way of displaying data and ReportLab supports them. Be warned! The syntax is ugly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #!/usr/bin/env python from reportlab.lib import colors from reportlab.lib.units import inch from reportlab.lib.pagesizes import letter from reportlab.platypus import SimpleDocTemplate, Spacer, Table, TableStyle doc = SimpleDocTemplate("table.pdf", pagesize=letter) data = [ ['Item', 'Cost', 'Quantity'], ['Widget', 3.99, 26], ['Whatsit', 2.25, 26], ['Hooplah', 10.00, 26], ] parts = [] table = Table(data, [3 * inch, 1.5 * inch, inch]) table_with_style = Table(data, [3 * inch, 1.5 * inch, inch]) table_with_style.setStyle(TableStyle([ ('FONT', (0, 0), (-1, -1), 'Helvetica'), ('FONT', (0, 0), (-1, 0), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, -1), 8), ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.black), ('BOX', (0, 0), (-1, 0), 0.25, colors.green), ('ALIGN', (0, 0), (-1, 0), 'CENTER'), ])) parts.append(table) parts.append(Spacer(1, 0.5 * inch)) parts.append(table_with_style) doc.build(parts) |
The first thing you need to make a Table is some data. Any matrix with a standard number of columns will do. The Table needs two arguments: The data and a sequence of column widths. With that, you can build a Table like any other Flowable. It will look bland, but it will be there.
Tables can be styled to your liking with setStyle and an instance of TableStyle. TableStyles are ugly, syntax-wise. They take one argument, which is a sequence of tailored sequences. For the most part, these tailored sequences are structured as (attribute, start_cell, end_cell, attribute_value). There are a few, like BOX and INNERGRID, that have five values instead of four. The last two are the line width and color. The are more TableStyle attributes than I have used in this example. I would point you to a reference, but I have yet to find one. I will probably make an entire post dedicated to this.
Adding images to PDFs is not much different than adding text. Here is how to add an image as a flowable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #!/usr/bin/env python import os import urllib2 from reportlab.lib.pagesizes import letter from reportlab.platypus import SimpleDocTemplate, Image filename = './python-logo.png' def get_python_image(): """ Get a python logo image for this example """ if not os.path.exists(filename): response = urllib2.urlopen( 'http://www.python.org/community/logos/python-logo.png') f = open(filename, 'w') f.write(response.read()) f.close() get_python_image() doc = SimpleDocTemplate("image.pdf", pagesize=letter) parts = [] parts.append(Image(filename)) doc.build(parts) |
Ignore the get_python_image function. It is in there only to make this example easily runnable. As you see, I import the Image Flowable from platypus. Image takes a minimum of one argument, which is the path to the image to use. You can build it into a SimpleDocTemplate just like a Paragraph or a Spacer.
Writing images to a canvas is easy, but has one gotcha.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #!/usr/bin/env python import os import urllib2 from reportlab.lib.pagesizes import letter from reportlab.lib.units import inch from reportlab.pdfgen import canvas filename = './python-logo.png' def get_python_image(): """ Get a python logo image for this example """ if not os.path.exists(filename): response = urllib2.urlopen( 'http://www.python.org/community/logos/python-logo.png') f = open(filename, 'w') f.write(response.read()) f.close() get_python_image() c = canvas.Canvas('imageabs.pdf', pagesize=letter) width, height = letter c.drawImage(filename, inch, height - 2 * inch) # Who needs consistency? c.showPage() c.save() |
We use the drawImage method of our Canvas instance. Here is the gotcha. Unlike drawString and its siblings, drawImage takes its content, the path to the image, first and its coordinates second. This is poor design by the ReportLab developers. It is still easy to add the image though. Just remember that if you get an obtuse error like this:
AttributeError: 'float' object has no attribute 'jpeg_fh'
Or this:
AttributeError: 'int' object has no attribute 'jpeg_fh'
Then the arguments are out of order.
In my last post, I presented how to place text in an absolute position on a page. You will not want to do this all the time. Imagine putting a book of text in PDF form that way. An exercise in masochism to be sure. ReportLab offers a spectacular framework, called platypus, for building real documents. There are tons of new terms to learn here, so I will try to be thorough. Here is some example code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #!/usr/bin/env python from reportlab.lib.pagesizes import letter from reportlab.lib.styles import ParagraphStyle from reportlab.lib.units import inch from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer doc = SimpleDocTemplate("paragraphs.pdf", pagesize=letter) parts = [] style = ParagraphStyle( name='Normal', fontName='Helvetica-Bold', fontSize=9, ) parts.append(Paragraph("Paragraphs are a kind of Flowable. " * 20, style)) parts.append(Spacer(1, 0.2 * inch)) parts.append(Paragraph("Paragraphs are natural in their behavior. " * 20, style)) parts.append(Spacer(1, 0.2 * inch)) parts.append(Paragraph( "Paragraphs make sense for flexible and dynamic documents. " * 20, style)) doc.build(parts) |
The first point of interest is line 7. SimpleDocTemplate is just what it sounds like. It provides information to control the behavior of the flowables. It is also used to write the PDF to disk. You can write your own Templates to offer features like multiple columns. I will cover that in another post.
On lines 10 to 14, we are defining a ParagraphStyle. This lets you define how your text will be displayed, like font and alignment. You might be wondering about the name. This is required. It is used when the ParagraphStyle is part of a StyleSheet, which I will cover in another post.
Now, we are ready to build a Paragraph object. This is the Flowable I have been talking about. In ReportLab, Flowables are objects that have wrapping, positioning, and splitting behavior defined. A Paragraph will stay within the margins and span across pages without trouble. They only require some text and a ParagraphStyle.
A Spacer is another Flowable. It is just whitespace. It takes two arguments, width and height.
When we are ready to write the PDF, we call our doc's, our SimpleDocTemplate instance's, build method with a collection of Flowables.
When making PDFs in python, we use reportlab for the most part. It is an extensive module that can do make about anything you would want in a PDF. Today, I will cover how to do the most basic function, putting some text somewhere on the page. Here is the simplest example:
1 2 3 4 5 6 7 | #!/usr/bin/env python from reportlab.pdfgen import canvas c = canvas.Canvas('rldemo1.pdf') c.drawString(100, 100, 'Hello, world!') c.showPage() c.save() |
You will notice that reportlab takes care of opening the file when we create a new Canvas instance. The drawString method puts a piece of text in an interesting place. It starts 100 points from the left as expected, but also 100 points from the bottom. I expected the origin to be the top-left, but reportlab starts from the bottom-left. With showPage, we commit our changes and save will write the file to disc. If you run this, you will also notice that the page size is A4 by default, which is because ReportLab, the company, is based in the UK. So how do we change that and start from the top instead of the bottom in reportlab?
1 2 3 4 5 6 7 8 9 | #!/usr/bin/env python from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas c = canvas.Canvas('rldemo2.pdf', pagesize=letter) width, height = letter c.drawString(100, height - 100, 'From the top!') c.showPage() c.save() |
So here we supply Canvas with the letter pagesize. Just that simple. Now that we have the pagesize, which is but a tuple, we can extract the width and height. With the page height, we can subtract the offset from the top to have the text placed relative to the top of the page. Can I use something other than points? Real paper layouts are done in inches and centimeters.
1 2 3 4 5 6 7 8 9 10 11 12 13 | #!/usr/bin/env python from reportlab.lib.pagesizes import letter from reportlab.lib.units import inch, cm from reportlab.pdfgen import canvas c = canvas.Canvas('rldemo3.pdf', pagesize=letter) width, height = letter c.drawString(inch, height - inch, '1 inch') c.drawString(inch, height - 2 * inch, '2 inches') c.drawString(cm, cm, '1 cm') c.drawString(cm, 2 * cm, '2 cm') c.showPage() c.save() |
Our reportlab module features a variety of constants for unit size, which are multiples of points. One gotcha with all of the text placement. The text is always drawn top and right of the coordinates given with drawString, which you can see if you run this example. You can write text from the left now, but what about centered text and right-aligned text?
1 2 3 4 5 6 7 8 9 | #!/usr/bin/env python from reportlab.lib.pagesizes import letter from reportlab.lib.units import inch from reportlab.pdfgen import canvas c = canvas.Canvas('rldemo4.pdf', pagesize=letter) width, height = letter c.drawString(inch, height - inch, 'Left') c.drawCentredString(width / 2.0, height - inch, 'Center') # Notice the UK |
spelling :) c.drawRightString(width - inch, height - inch, 'Right') c.showPage() c.save()
There are two more methods for printing text, drawCentredString and drawRightString. The only difference from drawString is that these start drawing from the center and right. All three still go from the bottom up.
