Archive
Tags
android (3)
ant (2)
beautifulsoup (1)
debian (1)
decorators (1)
django (9)
dovecot (1)
encryption (1)
fix (4)
gotcha (2)
hobo (1)
htmlparser (1)
imaplib (2)
java (1)
json (2)
kerberos (2)
linux (7)
lxml (5)
markdown (4)
mechanize (6)
multiprocessing (1)
mysql (2)
nagios (2)
new_features (3)
open_source (5)
optparse (2)
parsing (1)
perl (2)
postgres (1)
preseed (1)
pxe (4)
pyqt4 (1)
python (41)
raid (1)
rails (1)
red_hat (1)
reportlab (4)
request_tracker (2)
rt (2)
ruby (1)
scala (1)
screen_scraping (7)
shell_scripting (8)
soap (1)
solaris (3)
sql (2)
sqlalchemy (2)
tips_and_tricks (1)
twitter (2)
ubuntu (1)
vmware (2)
windows (1)
zimbra (2)

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.

Posted by Tyler Lesmann on January 29, 2009 at 12:53
Tagged as: python reportlab

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.

Posted by Tyler Lesmann on January 28, 2009 at 6:07 and commented on 3 times
Tagged as: python reportlab

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.

Posted by Tyler Lesmann on January 27, 2009 at 12:50
Tagged as: python reportlab

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.

Posted by Tyler Lesmann on January 23, 2009 at 6:32 and commented on 1 time
Tagged as: python reportlab