Skip to content

Instantly share code, notes, and snippets.

@JamoCA
Last active April 10, 2025 00:13
Show Gist options
  • Save JamoCA/b957c34cddea38f4bd2d777b41e348ac to your computer and use it in GitHub Desktop.
Save JamoCA/b957c34cddea38f4bd2d777b41e348ac to your computer and use it in GitHub Desktop.
Comparing ColdFusion CFDocument to WKHTMLTOPDF with GhostScript post-optimization
<!---
Comparing ColdFusion CFDocument to WKHTMLTOPDF with GhostScript post-optimization
2025-04-09
Requires WKHTMLTOPDF (LGPLv3; portable) https://wkhtmltopdf.org/
Requires GhostScript (GNU GPL Affero license; portable) https://www.ghostscript.com/
GIST: https://gist.github.com/JamoCA/b957c34cddea38f4bd2d777b41e348ac
Blog: https://dev.to/gamesover/pdf-generation-bloat-and-optimization-2118
Tweet: https://x.com/gamesover/status/1910123216972165226
LinkedIn: https://www.linkedin.com/posts/jamesmoberg_cfml-activity-7315889428340609024-5rEy
Test image from https://www.nasa.gov/image-detail/expedition-73-launch-3/
--->
<cfscript>
protocol = (CGI.SERVER_PORT_SECURE) ? "https" : "http";
results = [:];
config = [
"testDate": tostring(datetimeformat(now(), "yyyymmddHHnnss")),
"fileRootDir": getdirectoryfrompath(getcurrenttemplatepath()) & "_files\PDFSize",
"webRootDir": protocol & "://" & CGI.SERVER_NAME & CGI.SCRIPT_NAME.replaceAll("/[^/]*$", "/_files/PDFSize"),
"imageUrl": "https://www.nasa.gov/wp-content/uploads/2025/04/54438297406-927b72555e-o.jpg",
"wkhtmltopdfPath": "c:\wkhtmltopdf\wkhtmltopdf.exe",
"ghostscriptPath": "c:\gs\gswin64.exe",
"ghostscriptConfig": "-sDEVICE=pdfwrite -dDetectDuplicateImages=true -dPDFSETTINGS=/ebook -dCompatibilityLevel=1.6 -dPrinted=false -dNOPAUSE -dQUIET -dBATCH",
"useTestImage": 0,
"deleteFiles": 0,
"tests": ["cfdocument", "wkhtmltopdf", "cfhtmltopdf"]
];
if (!directoryexists(config.fileRootDir)){
directorycreate(config.fileRootDir);
}
</cfscript>
<!--- download remove test image and use locally. --->
<cfif isvalid("boolean", config.useTestImage) && config.useTestImage>
<cfhttp url="#config.imageUrl#" method="get" getasbinary="yes" result="httpResult" throwonerror="yes" useragent="#CGI.HTTP_USER_AGENT#"></cfhttp>
<cfif val(httpResult.statusCode) eq 200>
<cffile action="write" file="#config.fileRootDir#\image_#config.testDate#.#lcase(listlast(config.imageUrl,"."))#" output="#httpResult.fileContent#" mode="644">
<cfelse>
<cfset config.useTestImage = 0>
<cfoutput>Error downloading image: #httpResult.statusCode#</cfoutput>
</cfif>
</cfif>
<cfsavecontent variable="config.html"><!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8">
</head>
<body>
<h1>Hello World</h1>
<cfif isvalid("boolean", config.useTestImage) && config.useTestImage>
<cfoutput>
<img src="#config.webRootDir#/image_#config.testDate#.#lcase(listlast(config.imageUrl,"."))#" width="400" height="248" border="1">
</cfoutput>
</cfif>
</body>
</html>
</cfsavecontent>
<!--- <cf_dump var="#CGI#">
<cfabort> --->
<cfset filewrite("#config.fileRootDir#\html_#config.testDate#.htm", config.html, "utf-8")>
<!--- WKHTMLTOPDF --->
<cfset test = "WKHTMLTOPDF">
<cfif arrayfind(config.tests, lcase(test))>
<cfset timeStart = gettickcount()>
<cfexecute name="#config.wkhtmltopdfPath#" arguments="""#config.webRootDir#/html_#config.testDate#.htm"" #config.fileRootDir#\#test#_#config.testDate#.pdf" timeout="60">
<cfset results["#test#_#config.testDate#.pdf"] = ["generation": gettickcount() - timeStart]>
</cfif>
<!--- CFDocument --->
<cfset test = "CFDocument">
<cfif arrayfind(config.tests, lcase(test))>
<cfset timeStart = gettickcount()>
<cfdocument format="pdf" src="#config.webRootDir#/html_#config.testDate#.htm" filename="#config.fileRootDir#\#test#_#config.testDate#.pdf" overwrite="yes"></cfdocument>
<cfset results["#test#_#config.testDate#.pdf"] = ["generation": gettickcount() - timeStart]>
</cfif>
<!--- CFHTMLTOPDF --->
<cfset test = "CFHTMLTOPDF">
<cfif arrayfind(config.tests, lcase(test))>
<cfset timeStart = gettickcount()>
<cfhtmltopdf source="#config.webRootDir#/html_#config.testDate#.htm" destination="#config.fileRootDir#\#test#_#config.testDate#.pdf" overwrite="yes"></cfhtmltopdf>
<cfset results["#test#_#config.testDate#.pdf"] = ["generation": gettickcount() - timeStart]>
</cfif>
<cfset sleep(1000)>
<!--- Ghostscript WKHTMLTOPDF --->
<cfset test = "WKHTMLTOPDF">
<cfif arrayfind(config.tests, lcase(test))>
<cfset timeStart = gettickcount()>
<cfexecute name="#config.ghostscriptPath#" arguments="#config.ghostscriptConfig# -sOUTPUTFILE=""#config.fileRootDir#\#test#_GS_#config.testDate#.pdf"" ""#config.fileRootDir#\#test#_#config.testDate#.pdf""" timeout="60">
<cfset results["#test#_GS_#config.testDate#.pdf"] = ["generation": gettickcount() - timeStart]>
</cfif>
<!--- Ghostscript CFDocument --->
<cfset test = "CFDocument">
<cfif arrayfind(config.tests, lcase(test))>
<cfset timeStart = gettickcount()>
<cfexecute name="#config.ghostscriptPath#" arguments="#config.ghostscriptConfig# -sOUTPUTFILE=""#config.fileRootDir#\#test#_GS_#config.testDate#.pdf"" ""#config.fileRootDir#\#test#_#config.testDate#.pdf""" timeout="60">
<cfset results["#test#_GS_#config.testDate#.pdf"] = ["generation": gettickcount() - timeStart]>
</cfif>
<!--- Ghostscript CFHTMLTOPDF --->
<cfset test = "CFHTMLTOPDF">
<cfif arrayfind(config.tests, lcase(test))>
<cfset timeStart = gettickcount()>
<cfexecute name="#config.ghostscriptPath#" arguments="#config.ghostscriptConfig# -sOUTPUTFILE=""#config.fileRootDir#\#test#_GS_#config.testDate#.pdf"" ""#config.fileRootDir#\#test#_#config.testDate#.pdf""" timeout="60">
<cfset results["#test#_GS_#config.testDate#.pdf"] = ["generation": gettickcount() - timeStart]>
</cfif>
<cfset sleep(2000)>
<cfdirectory action="list" directory="#config.filerootDir#" name="fileResults" filter="*#config.testDate#*" sort="name" type="file">
<cfloop query="fileResults">
<cfif structkeyexists(results, name)>
<cfset results[name]["filesize"] = size>
</cfif>
</cfloop>
<cfdump var="#results#" label="Generation Duration (ms)">
<cfdump var="#config#" label="Test Configuration">
<cfif isvalid("boolean", config.deleteFiles) && config.deleteFiles>
<cfloop query="fileResults">
<cftry>
<cfset filedelete(directory & "\" & name)>
<cfcatch><cfoutput><div>Unable to delete file #name#</div></cfoutput></cfcatch>
</cftry>
</cfloop>
<cfelse>
<cfdump var="#fileResults#" label="Temp files">
</cfif>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment