Monday, July 02, 2012

Displaying PDF Form Fields Using iText and ColdFusion

One of my most recently developed web application is a new health care system for a number of laborites that perform various testing. The initial workflow has the central office receiving the lab test order and sample, entering it into the system, and then sending the sample and five or six (depending on the test ordered) worksheets along to the lab techs. Currently the worksheets that have to be completed are a variety of MS Excel, MS Word and an Adobe PDF file. This can take anywhere between 3 to 8 minutes to complete, depending on which forms are needed and what information is available on the test order. They asked for an easier way to do this.

I decided to explore generating a single PDF file that would be generated after the test order is entered in the system. The fields on the various documents that normally the administrative staff have to fill in would be populated by the web application. Normally this would be easy to do using cfpdfform, however for this project we’re using Railo instead of Adobe ColdFusion and the cfpdfform tag isn’t available. I was hoping that Railo 4 would include this tag as a feature, but there was no mention of it being included at cf.Objective() 2012 by the Railo team when I asked. I’ve asked on the Railo list twice, initially they said yes but most recently they haven’t responded. Therefore I needed to find an alternative solution, which I did, and here’s what I ended up doing.

Create a single Adobe Acrobat PDF Form

The first step was to use Adobe Acrobat Professional to combine the MS Excel, MS Word and Adobe PDF files into a single PDF file. The next step was to create the form fields within the PDF for the web application to populate from the database. Since every page had a “name” field, we just made that field name the same on each page.

Get iText

iText is a Java component that is designed to work with PDF files. It can create, read and update PDF files. The technical documentation on the website is pretty thin, they really encourage you to buy a copy of their “iText in Action” book. I’d have no problem with this, but they don’t currently offer a Kindle version yet so I’m holding off on that. I did find the API documentation is available online that you can use, and they do have the samples from the book available for download that may help.

Download the iText PDF community version from here. I decompressed these files and placed them in the same directory as I had my Railo JAR files, then I restarted Apache Tomcat. I’m pretty sure I could put these in my ColdFusion on Wheels application directory /lib directory and load them using the javaLoader for CFWheels, but I haven’t tried it yet.

CFDump the form fields

The next thing I wanted to do was to dump the form fields from the PDF file to make sure I was able to read the file correctly. I placed the single PDF file that we had created with all of the form fields with my ColdFusion on Wheels application directory. I then created a CFML page and used the following code to dump the form fields that are found within the entire file.


readPDF = expandpath("the_file_name_here.pdf");
writePDF = expandpath("#createUUID()#.pdf");
fileIO = createObject("java","java.io.FileOutputStream").init(writePDF);
reader = createObject("java","com.itextpdf.text.pdf.PdfReader").init(readPDF);
stamper = createObject("java","com.itextpdf.text.pdf.PdfStamper").init(reader, fileIO);
pdfForm = stamper.getAcroFields();


This dumped out a list of all of the form fields within the PDF so I can make sure that iText was able to read them, and that I spelled them correctly.

Populate the Form Fields

The next step was to populate these fields with data from my database. I also wanted to display this PDF in the users browser. I was able to to this pretty much as I did above, but just with a minor changes.


readPDF = expandpath("the_file_name_here.pdf");
writePDF = expandpath("#createUUID()#.pdf");
fileIO = createObject("java","java.io.FileOutputStream").init(writePDF);
reader = createObject("java","com.itextpdf.text.pdf.PdfReader").init(readPDF);
stamper = createObject("java","com.itextpdf.text.pdf.PdfStamper").init(reader, fileIO);
pdfForm.setField("date_sample_collected", "#date_sample_collected#");
pdfForm.setField("patient_name", "#patient_name#");
pdfForm.setField("date_of_birth", "#date_of_birth#");
pdfForm.setField("order_number", "#order_number#");
stamper.setFormFlattening(true);
stamper.close();
reader.close();
fileIO.close();


Summary

Using this code I was able to satisfy the initial request that the administrative staff, to save them from creating these additional forms manually with the data they had just entered into the web application.

I know I can make this better though, since not every page of that PDF is needed, it depends on the test selected. I plan to re-factor this application code in the future to to determine which test was selected, then only grab those worksheets. This means I’ll need to separate each of the worksheets out into a separate PDF file, then stitch them together, depending on what the user has entered for the order, into a single file.

Thanks To

Thanks to this cfSearching blog post that helped, as well as rip747’s Stackoverflow posting that helped me complete this task.

2 comments:

Anonymous said...

I was wondering if you can maybe help me out. I'm trying to follow your example with iText and Railo in order to dump the form fields out of my PDF but all I get is a blank page. The code I'm using is below:



readPDF = expandpath("new_patient_registration.pdf");
writePDF = expandpath("#createUUID()#.pdf");
fileIO = createObject("java","java.io.FileOutputStream").init(writePDF);
reader = createObject("java","com.itextpdf.text.pdf.PdfReader").init(readPDF);
stamper = createObject("java","com.itextpdf.text.pdf.PdfStamper").init(reader, fileIO);
pdfForm = stamper.getAcroFields();

Anonymous said...

Hey Troy! Thanks for this tutorial/article. It just saved me. Migrated to Railo and didn't realize it doesn't have CFPDFFORM. Huge oversight on our part but none-the-less. Thank you!!!