Last active
April 10, 2025 00:13
-
-
Save JamoCA/b957c34cddea38f4bd2d777b41e348ac to your computer and use it in GitHub Desktop.
Comparing ColdFusion CFDocument to WKHTMLTOPDF with GhostScript post-optimization
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
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