DEV Community

Cover image for ColdFusion getQRSVG() UDF - Returns responsive SVG source
James Moberg
James Moberg

Posted on

ColdFusion getQRSVG() UDF - Returns responsive SVG source

In a project I'm working on, I needed to generate a QR code and display it within an HTML file that is converted to a PDF using WKHTMLTOPDF. I didn't want to rely upon physical, statically-hosted SVG image files and wished to embed it. With WKTHMLTOPDF, I could use javascript to generate the QR code, but that requires adding a time delay and I didn't want to negatively impact processing time (even though WKHTMLTOPDF is faster than CFDocument.)

I already have a ColdFusion wrapper for Zint Barcode Generator function, so I just needed to write a CFML wrapper to:

  • use generateZint() to create a temporary SVG file
  • read the temp file data
  • remove XML & HTML doctype headers
  • modify the SVG XML source to "make it responsive"
  • delete the temp file

Here it is. I hope that this helps some CFML developers.

UPDATE (8 Hours later)

I've updated the UDF so that it isn't dependent on the generateZint() UDF and so it outputs the SVG text directly to the console... thus skipping writing, reading & deleting. The process is now only:

  • use zint.exe to capture SVG output
  • remove XML & HTML doctype headers

I didn't notice much difference in terms of performance, but I consider it a win anytime I can reduce the amount of I/O calls to the file system.

Source Code

https://gist.github.com/JamoCA/fbbd2599102216448ada8e9f85d40b9c

<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>
view raw getQRSVG.cfm hosted with ❤ by GitHub
<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>
view raw zint.cfm hosted with ❤ by GitHub

Top comments (0)