This week I was trying to figure out how to make Reportlab do something I had never attempted before. Namely, I wanted to create about a half page’s worth of static text positioned exactly and then have a table of line items that could potentially fill the rest of the page and continue for N pages thereafter. The problem is that mixing Reportlab’s canvas object with flowables can be messy. Reportlab talks a little about using templates in its user guide, but it only shows how to add header and footer type information. That’s actually all the information I needed, but it took me quite a while to realize that. I asked about how to do this sort of thing on the Reportlab mailing list. At the time of this writing, no one on there told me how to do it. Anyway, I figured it out on my own and now I’m going to show you! If you’d like to follow along, you’ll probably need to go get a free copy of Reportlab yourself.
Diving Into the Code
I had to go spelunking into Reportlab’s source code to figure out how to do this fun activity. All you really need to do is create a method in your class that does all your static canvas drawing. Then when you’re done, you create a list object with a Reportlab Spacer in it that tells Reportlab that it needs to skip over the area you drew in. Then you can create your Table object and add it to the list. Finally, you just need to build your document. Yes, if you’re new to Reportlab, that probably all sounded like Greek to you. It’s easier just to show you, so check out the code below:
from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle, TA_CENTER from reportlab.lib.units import inch, mm from reportlab.pdfgen import canvas from reportlab.platypus import Paragraph, Table, SimpleDocTemplate, Spacer ######################################################################## class Test(object): """""" #---------------------------------------------------------------------- def __init__(self): """Constructor""" self.width, self.height = letter self.styles = getSampleStyleSheet() #---------------------------------------------------------------------- def coord(self, x, y, unit=1): """ http://stackoverflow.com/questions/4726011/wrap-text-in-a-table-reportlab Helper class to help position flowables in Canvas objects """ x, y = x * unit, self.height - y * unit return x, y #---------------------------------------------------------------------- def run(self): """ Run the report """ self.doc = SimpleDocTemplate("test.pdf") self.story = [Spacer(1, 2.5*inch)] self.createLineItems() self.doc.build(self.story, onFirstPage=self.createDocument) print "finished!" #---------------------------------------------------------------------- def createDocument(self, canvas, doc): """ Create the document """ self.c = canvas normal = self.styles["Normal"] header_text = "This is a test header" p = Paragraph(header_text, normal) p.wrapOn(self.c, self.width, self.height) p.drawOn(self.c, *self.coord(100, 12, mm)) ptext = """Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.""" p = Paragraph(ptext, style=normal) p.wrapOn(self.c, self.width-50, self.height) p.drawOn(self.c, 30, 700) ptext = """ At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. """ p = Paragraph(ptext, style=normal) p.wrapOn(self.c, self.width-50, self.height) p.drawOn(self.c, 30, 600) #---------------------------------------------------------------------- def createLineItems(self): """ Create the line items """ text_data = ["Line", "DOS", "Procedure
/Modifier", "Description", "Units", "Billed
Charges", "Type1
Reductions", "Type2
Reductions", "Type3
Reductions", "Allowance", "Qualify
Code"] d = [] font_size = 8 centered = ParagraphStyle(name="centered", alignment=TA_CENTER) for text in text_data: ptext = "%s" % (font_size, text) p = Paragraph(ptext, centered) d.append(p) data = [d] line_num = 1 formatted_line_data = [] for x in range(200): line_data = [str(line_num), "04/12/2013", "73090", "Test Reflexes", "1", "131.00", "0.00", "0.00", "0.00", "0.00", "1234"] for item in line_data: ptext = "%s" % (font_size-1, item) p = Paragraph(ptext, centered) formatted_line_data.append(p) data.append(formatted_line_data) formatted_line_data = [] line_num += 1 table = Table(data, colWidths=[20, 40, 45, 120, 30, 40, 50, 50, 50, 50, 30]) self.story.append(table) #---------------------------------------------------------------------- if __name__ == "__main__": t = Test() t.run()
Now we’ll need to spend a little bit of time going over what’s happening here. First off, we import a whole bunch of various items from Reportlab. Next we create our Test class. We initialize a few things and then we start getting to the good stuff. The coord method is something fun I found on StackOverflow that helps a lot in positioning flowables on the canvas object. We’ll just skip that method and head on over to run. Here we create our document object and our story list. You’ll note that we’ve already put a Spacer into it that’s 2.5 inches in width. That’s the amount of space that is reserved for the canvas to draw in. Next we call our createLineItems method which creates a 200 row Table object that is added to our story.
Then we call the doc object’s build method and tell it to execute createDocument. As you may have guessed, build will actually create the PDF itself. The createDocument method contains the canvas drawing code. Once everything is done, we print out a message to stdout to let the user know that their new document is ready for viewing!
Wrapping Up
At this point, you have the knowledge needed to write a mash-up of static content and flowables. You might like to know that you can also pass the build method an onLastPages parameter which tells it to call another method of your own. Most examples that show onFirstPage and onLastPages use them for headers and footers. Maybe that’s their main purpose, but you can use them for yourself. If you go digging in the source, you’ll discover you can also pass a list of PageTemplates which could make really complex layouts that much easier. Anyway, I hope this article was helpful to you and that you’ll be able to use this new information in your own code. Have fun!
Related Articles
- Reportlab: Mixing Fixed Content and Flowables
- Reportlab Tables รขโฌโ Creating Tables in PDFs with Python
- >A Simple Step-by-Step Reportlab Tutorial
I’m glad you’re finding them so useful. I wish I had time to write more. Thanks for reading!
Glad you found it helpful.
Thank you – it fits perfectly for me. But how can I place the table in a fixed position (x,y coordinates) in this example (a combination with this post https://www.blog.pythonlibrary.org/2012/06/27/reportlab-mixing-fixed-content-and-flowables/)? Best regards, Stefan
The table has the same methods as the Paragraph, so you can still use wrapOn() and drawOn() with it to help you in positioning the table.
This was very useful, thank you very very much ๐ good work ๐
Thanks! I’m glad you found it so helpful!