Skip to main content
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Rex Aglibot
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Rex Aglibot

Rendering 1-Dimensional Barcodes With Zxing And ColdFusion

By
Published in Comments (4)

At PAI, we deal with manufacturing; and, manufacturing workflows run on barcodes. As such, I need to learn about rendering barcodes in ColdFusion; which, it turns out, is relatively easy thanks to the Zxing "Zebra Crossing" Java library from Sean Owen (currently in maintenance mode). Today, I wanted to look at various approaches to rendering 1-Dimensional barcodes (specifically the CODE_39 format) in ColdFusion since these are what I need to print in the immediate future.

In the ColdFusion community, there's a bunch of prior art on the matter. In fact, I'm like 15-years late to the conversation about using Zxing to render barcodes:

That said, I wanted to roll-up my sleeves and get my hands dirty as I start to build up the possible use-cases in my mind. First, I went to Maven Repository and downloaded two JAR files:

Then, since I'm using Lucee CFML, I created a utility function that proxies the Java class creation through fromJars(). This allows me to create an isolated Class Loader by providing a collection of JAR files with each call to the underlying createObject() function.

<cfscript>

	/**
	* I create a Java class from the given JAR files using an isolated classloader.
	*/
	private any function fromJars( required string classname ) {

		var jarPaths = [
			expandPath( "./vendor/com.google.zxing/3.5.3/core-3.5.3.jar" ),
			expandPath( "./vendor/com.google.zxing/3.5.3/javase-3.5.3.jar" )
		];

		return createObject( "java", classname, jarPaths );

	}

</cfscript>

Once I had this utility function, I was able to start playing around with the ZXing library. This library can do a lot. But, for the scope of these experiments, I'm only dealing with two classes: a "barcode writer", which take my input value and encodes it into a data structure that represents the barcode; and, an "image writer", which takes said data structure and serializes it into an image.

Rendering Barcodes With CSS Flexbox

But, we don't even need the image writer right away - we can do a good deal with just the barcode writer. Once of the encode() method signatures returns an array of Boolean values that define the on/off sequence of the barcode pixels. We can take those pixels and render them right to the browser using something like CSS Flexbox:

<cfscript>

	include "./utils.cfm";

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// When encoding a string as the sole argument, the writer returns an array of Boolean
	// flags in which True indicates a filled segment and False indicates a blank segment.
	// These segments make up the vertical lines of a rendered barcode.
	pixels = fromJars( "com.google.zxing.oned.Code39Writer" )
		.encode( "https://www.bennadel.com/" )
	;

</cfscript>
<cfoutput>

	<h1>
		Render 1D Barcode Using ColdFusion, Zxing, And CSS Flexbox
	</h1>

	<style type="text/css">
		.barcode {
			background-color: white ;
			display: flex ;
			height: 100px ;
			/**
			* We don't want to allow the Flexbox container to shrink smaller than the
			* number of segments or we run the risk of losing data fidelity. As such,
			* we're setting the min-width the number of pixels.
			*/
			min-width: #arrayLen( pixels )#px ;
			max-width: 1000px ;

			& > * {
				flex: 1 1 auto ;

				&[data-on="true"] {
					background-color: deeppink ;
				}
			}
		}
	</style>

	<div class="barcode">
		<cfloop array="#pixels#" item="pixel">
			<span data-on="#pixel#"></span>
		</cfloop>
	</div>

</cfoutput>

In this example, my rendered barcode is a Flexbox container in which each on/off pixel is represented by a flex-child, one per barcode pixel. And when we run this ColdFusion code, we get the following output:

Barcode rendered with ZXing, CSS Flexbox, and ColdFusion.

One thing that I learned from the CSS Flexbox approach is that the barcode still works even when it streeeeeeetches. I have the min-width set to the number of pixels so that we don't lose data fidelity as the browser shrinks; but, I allow the barcode to grow up to 1,000 pixels wide. And, if I do that, take a screenshot of it, and then upload it to a barcode reader, it still reads it properly.

Rendering Barcodes With SVG

After getting the CSS Flexbox approach to work, I wanted to try encoding the pixels as SVG rectangles. This is, essentially, the same exact approach using a different type of HTML container:

<cfscript>

	include "./utils.cfm";

	pixels = fromJars( "com.google.zxing.oned.Code39Writer" )
		.encode( "https://www.bennadel.com/" )
	;

</cfscript>
<cfoutput>

	<h1>
		Render 1D Barcode Using ColdFusion, Zxing, And SVG
	</h1>

	<style type="text/css">
		.barcode {
			color: cornflowerblue ;
		}
	</style>

	<!--- Note: the Viewbox width is +1 pixels to give the last Rect room to render. --->
	<svg viewBox="0 0 #( arrayLen( pixels ) + 1 )# 100" class="barcode">
		<cfloop array="#pixels#" item="pixel" index="i">
			<cfif pixel>
				<rect x="#i#" y="0" width="1" height="100" fill="currentColor" />
			</cfif>
		</cfloop>
	</svg>

</cfoutput>

Notice that the viewBox encodes the dimensions of the barcode. This allows my rect element to be relative to the viewBox without caring about how big the actual <svg> element is. Which, in turn, allows this element to scale up and down based on its parent container.

If we run this ColdFusion code, we get the following output:

Barcode rendered with ZXing, SVG, and ColdFusion.

This barcode only needs about 650 pixels to render. But, on my monitor, it will render at about 2,590 pixels wide. And, as I mentioned in the CSS Flexbox approach, even as the barcode stretches, it retains its fidelity.

Rendering Barcodes With SVG Data URI

Rendering the 1D barcode as an SVG element is nice; but, it's still not an "image". Meaning, I can't right-click and save the <svg> element as a file. But, if we can generate SVG markup in ColdFusion, then we should be able to serialize that markup into a Data URI and render it as an actual <img> element.

<cfscript>

	include "./utils.cfm";

	pixels = fromJars( "com.google.zxing.oned.Code39Writer" )
		.encode( "https://www.bennadel.com/" )
	;

</cfscript>
<cfoutput>

	<h1>
		Render 1D Barcode Using ColdFusion, Zxing, And SVG Data URI
	</h1>

	<!--- Store SVG to an output buffer. --->
	<cfsavecontent variable="data">
		<!--- NOTE: xmlns attribute is required for data URI to work. --->
		<svg viewBox="0 0 #( arrayLen( pixels ) + 1 )# 50" xmlns="http://www.w3.org/2000/svg">
			<defs>
				<rect id="true" y="0" width="1" height="50" fill="black" />
				<rect id="false" y="0" width="1" height="50" fill="white" />
			</defs>
			<cfloop array="#pixels#" item="flag" index="i">
				<use href="###flag#" x="#i#" />
			</cfloop>
		</svg>
	</cfsavecontent>

	<!--- Remove all extra space. --->
	<cfset trimmedData = data
		.reReplace( "\s+", " ", "all" )
		.reReplace( "\s+<", "<", "all" )
		.trim()
	/>
	<!--- Encode for the URL (spaces must be encoded as %20). --->
	<cfset encodedImage = encodeForUrl( trimmedData )
		.replace( "+", "%20", "all" )
	/>

	<img src="data:image/svg+xml,#encodedImage#" height="50"  />

</cfoutput>

This time, instead of rendering the <svg> element directly to the browser, I'm rendering it to to a CFSaveContent output buffer. Then, I'm stripping out the rendered white space, encoding it as a URL, and putting in the src attribute of an image tag. And, when we run this ColdFusion code, we get the following output:

Barcode rendered with ZXing, SVG Data URI, and ColdFusion.

In the above example, you can see that after I call encodeForUrl(), I have to manually convert the + character to %20 in order to get the data URI to work. To be honest, I'm not sure why that's the case. But, we should be able to work around this issue by Base64-encoding the SVG string:

<cfscript>

	include "./utils.cfm";

	pixels = fromJars( "com.google.zxing.oned.Code39Writer" )
		.encode( "https://www.bennadel.com/" )
	;

</cfscript>
<cfoutput>

	<h1>
		Render 1D Barcode Using ColdFusion, Zxing, And SVG Data URI
	</h1>

	<!--- Store SVG to an output buffer. --->
	<cfsavecontent variable="data">
		<!--- NOTE: xmlns attribute is required for data URI to work. --->
		<svg viewBox="0 0 #( arrayLen( pixels ) + 1 )# 50" xmlns="http://www.w3.org/2000/svg">
			<defs>
				<rect id="true" y="0" width="1" height="50" fill="black" />
				<rect id="false" y="0" width="1" height="50" fill="white" />
			</defs>
			<cfloop array="#pixels#" item="flag" index="i">
				<use href="###flag#" x="#i#" />
			</cfloop>
		</svg>
	</cfsavecontent>

	<!--- Remove all extra space. --->
	<cfset trimmedData = data
		.reReplace( "\s+", " ", "all" )
		.reReplace( "\s+<", "<", "all" )
		.trim()
	/>

	<!--- Encode for the URL as Base64. --->
	<cfset encodedImage = binaryEncode( charsetDecode( trimmedData, "utf-8" ), "base64" ) />

	<img src="data:image/svg+xml;base64,#encodedImage#" height="50"  />

</cfoutput>

This does the exact same thing; only, instead of embedded the SVG data directly into the src attribute, we're encoding it as Base64 first. And, by doing so, we don't have to worry about which characters are or are not valid.

Rendering Barcodes With PNG Data URI

Rendering barcodes as an SVG data URI is relatively simple! But, unfortunately, it doesn't seem to work in the PDF extension in Lucee CFML. But, you know what does work in a PDF? A PNG data URI!

Rendering a PNG is more complicated because it's a more complicated data format. But, the ZXing library has us covered. Instead of having the "barcode writer" return an array of pixels, we need to have it return a "bit matrix" that encodes those pixels into a 2D format. Then, we need to pull in the "image writer" to encode that bit matrix into a PNG value.

In this demo, I'm having it write the PNG value to a ByteArrayOutputStream so that I can capture the binary value and encode it into Base64, the same way we were doing with the SVG approach above. Then, I can consume this value directly in the <img[src]> attribute.

<cfscript>

	include "./utils.cfm";

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// Instead of getting an array of on/off pixels, this time we're going to render the
	// barcode to a pixel matrix (this is essentially the same thing, but uses a width and
	// height to represent the pixels in 2D space).
	bitMatrix = fromJars( "com.google.zxing.oned.Code39Writer" ).encode(
		"https://www.bennadel.com/",
		fromJars( "com.google.zxing.BarcodeFormat" ).valueOf( "CODE_39" ),
		0,  // width - setting to 0 to generate minimum size.
		100 // height.
	);
	// The width / height that we passed-in above are the "preferred" dimensions (with "0"
	// indicating a desire for the minimum possible values). If the bit matrix needs to
	// use more than the preferred space, it will. As such, the rendered dimensions may
	// not match what we passed-in. But, we can read the rendered dimensions.
	barcodeWidth = bitMatrix.getWidth();
	barcodeHight = bitMatrix.getHeight();

	// Write the bit matrix data to a PNG. But, instead of writing to a file, we're going
	// to write it to a binary buffer so that we can simply generate a data URI instead of
	// having to clean-up a temporary, physical file.
	byteStream = createObject( "java", "java.io.ByteArrayOutputStream" ).init();
	// Translate to the PNG image format.
	fromJars( "com.google.zxing.client.j2se.MatrixToImageWriter" )
		.writeToStream( bitMatrix, "png", byteStream )	
	;

	// Encode the PNG image data as base64 for our data URI.
	encodedImage = binaryEncode( byteStream.toByteArray(), "base64" );

</cfscript>
<cfoutput>

	<h1>
		Render 1D Barcode Using ColdFusion, Zxing, And PNG Data URI
	</h1>

	<img
		src="data:image/png;base64,#encodedImage#"
		width="#barcodeWidth#"
		height="#barcodeHight#"
	/>

</cfoutput>

When we run this ColdFusion code, we render a PNG-based image without creating an intermediary PNG file:

Barcode rendered with ZXing, PNG Data URI, and ColdFusion.

This inline PNG approach works in Lucee CFML PDF extension, but, I haven't tested in Adobe ColdFusion version. I'm assuming it works there as well since data URIs are a relatively old concept.

Rendering Barcodes With PNG Image APIs

The inline PNG image approach is nice because it's wholly self-contained. But, for the sake of exploration, let's create an image API that takes a url.value and encodes it into a PNG image that gets returned in the HTTP response. This way, our calling page doesn't have to go through the machinations of encoding the image data.

In this example, we'll use two different approaches: one that renders the PNG image from a byte-array, just as we did above; and, one that renders the PNG image to an intermediary, temporary file.

First, our calling page:

<cfoutput>

	<h1>
		Render 1D Barcode Using ColdFusion, Zxing, And PNG
	</h1>

	<p> Rendered to response stream: </p>
	<img src="png-file-stream.cfm?value=#encodeForUrl( 'https://www.bennadel.com/' )#" />
	
	<p> Rendered to temp file: </p>
	<img src="png-file.cfm?value=#encodeForUrl( 'https://www.bennadel.com/' )#" />

</cfoutput>

Both of the <img> tags are using a CFML API as the src. The first ColdFUsion API will render the image by streaming the byte-array data back in the response:

<cfscript>

	include "./utils.cfm";

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// The value to encode into a 1D (Code 39) barcode.
	param name="url.value" type="string";
	// The preferred dimensions.
	param name="url.width" type="numeric" default=0;
	param name="url.height" type="numeric" default=50;

	bitMatrix = fromJars( "com.google.zxing.oned.Code39Writer" ).encode(
		url.value,
		fromJars( "com.google.zxing.BarcodeFormat" ).valueOf( "CODE_39" ),
		val( url.width ),
		val( url.height )
	);

	byteStream = createObject( "java", "java.io.ByteArrayOutputStream" ).init();
	fromJars( "com.google.zxing.client.j2se.MatrixToImageWriter" )
		.writeToStream( bitMatrix, "png", byteStream )	
	;

	header
		name = "X-Barcode-Width"
		value = bitMatrix.getWidth()
	;
	header
		name = "X-Barcode-Height"
		value = bitMatrix.getHeight()
	;
	content
		type = "image/png"
		variable = byteStream.toByteArray()
	;

</cfscript>

As you can see, this approach mirrors the previous one. Only instead of encoding the binary data into a data URI, it's streaming it back using the CFContet tag.

In the second CFML API, we're going to have the "image writer" write to a temporary PNG file:

<cfscript>

	include "./utils.cfm";

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// The value to encode into a 1D (Code 39) barcode.
	param name="url.value" type="string";

	// Instead of getting an array of on/off pixels, this time we're going to render the
	// barcode to a pixel matrix (this is essentially the same thing, but uses a width and
	// height to represent the pixels in 2D space).
	bitMatrix = fromJars( "com.google.zxing.oned.Code39Writer" ).encode(
		url.value,
		fromJars( "com.google.zxing.BarcodeFormat" ).valueOf( "CODE_39" ),
		0,  // width - setting to 0 to generate minimum size.
		100 // height.
	);

	// Write the bit matrix to a temporary PNG file on disk.
	file = createObject( "java", "java.io.File" )
		.init( getTempFile( prefix = "barcode", extension = "png" ) )
	;
	fromJars( "com.google.zxing.client.j2se.MatrixToImageWriter" )
		.writeToPath( bitMatrix, "png", file.toPath() )	
	;

	// Serve up (and then delete) the temporary PNG file.
	content
		type = "image/png"
		file = file.getAbsolutePath()
		deleteFile = true
	;

</cfscript>

This time instead of calling .writeToStream() on the image writer, we're using .writeToPath() and having it render the barcode to a PNG file in our temp directory. Then, I'm having the CFContent tag server and delete the temporary file.

Now, if we run the calling page, we get the following ColdFusion output:

Barcode rendered with ZXing, PNG, and ColdFusion.

As you can see, both remote ColdFusion calls rendered the same PNG file (at different height values).

When I found out that I had to start rendering barcodes in ColdFusion, I thought it was going to be a relatively "big ask". But, the fact that ColdFusion is built on top of Java continues to make my life hella easy! Thank goodness the ZXing barcode library exists.

Want to use code from this post? Check out the license.

Reader Comments

16,029 Comments

@Ray,

I love how relatively easy this Zxing library is to use! Barcodes have always seemed like some very mysterious thing to me (especially QR codes). But, it's all just data to "bitmatrix" and then bitmatrix to "image" calls.

I mean, don't get me wrong, I have no idea what this library is doing under the hood. I tried to look, but it seems to be all math and bit-shifting. Over my head. I just love that they've created such a nice abstraction.

I just started playing around with QR codes. Was surprising to find that the whole "logo in the middle" thing isn't part of the QR code specification - it's just taking advantage of the fact that the QR code can do error correction. So, as long as the logo doesn't cover too much of the data, it "just works". Bonkers! 😮

16,029 Comments

Also, for anyone that comes across this code, I realize now that I didn't need to do this full hoop-jumping in this line of code:

fromJars( "com.google.zxing.BarcodeFormat" )
	.valueOf( "CODE_39" )

I used the .valueOf() call because the JavaDocs seem to imply that this is the way to do it. And, this is the way a lot of the public demos do it. But, at least with ColdFusion, this kind of reflection is unnecessary. I could have just done:

fromJars( "com.google.zxing.BarcodeFormat" )
	.CODE_39

Easier to read this way.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel