One Year With Wkhtmltopdf: One Thousand Problems, One Thousand Solutions

Have you ever tried to generate a pdf in your application? This year, I have. Twice.

I’m sure there are several means to this end. For example, one of my colleagues used phantomjs and then wrote an article¬†about his experience. But if I’m here now, it’s to tell you my story with wkhtmltopdf.

It all started in February when my client requested a feature that would allow him to print any page of the application we were building. When we started, we didn’t know how to do it and we tried different tools. Some of these tools had issues with our NVD3 charts, others were incompatible with our security requirements.

Then we discovered wkhtmltopdf!

wkhtmltopdf1

The proof of concept

The way to use wkhtmltopdf is really simple:

  • Take the html and give it to wkhtmltopdf
  • Take the pdf and give it to the user
  • Take a beer and give me one

But first of all you should know two or three things:

  • Wkhtmltopdf has dependencies. On Linux, I had to install zlib, fontconfig, freetype, and X11 libs
  • Wkhtmltopdf’s current stable release is 0.12.4 and based on Qt 4.8.5 version. This is an old Qt version (used by Google Chrome 18/19/20) that does not support flexbox¬†for example
  • An alpha release based on Qt 5.4.2 (with an updated browser engine) is available

Now let’s dive into a real-world example! The front-end is based on an AngularJS 1 app.

Take the html and give it to wkhtmltopdf

So first we need a button.

// template.html

<a ng-click="print()">Print</a>

Then we send the html to the backend to generate our pdf.

// controller.js

$scope.print = function() { 
  var html = document.getElementsByTagName('html')[0];
  var body = {html: html.outerHTML};
  $http.post('api/pdf/print', body, {responseType: 'arraybuffer'});
}

The best way to use wkhtmltopdf in your backend is to install a wrapper. There are several wrappers, I used the first one I found.

// package.json

{ 
  "dependencies": { 
    "wkhtmltopdf": "0.1.5"
  }
}

Don’t forget to install the binary wkhtmltopdf in your project!

// pdf.js

var wkhtmltopdf = require('wkhtmltopdf');
module.exports = function(PDF) {
  PDF.print = function(req, res, done) {
    var html = req.body.html;
    var projectLocation = __dirname + '/../../';
    var CSSLocation = projectLocation + 'client/www/style/' + 'main.css';
    var wkBinPath = projectLocation + 'bin/wkhtmltopdf';
    wkhtmltopdf.command = wkBinPath;
    var options = { 'user-style-sheet': CSSLocation, };
    var stream = wkhtmltopdf(html, options);
    done;
  }
}

Of course, the projectLocation, CSSLocation and wkBinPath depend on the architecture of your project.

Take the pdf and give it to the user

Currently, you still have nothing. But you’ve done the hardest part!

Indeed wkhtmltopdf generated a stream for you. Now you have to send the stream to your frontend.

// pdf.js

    var stream = wkhtmltopdf(html, options);
    res.set('Content-Disposition', 'attachment; filename=transcript.pdf');
    res.set('Content-Type', "application/pdf");
    stream.pipe(res);
    done;

And make your user download it.

// controller.js

  $http.post('api/pdf/print', body, {responseType: 'arraybuffer'})
  .success(function(response) { 
    var file = new Blob([ response ], {type: 'application/pdf'});
    FileSaver.saveAs(file, 'print.pdf');
  }
)

You’re going to need to install another dependency, FileSaver:

// bower.json

{ 
  "dependencies": {
    "angular-file-saver": "1.1.0"
  }
}

Don’t forget to inject ‘ngFileSaver’ in your module!

Share a drink and look at the result

You might get some issues, like this “size issues”:

POC

And it’s certainly not what you wanted. But this is enough to say: “I can do it!”.

In the next article, we’re going to see how to improve your pdf.

wkhtmltopdf2


You liked this article? You'd probably be a good match for our ever-growing tech team at Theodo.

Join Us