BoxLang 🚀 A New JVM Dynamic Language Learn More...
After years of wrestling with cfdocument from Adobe and Lucee, we centralized our "convert this to PDF" functions using a wkhtmltopdf Docker container based on this repository:
https://github.com/MotorsportReg/docker-wkhtmltopdf-service
All this module does is provide a friendly interface to the API for that service. This module does not contain the wkhtmltopdf utility or do any conversion by itself. You must be running wkhtmltopdf as a service for this module to do any good.
While Coldbox's renderData()
convention makes it very
easy to convert anything to PDF (or JSON, XML, or HTML), you're at the
mercy of the application engine's PDF library -- and you have to
include the PDF extension in your deployment (for Lucee) or the Adobe
PDF Service (for ACF).
The wkhtmltopdf module was inspired by Ryan Guill's recommendation of the above docker-wkhtmltopdf-service on the CFML Slack (and some sample code, included at the end of this README)
A simple request to wkhtmltopdf, whether you're executing it locally or using an API to a Docker container, doesn't need even a small module -- cfhttp will do the job. But if you're making these requests all over your application, it's nice to centralize common settings and the relevant code. For more complicated PDFs with multiple sources (or sources combined from URLs and strings), we enjoy even more benefit from re-using code. We also wanted to memorialize Ryan's recommendation for a wonderful and simple Docker "edition" of wkhtmltopdf and centralize our settings for the hostname, port, and TLS status of the wkhtmltopdf service.
Instantiate the PDF Service:
property name="PDFService" inject="PDFService@wkhtmltopdf";
or
PDFService = wirebox.getInstance(
"PDFService@wkhtmltopdf" );
For simple requests where you have the source HTML available in one
variable or accessible via a single URL, use the toPDF
function:
toPDF
Name | Type | Required | Default | Description |
---|---|---|---|---|
content | string | true | null | HTML to be converted to PDF, or a valid URL |
output | string | false | Desired output type (pdf, png, or jpg). | |
options | struct | false | null | An optional struct of wkhtmltopdf parameters for the request. |
Example:
var myPDF = PDFService.toPDF(
content = '<html><head><title>A Sample PDF</title></head><body><p>Some Sample Content</p></body></html>'
);
If you want to build a PDF from multiple source strings, multiple
URLs, or a combination of both, you can build an array of
PDFRequest
objects. Each PDFRequest is very similar to a
toPDF()
call; it contains the data relevant to a single
source (whether a string or a URL) and whatever options and/or cookies
apply to that request. Keep in mind that each request object needs its
own set of options, even if you are re-using the same options across
all requests!
First, create one or more PDFRequest objects using the convenience
method toPDFRequest()
. Then, send the array of
PDFRequests to toPDFMultiple()
.
toPDFRequest
Name | Type | Required | Default | Description |
---|---|---|---|---|
content | string | true | null | HTML to be converted to PDF, or a valid URL |
isURL | booelan | false | null | Indicate
whether content is a string to be converted or a
URL. Uses isValid( "url") if not provided. |
options | struct | false | null | An optional struct of wkhtmltopdf parameters for the request. |
cookies | struct | false | null | An optional struct of cookies to pass to wkhtmltopdf. |
toPDFMultiple
Name | Type | Required | Default | Description |
---|---|---|---|---|
requests | array | true | null | Array
of PDFRequest objects (from toPDFRequest() ) |
output | string | false | Desired output type (pdf, png, or jpg). |
Example:
var pdfRequest = PDFService.toPDFRequest(
content = 'https://www.google.com',
options = {
'grayscale' = true
}
);
var pdfRequest2 = PDFService.toPDFRequest(
content = '<html><head><title>A Document</title></head><body><p>Some text</p></body></html>'
);
var pdfResult = PDFService.toPDFMultiple( requests = [ pdfRequest, pdfRequest2 ] );
Every request to wkhtmltopdf supports an options struct whose values are passed to the wkhtmltopdf binary. Future versions of this module may list and validate these options directly, but for now, CF will pass whatever options you specify to wkhtmltopdf without validating whether the options exist or the value specified is supported. Available options are listed in the wkhtmltopdf documentation but note that image return types do not support all options.
PDFService returns a binary object. We can still take advantage of
renderData()
but we just won't ask it to do the
conversion. Since PDFService gives us a binary object, here's how we
would display the myPDF object above in a handler event:
return event.renderData(
data = myPDF,
isBinary = true,
contentType = "application/pdf"
);
Alternatively, you could call cfcontent
directly with a
MIME-type of application/pdf
. renderData()
does this for us and also adds appropriate headers for content-length.
If PDFService gets anything other than a 200 OK
from
wkhtmltopdf, either because it can't connect or the wkhtmltopdf
service returns an error code, it will throw an exception along with
the HTTP Status code from the attempted connection to wkhtmltopdf.
This module relies on Coldbox and Wirebox to simplify some of the more complex use cases, like asking for multiple source documents, or mixed URL and content requests. If you aren't using Coldbox in your app, you can still take advantage of wkhtmltopdf -- just not with this module. Start with Ryan's sample code (converted to cfscript)
public binary function printPDF(
required string html,
struct options = {}
) {
var args = {
"output" : "pdf",
"request" [
{
"content" : arguments.html,
"options" : arguments.options
}
]
};
cfhttp(
url = "URLtoMywkhtmltopdfService",
method = "POST",
result = "local.httpResult",
throwonerror = true
) {
cfhttpparam( type="body" value = serializeJSON( args ) );
}
return httpResult.fileContent.toByteArray();
}
$
box install wkhtmltopdf