FORGEBOX Enterprise 🚀 - Take your ColdFusion (CFML) Development to Modern Times! Learn More...

wkhtmltopdf

v1.1.0 Modules

wkhtmltopdf (A CFML/Coldbox Wrapper)

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.

Inspiration

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.

Requirements:

  • Supported Engines: Lucee 5+, Adobe Coldfusion 11+
  • a wkhtmltopdf service accessible to your CF engine
  • Coldbox. It would not take much doing to re-write the module without Wirebox or Hyper ... but just use Coldbox.

Usage

Instantiate the PDF Service:

property name="PDFService" inject="PDFService@wkhtmltopdf";

or

PDFService = wirebox.getInstance( "PDFService@wkhtmltopdf" );

Converting a single content string

For simple requests where you have the source HTML available in one variable or accessible via a single URL, use the toPDF function:

toPDF

NameTypeRequiredDefaultDescription
contentstringtruenullHTML to be converted to PDF, or a valid URL
outputstringfalsepdfDesired output type (pdf, png, or jpg).
optionsstructfalsenullAn 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>'
);

Converting multiple sources: The PDF Request Object

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

NameTypeRequiredDefaultDescription
contentstringtruenullHTML to be converted to PDF, or a valid URL
isURLbooelanfalsenullIndicate whether content is a string to be converted or a URL. Uses isValid( "url") if not provided.
optionsstructfalsenullAn optional struct of wkhtmltopdf parameters for the request.
cookiesstructfalsenullAn optional struct of cookies to pass to wkhtmltopdf.

toPDFMultiple

NameTypeRequiredDefaultDescription
requestsarraytruenullArray of PDFRequest objects (from toPDFRequest())
outputstringfalsepdfDesired 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 ] );

The options struct

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.

Rendering the Result

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.

Error Handling

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.

But I don't have Coldbox!

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();
}

wkhtmltopdf (A CFML/Coldbox Wrapper) Version History

v1.1.0 (June 27, 2022)

  • Fixed ACF support by detecting the server engine and adding a .toByteArray() to the HTTP response
  • Fixed URL/content detection across engines

v1.0.1 (June 22, 2022)

v1.0.0 (January 4, 2020)

  • Initial release

$ box install wkhtmltopdf

No collaborators yet.
     
  • {{ getFullDate("2020-01-04T11:57:00Z") }}
  • {{ getFullDate("2022-06-27T19:08:52Z") }}
  • 4,859
  • 10,381