Thursday, December 20, 2007

Understanding the iText BarcodeInter25 method

One of the cooler aspects of the Creating a wicked Film Festival VIP Pass with iText example was the use of bar codes. Not being familiar with them, or the BarcodeInter25 method used in the example, I did some searching. Thanks to wikipedia and barcode-1.net I discovered that the iText method generates a bar code in a standard format known as Interleaved 2 of 5.

Inter-whaaat?


The Interleaved 2 of 5 format is used to encode pairs of numbers using a set of bars. Each number, 0 through 9, is represented by a set of 5 bars. The number zero is represented by NNWWN, where "N" is a narrow bar and "W" is a wide bar.


0 NNWWN
1 WNNNW
2 NWNNW
3 WWNNN
4 NNWNW
5 WNWNN
6 NWWNN
7 NNNWW
8 WNNWN
9 NWNWN

The term interleaved means that two numbers are encoded in a set of 5 bars and the spaces in between those bars. The first number is represented by the black bars. The second number is represented by the spaces in between the bars. This bar code format begins with a start code NNNN and ends with a stop code WNN.

Because I am curious *cough* geek *cough* , I created a simple bar code to test this information. As you can see this is the bar code I generated.



After counting the bars things seemed to match up, but then I remembered the descriptions said this format encodes pairs of numbers. My sample number contained 7 digits, so why did it work? Then I remembered the sample code adds a checksum digit. So my 7 digit number plus the 1 digit checksum produces an even number of digits to encode.

How is the checksum calculated


According to barcode-1.net this format uses the modulo 10 scheme to calculate the checksum. So I took my sample number and calculated what the checksum digit should be.

My base number is: 0012345

1. Add the digits in odd numbered positions: 0 + 1 + 3 + 5 == 9
2. Multiply the result by three: 9 x 3 == 27
3. Add the digits in even numbered positions: 0 + 2 + 4 == 6
4. Add the two results together: 6 + 27 == 33
5. Calculate modulo: 33 mod 10 == 3
6. Subtract the modulo from ten: 10 - 3 == 7

The checksum for this number is: 7

I checked it against my sample bar code and it matched.



There are additional rules about the dimensions of the bars, but I will leave you to read more about it on your own. If you would like to generate a sample bar code PDF in ColdFusion, see the code below.

As usual these ramblings are based on my own unofficial reading, testing and observations. So corrections/questions/suggestions are welcome ;)

ColdFusion Code

<h1>Simple Bar Code Example</h1>

<cfscript>
savedErrorMessage = "";

// cfSearching: All file paths are relative to the current directory
fullPathToOutputFile = ExpandPath("./SimpleBarCodeExample.pdf");
fullPathToITextJar = ExpandPath("./iText-2.0.7.jar");
dotNotationPathToJavaLoader = "javaloader.JavaLoader";
barCodeNumber = "0012345";

// cfSearching: get an instance of the javaloader
pathsForJavaLoader = arrayNew(1);
arrayAppend(pathsForJavaLoader, fullPathToITextJar);
javaLoader = createObject("component", dotNotationPathToJavaLoader).init(pathsForJavaLoader);

// cfSearching: create color and font used for drawing text
color = createObject("java", "java.awt.Color");
BLACK = color.init(javacast("int", 0), javacast("int", 0), javacast("int", 0) );
BaseFont = javaLoader.create("com.lowagie.text.pdf.BaseFont");
FONT = javaLoader.create("com.lowagie.text.FontFactory").getFont(BaseFont.HELVETICA,
BaseFont.WINANSI, BaseFont.NOT_EMBEDDED).getBaseFont();

try {
// cfSearching: create a document with a custom page size 4 inches x 3 inches
pageDimensions = javaLoader.create("com.lowagie.text.Rectangle").init( inchesToPoints(4), inchesToPoints(3));
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);

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

directContent.beginText();
directContent.moveText(inchesToPoints(0.25), HEIGHT - inchesToPoints(0.75));
directContent.setFontAndSize(FONT, javacast("float", 16) );
directContent.setColorFill(BLACK);
directContent.showText("Barcode number: "& barCodeNumber);
directContent.endText();

Rectangle = javaLoader.create("com.lowagie.text.Rectangle");
PushbuttonField = javaLoader.create("com.lowagie.text.pdf.PushbuttonField");
barcode = PushbuttonField.init(writer, rectangle.init( inchesToPoints(0.25),
inchesToPoints(1),
WIDTH - inchesToPoints(0.25),
HEIGHT - inchesToPoints(1)),
"barcode");


// cfSearching: generate the barcode image. does nothing if code is invalid
try {
code = javaLoader.create("com.lowagie.text.pdf.BarcodeInter25").init();
code.setGenerateChecksum(true);
code.setBarHeight( inchesToPoints(1) );

code.setCode( javacast("string", barCodeNumber) );
code.setFont( javacast("null", "") );
cb = javaLoader.create("com.lowagie.text.pdf.PdfContentByte").init(writer);
template = code.createTemplateWithBarcode(cb, javacast("null", ""), javacast("null", ""));
barcode.setLayout(PushbuttonField.LAYOUT_ICON_ONLY);
barcode.setProportionalIcon(false);
barcode.setTemplate(template);
writer.addAnnotation(barcode.getField());

} catch (java.lang.Exception e) {
// not a valid code, do nothing
}

}
catch (com.lowagie.text.DocumentException de) {
savedErrorMessage = de;
}
catch (java.io.IOException ioe) {
savedErrorMessage = ioe;
}

// step 4
document.close();
outStream.close();
</cfscript>

<!--- show any errors --->
<cfif len(savedErrorMessage) gt 0>
Error - unable to create document
<cfdump var="#savedErrorMessage#">
</cfif>

<cffunction name="inchesToPoints" returntype="numeric" access="public" output="false"
hint="Converts inches to Postscript points">
<cfargument name="inches" type="numeric" required="true">
<cfreturn javacast("float", (arguments.inches * 72)) >
</cffunction>

6 comments:

Matthew September 1, 2009 at 7:02 AM  

Have you ever looked into the BarcodeDataMatrix classes in IText? I have been playing with these classes in the last few days and I would love to see some CF tutorials on how to use them. I got some barcodes working, but I still am not understanding some things.

-Matthew

cfSearching September 1, 2009 at 12:43 PM  

@Matthew,

No, not much beyond the basics. But I am always up for learning more about what makes things tick ;-) Any parts in particular did not make sense?

-Leigh

Anonymous,  September 1, 2009 at 8:05 PM  

@Leigh,

Well the setWidth() and setHeight() of the DataMatrix class can only be certain sizes, and i just was curious why that is so.

Also im trying to create an awt image using the createAwtImage(color,color) and then save that image out as a file .gif, .jpg etc, but i keep getting a black square.

if you use the createImage() it creates an com.lowagie.text.Image. I dont see a way to convert that to a graphic file, only know how to put it in a pdf document.

I can send you my cf code if you want to take a look at it. Let me know.

-Matthew (cant seem to login to google)

cfSearching September 1, 2009 at 10:24 PM  

@Matthew,

Yes, please email it to me. It is cfsearching (using either yahoo or gmail).

-Leigh

cfSearching September 2, 2009 at 1:38 AM  

@Matthew,

..setWidth() and setHeight() ...can only be certain sizes

Apparently it corresponds to the size of the symbols used to make up the barcode, and capacity

http://hem.bredband.net/aditus/chunkhtml/ch25.html#id2603018

Creating awt images works for me from java. So maybe there is a problem with your cf code or bar code. Are you checking the result of barcode.generate(...) to make sure it is valid? If it is not equal to BarcodeDatamatrix.DM_NO_ERROR, there was a problem.

I am not sure if there is a public way to convert com.lowagie.text.Image to an awt image. The simplest solution is probably to use createAwtImage with the default colors.

-Leigh

cfSearching November 5, 2009 at 7:46 PM  

FYI: I happened to come across this entry by Ryan Stille, where he created a cfc wrapper for Java4Less' 2d barcode reader/writer.
http://www.stillnetstudios.com/2d-barcodes-coldfusion/

Apparently Java4Less offers a free trial. So you could try test it out first along with the cfc at http://2dbarcode.riaforge.org/.

-Leigh

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep