Articles
ColdFusion Wrapper for Zint Barcode Generator
CFML source with 93 example barcode images in 3 formats
I referenced Zint Barcode Generator in a dev.to blog posts in 2020 & 2024. My initial UDF included a very basic command line syntax for a QR code. Zint is able to generate 50 93 different barcode formats and I haven't found a java library that is capable of supporting the same formats or outputting files as BMP, EPS, GIF, PCX, TIF, EMF, PNG or SVG. (NOTE: I prefer to save as SVG and then use the static file when generating PDFs using WKHTMLTOPDF.)
There is a java port of Zint called OkapiBarcode. It's actively maintained, but looks like it requires more effort when building barcodes, requires the use of constants and doesn't appear to have any documentation beyond a couple of basic examples.
A project that I'm currently working on needs QR codes for the ticketing service. Normally, I'd use QRCode.js as it works with my CF_WKHTMLTOPDF custom tag that I shared on Github. This works only if "JavascriptDelay" is configured for a second or two, but I'm not sure how reliable this would be when generating multiple pages that each contain a QR code. (NOTE: The built-in CFHTMLtoPDF tag doesn't support a javascript delay.)
In my quest to create a "more perfect DRY world" for myself, I decided to expand our existing logic that generates a command line string. Support for other features has been added and it triggers the executable and returns results. I've also made some tweaks to allow an entire configuration to be passed as a single "options" argument.
In addition, I've included base arguments for all generated PNG, GIF and SVG images and included the duration, filesize and sample images.
Source Code (This returns SVG source code; no file created)
<cfscript>
/*
* getQRSVG UDF
* @displayname getQRSVG
* @Dependency Requires Zint executable from https://zint.org.uk/
* @Dependency_Documentation https://zint.org.uk/manual/chapter/4
* @author James Moberg http://sunstarmedia.com, @sunstarmedia
* @version 1
* @lastUpdate 2025-03-13
* @gist https://gist.github.com/JamoCA/fbbd2599102216448ada8e9f85d40b9c
* @blog https://dev.to/gamesover/coldfusion-getqrsvg-returns-responsive-svg-source-37hf
* @twitter https://x.com/gamesover/status/1900234390921502840
* @LinkedIn https://www.linkedin.com/posts/jamesmoberg_coldfusion-getqrsvg-udf-returns-responsive-activity-7306001304919650305-CcRp
* @param String data The data to encode.
* @param String exePath The filepath to the Zint executable.
*/
string function getQRSVG(required string data, string exePath="") hint="I create a temp SVG file and return the SVG source code (requires zint.exe)" {
local.sanitizedData = tostring(arguments.data).replaceAll("""", "\""");
local.cmd = "-b 58 --scale=8 --direct --filetype=svg -d ""#local.sanitizedData#""";
try {
cfexecute(name="#arguments.exePath#", arguments="#local.cmd#", timeout="90", variable="local.svg");
} catch (any error){
throw(message="getQRSVG UDF: Zint.exe error");
}
if (!find("<?xml", local.svg) || !find("<!", local.svg)){
throw(message="getQRSVG UDF: Zint.exe generated invalid SVG QR Code.");
}
local.svg = replace(local.svg, "<desc>Zint Generated Symbol</desc>", "<desc>#encodeforxml(arguments.data)#</desc>");
// replace fixed width/height to make responsive
local.wResults = refindnocase("width\s*=\s*""(\d+)+", local.svg, 1, true);
local.w = val(local.wResults.match[2]);
local.hResults = refindnocase("height\s*=\s*""(\d+)+", local.svg, 1, true);
local.h = val(local.hResults.match[2]);
local.svg = local.svg.replaceAll('width="\d+" height="\d+"', 'viewbox="0 0 #val(local.w)# #val(local.h)#" style="width:100%; display:block;"');
// remove xml header & html doctype
local.svg = local.svg.replaceAll("\<\?xml.*?\>", "").replaceAll("\s*\<\!.*?\>", "");
return trim(local.svg);
}
</cfscript>
Source Code (This generates physical files)
<cfscript>
/**
* generateZint UDF
* @displayname generateZint
* @Dependency Requires Zint executable from https://zint.org.uk/
* @Dependency_Documentation https://zint.org.uk/manual/chapter/4
* @author James Moberg http://sunstarmedia.com, @sunstarmedia
* @version 1
* @lastUpdate 2024-10-30 16:00 Pacific
* @gist https://gist.github.com/JamoCA/fbbd2599102216448ada8e9f85d40b9c
* @blog https://dev.to/gamesover/coldfusion-wrapper-for-zint-barcode-generator-15mc
* @twitter https://x.com/gamesover/status/1851768390760960101
* @LinkedIn https://www.linkedin.com/posts/jamesmoberg_coldfusion-activity-7257534479445962752-aeoL
* @param String filepath The path and file name to use when storing the generated file. (These params can also be defined in "options".)
* @param String barcodeType The barcode type. Normally a integer, but also accepts "QR". (https://zint.org.uk/manual/chapter/4#selecting-barcode-type)
* @param String data The data to encode.
* @param String exePath The filepath to the Zint executable.
* @param struct options All of the configuration data. Some very basic defaults are used if nothing is passed.
*/
struct function generateZint(string filepath="", string barcodeType=58, string data="", string exePath="", struct options={}) {
local.timestart = gettickcount();
local.response = [
"success": false
,"duration": 0
,"filePath": ""
,"cmd": ""
,"output": ""
,"errors": []
];
local.options = duplicate(arguments.options);
// reset some primary values if found in options & validate
if (local.options.keyexists("exePath")){
arguments.exePath = local.options.exePath;
structdelete(local.options, "exePath");
}
if (!fileexists(arguments.exePath)){
arrayappend(local.response.errors, "exePath is required.");
}
if (local.options.keyexists("o")){
arguments.filePath = local.options.o;
structdelete(local.options, "o");
}else if (local.options.keyexists("filePath")){
arguments.filePath = local.options.filePath;
structdelete(local.options, "filePath");
}
if (!len(arguments.filepath)){
arrayappend(local.response.errors, "filePath is required.");
}
if (local.options.keyexists("b")){
arguments.barcodeType = local.options.b;
structdelete(local.options, "b");
} else if (local.options.keyexists("barcodeType")){
arguments.barcodeType = local.options.barcodeType;
structdelete(local.options, "barcodeType");
}
arguments.barcodeType = (arguments.barcodeType eq "qr") ? 58 : arguments.barcodeType;
if (!val(arguments.barcodeType)){
arrayappend(local.response.errors, "barcodeType is required.");
}
if (local.options.keyexists("d")){
arguments.data = local.options.d;
structdelete(local.options, "d");
} else if (local.options.keyexists("data")){
arguments.data = local.options.data;
structdelete(local.options, "data");
}
if (!issimplevalue(arguments.data) || !len(arguments.data)){
arrayappend(local.response.errors, "data is required.");
}
// populate options with some default setting (if empty)
if (!structcount(local.options)){
// if QR code, set default options for barcode type 58
if (arguments.barcodeType eq 58){
local.options = [
"scale": javacast("int", 12)
,"fg": javacast("string", "000000")
,"bg": "ffffff"
,"nobackground": true
,"whitesp": javacast("int", 2)
,"vwhitesp": javacast("int", 2)
];
// default scale
} else {
local.options = [
"scale": javacast("int", 5)
];
}
}
local.cmd = [
"-o ""#arguments.filePath#"""
,"-b #javacast("int", val(arguments.barcodeType))#"
];
if (structcount(local.options)){
for (local.p in local.options){
local.v = trim(local.options[local.p]);
if (refindnocase("^([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$", local.v)){
local.v = javacast("string", local.v); // hex
} else if (isvalid("integer", local.v)) {
local.v = javacast("int", local.v); // integers
} else if (refind(local.v, "[:space:]")){
local.v = """#local.v#"""; // values with spaces
}
if (local.p eq "nobackground"){
if (isvalid("boolean", local.v) && local.v){
arrayappend(local.cmd, "--nobackground");
}
} else if (len(local.p) gt 1){
local.s = len(trim(local.v)) ? "--#local.p#=#local.v#" : "--#local.p#";
arrayappend(local.cmd, local.s);
} else if (len(local.p) eq 1){
local.s = len(trim(local.v)) ? "-#local.p# #local.v#" : "-#local.p#";
arrayappend(local.cmd, local.s);
}
}
}
arrayappend(local.cmd, "-d ""#replace(arguments.data, """", "\""", "all")#""");
local.response["cmd"] = arraytolist(local.cmd, " ");
if (!arraylen(local.response.errors)){
cfexecute(name="#arguments.exePath#", arguments="#local.response.cmd#", timeout="90", variable="local.response.output");
local.response.success = fileexists(arguments.filePath);
local.response.filePath = fileexists(arguments.filePath) ? arguments.filePath : "";
}
local.response.duration = javacast("int", gettickcount() - local.timestart);
return local.response;
}
/* Example
testImageName = "this-is-a-test.svg";
options = [
"filePath": "D:\www\#testImageName#"
,"data": "This is a test"
,"exePath": "C:\zint\zint.exe"
];
zintData = request.generateZint(options=options);
writeoutput('<img src="/#testImageName#" style="width:250px; height:auto;">');
writedump(var=zintData, label="zintData");
*/
</cfscript>