Sunday, December 16, 2007

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

While perusing the iText site yesterday, I stumbled across some newer code samples and a great example of creating a VIP pass to a film festival using iText. The code involves some interesting techniques I have not used before. Here is what the final product looks like. Pretty cool. So of course I had to try it out in ColdFusion!

Now the complete example on the iText site actually uses an HTML form you can fill out. The form information is then used to generate the PDF file. Since most of you are already familiar with basic HTML forms I will get to that part later.

Source: Creating an Accreditation Card with PDF Forms

What you will need for this example



1. A recent version of iText like 2.0.7. For instructions, see How to use a newer version of iText

2. This example uses three (3) images. You can download the first two images from the iText examples directory.

http://itext.ugent.be/wiki/examples/resources/kubrick.jpg
http://itext.ugent.be/wiki/examples/resources/fflogo.jpg

Since we are creating a VIP pass, the third image should be a photo of the lucky recipient. Yourself perhaps? ;) Okay, I confess.. not everyone will receive a VIP pass. But who wants to read a tutorial on creating a film festival pass for J. Nobody (Schlepper - 3rd class) ?


Creating the form


In Part 1 we are going to create the PDF form that will be used a template for the film festival passes. The form will contain the basic images, colors a few text fields.

First we initialize a few variables with the file paths used in the example. Most of my files are located in the same directory as the .cfm script. You can change the paths as needed


UPDATE: I wanted to emphasize an issue that is mentioned in the instruction link above. MX users that are running the JavaLoader.cfc must read the article Using a Java URLClassLoader in CFMX Can Cause a Memory Leak.

In summary it is recommended that you store a single instance of the javaLoader in the server scope, rather than creating a new object each time (as in the code below). This prevents a memory leak caused by a bug in ColdFusion MX. I do not know if this bug exists in ColdFusion 8.

The code for this example creates a new instance of the JavaLoader on each request. This was intended only to demonstrate how to use the JavaLoader. To avoid any confusion, my future code examples will use a server scoped object instead.



<cfscript>
// cfSearching: All file paths are relative to the current directory
// cfSearching: Your file paths may be different
fullPathToOutputFile = ExpandPath("./accreditation_form.pdf");
fullPathToKubrikImage = ExpandPath("./kubrick.jpg");
fullPathToFFLogoImage = ExpandPath("./fflogo.jpg");
fullPathToYourImage = ExpandPath("./yourPersonImage.jpg");
fullPathToITextJar = ExpandPath("./iText-2.0.7.jar");
dotNotationPathToJavaLoader = "com.javaloader.JavaLoader";
</cfscript>


Next we will define the dimensions of the PDF document. You may notice the width and height are in millimeters. Since the Document size is measured in points we must first convert this value to points. The conversion is done using a custom function mmToPoints() that will be shown later.


<cfscript>
// cfSearching: page width and height in millimeters
WIDTH = mmToPoints(53.98);
HEIGHT = mmToPoints(85.60);
</cfscript>


Next we create several java.awt.Color objects that will be used for the text color and background. We also use the FontFactory to load HELVETICA as our base font.


<cfscript>
// cfSearching: create colors used in the form template
color = createObject("java", "java.awt.Color");
RED = color.init( javacast("int", 255), javacast("int", 0), javacast("int", 80) );
GREEN = color.init(javacast("int", 192), javacast("int", 222), javacast("int", 30) );
BLUE = color.init(javacast("int", 0), javacast("int", 173), javacast("int", 205) );
ORANGE = color.init(javacast("int", 241), javacast("int", 165), javacast("int", 1) );
GRAY = color.init(javacast("int", 192), javacast("int", 192), javacast("int", 192) );
BLACK = color.init(javacast("int", 0), javacast("int", 0), javacast("int", 0) );
WHITE = color.init(javacast("int", 255), javacast("int", 255), javacast("int", 255) );
BaseFont = javaLoader.create("com.lowagie.text.pdf.BaseFont");
FONT = javaLoader.create("com.lowagie.text.FontFactory").getFont(BaseFont.HELVETICA,
BaseFont.WINANSI, BaseFont.NOT_EMBEDDED).getBaseFont(); </cfscript>


Finally it is time to start generating the form. First we create a new Document with custom dimensions. Then get an instance of the PDFWriter to generate the output file.


<cfscript>
// cfSearching: create a document with the preset page dimensions
pageDimensions = javaLoader.create("com.lowagie.text.Rectangle").init( WIDTH, HEIGHT );
document = javaLoader.create("com.lowagie.text.Document").init(pageDimensions);

// we create a writer that listens to the document and directs a PDF-stream to a file
outStream = createObject("java", "java.io.FileOutputStream").init(fullPathToOutputFile);
writer = javaLoader.create("com.lowagie.text.pdf.PdfWriter").getInstance(document, outStream);

</cfscript>


Next we open the document, and use the directContent to start adding the background colors. First a red band is added across the top section of the form. Then a narrower black band is added to give the effect of three stripes: red, black, red.


<cfscript>
// step 3
document.open();
// step 4
directContent = writer.getDirectContent();

// top part of the card
// cfSearching: create a red band across the top portion of the document
directContent.setColorFill(RED);
directContent.rectangle(javacast("float", 0), mmToPoints(57), WIDTH, HEIGHT - mmToPoints(57));
directContent.fill();

// cfSearching: overlay a smaller black band in the middle
directContent.setColorFill(BLACK);
directContent.rectangle(javacast("float", 0), mmToPoints(61), WIDTH, mmToPoints(21));
directContent.fill();

directContent.setColorFill(WHITE);

</cfscript>


If you were to close the document after these steps, this is how the PDF files would appear.



Next we add the images and festival name to the section we just filled with color.


<cfscript>
// cfSearching: add the kubrick photo and logo to the top section
Image = javaLoader.create("com.lowagie.text.Image");
kubrick = Image.getInstance(fullPathToKubrikImage);
kubrick.scaleToFit(WIDTH, mmToPoints(21));
kubrick.setAbsolutePosition( javacast("float", 0), mmToPoints(61));
directContent.addImage(kubrick);

logo = Image.getInstance(fullPathToFFLogoImage);
logo.scaleToFit(mmToPoints(14), mmToPoints(14));
logo.setAbsolutePosition(mmToPoints(37), mmToPoints(65));
directContent.addImage(logo);

// cfSearching: add the festival name just beneath the photo and logo
Element = javaLoader.create("com.lowagie.text.Element");
directContent.beginText();
directContent.setFontAndSize(FONT, javacast("float", 7));
directContent.showTextAligned(Element.ALIGN_CENTER,
"Flanders International Film Festival-Ghent", WIDTH / 2,
mmToPoints(58.5), javacast("float", 0) );
directContent.endText();
</cfscript>



We continue down the form and populate the footer with the festival website url and festival date. We also use the moveTo() and lineTo() methods to create two vertical dashes. The moveTo() method sets the line starting position and lineTo() sets the ending position, and draws a line in between the two points.


<cfscript>
Rectangle = javaLoader.create("com.lowagie.text.Rectangle");
TextField = javaLoader.create("com.lowagie.text.pdf.TextField");

// cfSearching: add the gray footer
filmfestival = TextField.init(writer, rectangle.init( javacast("float", 0),
javacast("float", 0), WIDTH, mmToPoints(9)),
"filmfestival");
filmfestival.setBackgroundColor(GRAY);
filmfestival.setTextColor(WHITE);
filmfestival.setText("www.filmfestival.be");
filmfestival.setFontSize(14);
filmfestival.setOptions(TextField.READ_ONLY);
filmfestival.setAlignment(Element.ALIGN_CENTER);
writer.addAnnotation(filmfestival.getTextField());

// cfSearching: add the festival date just above the footer
directContent.beginText();
directContent.moveText(mmToPoints(1.5), mmToPoints(12));
directContent.setFontAndSize(FONT, javacast("float", 21) );
directContent.setColorFill(RED);
directContent.showText("10");
directContent.setColorFill(BLUE);
directContent.showText("/");
directContent.setColorFill(RED);
directContent.showText("21");
directContent.setColorFill(BLUE);
directContent.showText("OCT");
directContent.setColorFill(GREEN);
directContent.showText("2006");
directContent.endText();
directContent.setColorStroke(BLUE);
directContent.moveTo(mmToPoints(24.5), mmToPoints(29));
directContent.lineTo(mmToPoints(24.5), mmToPoints(36));
directContent.moveTo(mmToPoints(24.5), mmToPoints(48));
directContent.lineTo(mmToPoints(24.5), mmToPoints(54));
directContent.stroke();
</cfscript>


Finally, we add a few text boxes for displaying the card number and owner's name. Then we create an image icon only PushButtonField that will be used to display the owner's image.


<cfscript>
// bottom part of the card
// central part of the card
PushbuttonField = javaLoader.create("com.lowagie.text.pdf.PushbuttonField");
photo = PushbuttonField.init(writer,
rectangle.init(mmToPoints(3), mmToPoints(29), mmToPoints(24), mmToPoints(54)),
"photo");
t1 = directContent.createTemplate(mmToPoints(21), mmToPoints(25));
t1.setColorFill(GRAY);
t1.rectangle( javacast("float", 0), javacast("float", 0), mmToPoints(21), mmToPoints(25));
t1.fill();
photo.setTemplate(t1);
photo.setLayout(PushbuttonField.LAYOUT_ICON_ONLY);
writer.addAnnotation(photo.getField());
type = TextField.init(writer, rectangle.init( mmToPoints(26), mmToPoints(46),
WIDTH - mmToPoints(1.5), mmToPoints(54)), "type");
type.setTextColor(GRAY);
type.setText("TYPE");
type.setFontSize(0);
writer.addAnnotation(type.getTextField());

number = TextField.init(writer, rectangle.init( mmToPoints(26),
mmToPoints(44), WIDTH - mmToPoints(1.5), mmToPoints(48)), "number");
number.setText("N° 0000000");
number.setFontSize(8);
writer.addAnnotation(number.getTextField());

name = TextField.init(writer, rectangle.init( mmToPoints(26),
mmToPoints(28), WIDTH - mmToPoints(1.5), mmToPoints(40)), "name");
name.setText("Name");
name.setFontSize(8);
name.setOptions(TextField.MULTILINE);
writer.addAnnotation(name.getTextField());

barcode = PushbuttonField.init(writer, rectangle.init(mmToPoints(3),
mmToPoints(23), WIDTH - mmToPoints(3), mmToPoints(28)), "barcode");

t2 = directContent.createTemplate(WIDTH - mmToPoints(6), mmToPoints(5));
t2.setColorFill(GRAY);
t2.rectangle(0, 0, WIDTH - mmToPoints(6), mmToPoints(5));
t2.fill();
barcode.setTemplate(t2);
barcode.setLayout(PushbuttonField.LAYOUT_ICON_ONLY);
writer.addAnnotation(barcode.getField());

</cfscript>


We now have a PDF form that can be used as a template in the next installment of this example.



Complete Code

Stay tuned for Part 2!

2 comments:

Anonymous,  December 16, 2007 at 7:04 AM  

It's always nice to see people enjoy themselves with iText. If you want to promote your examples, don't hesitate to post a link to them on the mailing list: itext-questions@lists.sourceforge.net
It's always nice to have references to how-to articles in the archives.

cfSearching December 16, 2007 at 3:22 PM  

Thanks. I appreciate the comment and may do that. The java tutorials and examples in the archives have certainly been helpful to me.

Thanks also for iText :) I am having a blast with it!

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep