Tuesday, December 18, 2007

Creating a wicked Film Festival VIP Pass with iText - Part 2

In Part 2 of this example, we are going to the use the PDF file we created in Part 1 as a template for generating the film festival cards. When we are finished the final product should look something like this. So let us get started.

We will first create an HTML form for entering the information to be displayed on the accreditation card. You can view a working form online at the iText site. Now unlike the html form on the iText site, our select list values below start at 1, not 0. The reason for this is that the values are used later in the code as an array index. Since CF arrays are 1-based, it made sense to start the values at 1, not 0.

<style type="text/css">
tr.unity { font-family: verdana,arial,helvetica; }
td { padding: 5px 5px 7px 8px; }

<h1>Accreditation Card Example - Step 2 (FillForm)</h1>
<form action="FilmFestivalFormAction.cfm" method="POST" enctype="multipart/form-data">
<tr class="unity">
<td><input type="Text" name="name" ></td>
<tr class="unity">
<td><input type="Text" name="number" ></td>
<tr class="unity">
<td><select name="type">
<option value="1">Guest</option>
<option value="2">Student</option>
<option value="3">Press</option>
<option value="4">Jury</option>
<option value="5">Vip Pass</option>
<option value="6">Crew</option>
<select name="color">
<option value="1">No admittance</option>
<option value="2">Press screenings only</option>
<option value="3">All screenings, no priority</option>
<option value="4">All screenings, priority</option>
<option value="5">Top priority</option>
<tr class="unity">
<td><input type="File" name="photo"></td>
<tr class="unity">
<td><input type="Checkbox" name="flatten" value="1"></td>
<tr class="unity"><td> </td><td><input type="Submit" value="Generate the PDF"></td></tr>

When the form is submitted we will use a simple CFFILE call to upload the photo. Since the photo is optional the sample code ignores any upload errors that may occur. You can modify this behavior, and the accepted images types if desired.

cfSearching: all file paths are relative to the current
directory. Your file paths may be different
<cfset fileSeparator = "/">
<cfset fullPathToPhotoFile = "">
<cfset fullPathToTemplateForm = ExpandPath("./accreditation_form.pdf")>
<cfset fullPathToOutputFile = ExpandPath("./accreditation_1.pdf")>
<cfset fullPathToITextJar = ExpandPath("./iText-2.0.7.jar") >
<cfset dotNotationPathToJavaLoader = "javaloader.JavaLoader" >

<!--- if a file was submitted, upload it to the current directory --->
<cfif structKeyExists(form, "photo") AND len(trim(form.photo))>
<cffile action="upload" filefield="form.photo"
accept="image/jpeg, image/pjpeg, image/gif, image/png"

<cfset fullPathToPhotoFile = savedTo.serverDirectory & fileSeparator & savedTo.serverFile >

<cfcatch type="any">
<!--- ignoring upload errors --->

Finally the code converts all file separator's to "/". This was thanks to a tip from Sean Corfield who pointed out that "/" should work on all operating systems. I would like to mention that normally I would place this type of code in a utility component function. But in the interest of simplicity I chose not to do so here.

<!--- fix the file paths. the "/" separator *should* work on all O/S --->
<cfset fullPathToTemplateForm = replace(fullPathToTemplateForm, "\", fileSeparator, "all")>
<cfset fullPathToOutputFile = replace(fullPathToOutputFile, "\", fileSeparator, "all")>
<cfset fullPathToPhotoFile = replace(fullPathToPhotoFile, "\", fileSeparator, "all")>

Now for the fun part ..

We are now ready to generate our PDF file using the form field information. First we will create two arrays. The first array stores the different pass types: Guest, VIP, Press, etcetera. The other contains java.awt.Color objects used to indicate the level of admittance.

A brief digression about the java code versus the CF code below. In the java example, the arrays are defined as constants. This means the arrays are created only once. You could do something similar by placing the code in a component, and initializing the arrays in the component's init() function. Then store the component in a shared scope like application. But again, in the interest of simplicity I chose not to do that here.

// cfSearching: the array positions are signifigant. they correspond to the
// cfSearching: select list values in the HTML form
passType = listToArray("Guest,Student,Press,Jury,VIP Pass,Crew");

color = createObject("java", "java.awt.Color");
BLUE = color.init( javacast("int", 0), javacast("int", 173), javacast("int", 205));

// cfSearching: array colors are GRAY, GREEN, RED, BLUE, ORANGE
passColor = arrayNew(1);
arrayAppend(passColor, color.init( javacast("int", 192), javacast("int", 192), javacast("int", 192)) );
arrayAppend(passColor, color.init( javacast("int", 192), javacast("int", 222), javacast("int", 30)) );
arrayAppend(passColor, color.init( javacast("int", 255), javacast("int", 0), javacast("int", 80)) );
arrayAppend(passColor, BLUE );
arrayAppend(passColor, color.init( javacast("int", 241), javacast("int", 165), javacast("int", 1)) );

Next we will use the JavaLoader.cfc to create a PdfReader and PdfStamper object. The PdfReader is used to read in the template file. The PdfStamper object will be used to manipulate the form field values and generate the new PDF file.

// cfSearching: create a reader and stamper for manipulating the template form
pdfReader = javaLoader.create("com.lowagie.text.pdf.PdfReader").init( fullPathToTemplateForm );
outStream = createObject("java", "java.io.FileOutputStream").init( fullPathToOutputFile );
pdfStamper = javaLoader.create("com.lowagie.text.pdf.PdfStamper").init(pdfReader, outStream);

Using the PdfStamper object we will fill in the owner's name, pass type, color and number. We will also change the background color of the film festival url field from gray to blue.

// cfSearching: FORM is a reserved CF word so we will use "formFields" instead
formFields = pdfStamper.getAcroFields();
formFields.setField("name", javacast("string", FORM.name) );
formFields.setFieldProperty("type", "textcolor", passColor[FORM.color], javacast("null", "") );
formFields.setField("type", javacast("string", passType[FORM.type]) );
formFields.setField("number", "N° "& FORM.number );
formFields.setFieldProperty("filmfestival", "bgcolor", BLUE, javacast("null", "") );

Next we replace the gray photo button in the template with a new button that uses the uploaded image as an icon. We use the AcroFields.replacePushbuttonField() method which makes replacing a button quite simple.

if ( FileExists(fullPathToPhotoFile) ) {
PhotoImage = javaLoader.create("com.lowagie.text.Image").getInstance( fullPathToPhotoFile );
bt = formFields.getNewPushbuttonFromField("photo");
bt.setLayout( PushbuttonField.LAYOUT_ICON_ONLY );
bt.setImage( PhotoImage );
formFields.replacePushbuttonField("photo", bt.getField());

In what may be the coolest part of this example, we use the FORM.number to generate an actual bar code and use it as a button icon. Now if you are already familiar with bar codes that may not be very impressive to you. Personally, I know very little about them so it was quite interesting to me. Especially after realizing it was an actual bar code that varied based on the FORM.number entered.

try {
code = javaLoader.create("com.lowagie.text.pdf.BarcodeInter25").init();
code.setBarHeight( mmToPoints(3) );
// cfSearching: pad the number with leading zeroes
barCodeNumber = trim(FORM.number);
offset = 13 - len(barCodeNumber);
if ( offset LT 13) {
barCodeNumber = right(repeatString("0", offset) & barCodeNumber, 13);
code.setCode( javacast("string", barCodeNumber) );
code.setFont( javacast("null", "") );
cb = javaLoader.create("com.lowagie.text.pdf.PdfContentByte").init(pdfStamper.getWriter());
template = code.createTemplateWithBarcode(cb, javacast("null", ""), javacast("null", ""));
bt = formFields.getNewPushbuttonFromField("barcode");
formFields.replacePushbuttonField("barcode", bt.getField());
} catch (java.lang.Exception e) {
// not a valid code, do nothing

Finally we apply the selected flattening setting and close the file. While this example creates a file, rather than streaming the PDF directly to the browser, the code could easily be modified to do so.

pdfStamper.setFormFlattening( javacast("boolean", FORM.flatten) );

Enter the confessional

I confess my curiosity about the BarcodeInter25 method got the best of me and delayed the completion of Part 2 of this example. But I can now confirm there is a wealth of interesting information about bar codes on the internet.. if you like that kind of thing ;) But I will save that for another entry.

Here is the complete code for Part 2. As always comments/suggestions/corrections are always welcome!


Anonymous,  January 21, 2009 at 3:16 PM  

thanks so much for your tutorials on how to use iText with ColdFusion. i'm only just getting started with how to use it.

at the moment i'm trying to create a text form field that accepts multiple lines of text, like paragraphs.

i'm having trouble implementing this, is it possible with iText?

again, thanks so much.


cfSearching January 22, 2009 at 4:55 PM  

Hi maya,

Yes, TextField's have a simple setting that allows "multiline" text. Here is a modification of one of the great examples from the iText site:

pathToOutFile = "c:\myFile.pdf";
document = createObject("java", "com.lowagie.text.Document").init();
outFile = createObject("java", "java.io.FileOutputStream").init(pathToOutFile);
writer = createObject("java", "com.lowagie.text.pdf.PdfWriter").getInstance( document, outFile );
// create text field
Rectangle = createObject("java", "com.lowagie.text.Rectangle");
TextField = createObject("java", "com.lowagie.text.pdf.TextField");
field = TextField.init(writer, Rectangle.init(67, 700, 340, 800), "myFieldName");
// change settings to allow multi-line text
// save the file to disk

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep