Numbered Lists (sort of) in Reportlab

So, for the first time, I think I may have stumbled upon something I wanted to do in code for which Google searches didn’t give me a quick and easy answer. (When it’s not quick, it generally because I’m slow to understand.)

So, since I worked out a solution for myself and am more than usually proud of it, here’s what I have.

The challenge:
I’m automatically generating worksheets in PDF format. For them, I’d like to have exercises in a nice numbered list. You know the format, it’s pretty easy to do in most software.

  1. This is an exercise.
  2. So is this.
  3. And here is a blank to fill ____________.

However, even though Reportlab includes numbering in the bullets, I spent most of yesterday evening trying to tweak different indentation settings to get it to do what I want. And it didn’t. (Again, maybe I’m slow to understand.)

The problem was that either the text didn’t wrap right (it should wrap to be beneath the beginning of the text, not to beneath the number) or I couldn’t get the spacing between the number and the content to look right.

My solution:
I wound up using a table for this. It’s not what I wanted and I feel like the way I have it is a bit of a hack, with different methods checking the length of the list that feeds the table, so that I can add lines via a method an arbitrary number of times. Nonetheless, I’m pretty happy with how the result looks.

from reportlab.lib.pagesizes import A4
from reportlab.lib.enums import TA_LEFT
from reportlab.platypus import SimpleDocTemplate, Paragraph, Table, TableStyle
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle, ListStyle
from reportlab.lib.units import cm

# We need some text to use. I happen to like this one
lines=['Sing, Goddess, sing of the rage of Achilles, son of Peleus--',
       'that murderous anger which condemned Achaeans',
       'to countless agonies and threw many warrior souls',
       'deep into Hades, leaving their dead bodies',
       'carrion food for dogs and birds--',
       'all in fulfilment of the will of Zeus.']

style=ParagraphStyle(name='Normal', alignment=TA_LEFT, fontSize=12, leading=18, fontName='Helvetica')
doc=SimpleDocTemplate('iliad.pdf', pageSize=A4)

# Add a title, straightforward enough.
document.append(Paragraph('<b>The Iliad</b> by Homer', style))

# Now, add numbered lines
for line in lines:
    epic.append([Paragraph('<b>'+str(len(epic)+1)+'.</b>', style),
                 Paragraph(line, style)])

t=Table(epic, colWidths=(1.2*cm, None))
    ('VALIGN', (0,0), (-1,-1), 'TOP')


That gives you this result: iliad.pdf.

About it
Obviously, it’s a table which is made up of a list of lists. The various ‘cells’ (entries in one list) need to be wrapped in Paragraph objects in order to get the wrapping and formatting right. (It seems the <b>bold</b> tags are processed by the Paragraph object, if you’re curious.)

As for the TableStyle, that sets the vertical alignment to ‘TOP’ as it defaults to middle, which looks odd when the line it’s numbering wraps.

Lastly, it was interesting — and helpful — to me that the table cells could be numbered with the negative notation that is familiar from things like lists and strings. That is to say that (0,0) is the top left and (-1, -1) is the bottom right, because it counts from the end of the list. That was nice. (I was ready to start doing janky things with len(list) to get it right.)

Maybe I overestimate the problem I solved…
I would be surprised if I’m the only one who has done this, but it is the best I could come up with. I’d be happy to know if I overlooked some obvious solution / reference. How have you (or others) solved the same problem?

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s