No registered users in community rubick
in last 10 minutes
in last 10 minutes
Dynamically generating PDF files with ReportLab
Introduction
It is pretty easy to generate dynamic PDF files from OpenACS. The hardest part is learnng Python and the Reportlab API. Getting to a 'Hello World' PDF is an easy process. In my case, I pulled the current date from the database and displayed it tiled on the PDF file.You'll need to be familiar enough with packages that you can set up a new package. You should probably refer to the following thread:
Thread on PDF generation
This document is released under GPL.
Install Reportlab
First of all, you need to install Reportlab, a Python based program which generates PDF files for you. For Debian Linux:- apt-get install python2.2 (other Linux distributions: install python in some way)
- Download the tgz file for ReportLab
- You then tar -xzf ReportLab.tgz
- For Debian, you then want to move the reportlab directory to /usr/local/lib/python2.2/site-packages/
- become another user
- $ python2.2
- import reportlab (see if it works)
Download the User Guide (in PDF format, of course), and go to the installation section. Follow their directions.
Install the Python Imaging Library
If you'd like to include bitmap images in your PDF files, you need to install the Python Imaging Library. I didn't do this, but I may come back to it later. Directions for installing this are in the User Guide, but the link in it was incorrect. Go to http://www.pythonware.com/products/pil/index.htm instead.
Create a new package, and set up procedures
If you're creating an OpenACS package, you probably should set it up now (or copy Tillmann's file as a starting point). In your packagename/tcl directory, place the following procedures.# /packagename/tcl/pdf-defs.tcl ad_library { by Jade Rubick (jade@bread.com) based on code by Tilmann Singer (tils@tils.net) License: GPL Generate pdf, preferably in the background. Note that there is no locking yet, so there may be more than 1 process generating the same pdf. But besides being inefficient this has no side-effects. Note this should also use namespaces! } ad_proc pdf_file_stub { type id } { # add in a security checking field, so that # you aren't vulnerable to attacks of the variety: # pdf_file_stub \"../../wherever/i/want/to/go\" #if { regexp { ([\.\.]) } $type } { #return "thats illegal bucko" #} return "/tmp/$type-$id" } ad_proc pdf_generate_pdf_if_necessary { type id } { if { ![file exists "[pdf_file_stub $type $id].pdf"] } { pdf_generate_pdf $type $id } } ad_proc pdf_return_pdf { type id } { pdf_generate_pdf_if_necessary $type $id ns_returnfile 200 application/pdf "[pdf_file_stub $type $id].pdf" } ad_proc pdf_generate_pdf { type id } { ns_log notice "!> Starting to generate pdf for type: $type id $id" file delete "[pdf_file_stub $type $id].txt" file delete "[pdf_file_stub $type $id].pdf" # there is probably a better way to do this, but you # can put checks in here for each type of PDF that you # generate, according to "type", and use this procedure # to generate the PDF file. # the PDF file is generated from a text file, which is # read directly from the database. Yes, this is less than # an optimal solution. So improve it and share your code # with me! # example if {$type == "test"} { # pull something from the database db_1row date "select to_char(sysdate,'Mon fmDD, YYYY HH:MI am') as today from dual" set txt_file [open "[pdf_file_stub $type $id].txt" w] fconfigure $txt_file -encoding "iso8859-1" puts $txt_file "$today" close $txt_file # the logic for creating the businesscard is in the Python file. exec "/web/intranet/bin/pdf-test.py" [pdf_file_stub $type $id] >> /tmp/$type.log 2>> /tmp/$type.log } elseif {$type == "business_card"} { db_1row card "select * from bu_cards where card_id=:card_id" # uh, ugly. no xml, just one value per line. saves me typing. set txt_file [open "[pdf_file_stub $type $id].txt" w] fconfigure $txt_file -encoding "iso8859-1" puts $txt_file "$name\n$subtitle\n$line1\n$line2\n$line3" close $txt_file # the logic for creating the businesscard is in the Python file. exec "/web/bin/pdf-businesscard.py" [pdf_file_stub $type $id] >> /tmp/$type.log 2>> /tmp/$type.log } ns_log notice "!> Finished with type: $type id $id" } 4. Insert the pdf-xxx.py Python files. I put mine in /web/bin/, but you can put it where you like, as long as you modify the Tcl procedures. /web/bin is probably a bad idea. Here are the Python files: # pdf-test.py #!/usr/bin/python import sys, copy, string, os, fileinput from reportlab.lib.units import cm from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer from reportlab.lib.styles import getSampleStyleSheet from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 bottom_margin = 1*cm left_margin = 2*cm height=5.4*cm width=8.5*cm file_stub = sys.argv[1] # never got around dealing with stdin that comes unicode encoded from # aolserver, grr. The file approach works though. lines = fileinput.input(file_stub + ".txt") today = lines[0] def onecard(c): t = c.beginText() t.setTextOrigin(0.5*cm, 3.6*cm) t.setFont("Helvetica-Bold", 12) t.textLines(today) c.drawText(t) #c.rect(0, 0, width, height) def grid(c): for i in range(0, 6): y = bottom_margin + i * height c.line(5, y, 20, y) c.line(A4[0] - 5, y, A4[0] - 20, y) for i in range(0, 3): x = left_margin + i * width c.line(x, 5, x, 20) c.line(x, A4[1] - 5, x, A4[1] - 20) def myFirstPage(c, doc): c.line(line_margin,height-margin_from_top,width-line_margin,height-margin_fr om_top) onecard() c = canvas.Canvas(file_stub + ".pdf") grid(c) c.translate(left_margin, bottom_margin) c.saveState() for i in range(0, 5): onecard(c) c.translate(0, height) c.restoreState() c.translate(width, 0) for i in range(0, 5): onecard(c) c.translate(0, height) c.save() # pdf-businesscard.py #!/usr/bin/python import sys, copy, string, os, fileinput from reportlab.lib.units import cm from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer from reportlab.lib.styles import getSampleStyleSheet from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import A4 bottom_margin = 1*cm left_margin = 2*cm height=5.4*cm width=8.5*cm file_stub = sys.argv[1] # never got around dealing with stdin that comes unicode encoded from # aolserver, grr. The file approach works though. lines = fileinput.input(file_stub + ".txt") name = lines[0] subtitle = lines[1] line1 = lines[2] line2 = lines[3] line3 = lines[4] def onecard(c): t = c.beginText() t.setTextOrigin(0.5*cm, 3.6*cm) t.setFont("Helvetica-Bold", 12) t.textLines(name) t.setFont("Helvetica", 10) t.textLines(subtitle) t.moveCursor(0, 10) t.textLines(line1) t.textLines(line2) t.textLines(line3) c.drawText(t) #c.rect(0, 0, width, height) def grid(c): for i in range(0, 6): y = bottom_margin + i * height c.line(5, y, 20, y) c.line(A4[0] - 5, y, A4[0] - 20, y) for i in range(0, 3): x = left_margin + i * width c.line(x, 5, x, 20) c.line(x, A4[1] - 5, x, A4[1] - 20) def myFirstPage(c, doc): c.line(line_margin,height-margin_from_top,width-line_margin,height-margin_fr om_top) onecard() c = canvas.Canvas(file_stub + ".pdf") grid(c) c.translate(left_margin, bottom_margin) c.saveState() for i in range(0, 5): onecard(c) c.translate(0, height) c.restoreState() c.translate(width, 0) for i in range(0, 5): onecard(c) c.translate(0, height) c.save()