A few months ago I had a client requirement for PDF generation, in this case to generate certificates that could be viewed online or printed. I spent some time looking into the best Drupal options available and picked up some advice along the way on how best to accomplish these aims. After mentioning my results to several people, it seemed that PDF generation was a common requirement and now I have the same need again on a personal project, so it seemed a good case study to walk you through what I found.
Why not just print?
If your requirements are simple, it may be easier to just to tell your website users to print and there’s nothing stopping them doing this. If we want a level of control over what is printed or we want to distribute files for printing, then we need to look into other options.
Web vs Print
PDF generation takes a slight change of mindset. As web developers, we have spent a lot of time convincing designers from a print background to stop producing pixel perfect designs that will be difficult to reproduce on the web. If you want to introduce PDF generation or any form of high designed print output, then we need to relearn some of our old skills we left behind. The nature of print means that it is precise and often needs pixel (or millimeter) perfect design.
What am I trying to accomplish?
I am currently working on a board game and I want to allow players to be able to create their own cards that can be shared on the website and printed for use in the game. We have a specific size and layout that these cards will always be and need to conform to, here’s the initial design that we will partially recreate:
PDF options in Drupal
There are two options in Drupal for creating PDFs. The Print module and Views PDF. Views PDF initially seemed the better option as it would allow us to leverage the power of views and the myriad options it offers. However, it has the PHP module as a dependency and as far as I know, is reliant on the
eval()
function. PDF generation has the potential to be a server intensive task and this method seemed inefficient to me, aside from my reluctance to ever have any kind of PHP evaluation module enabled in Drupal.
This caused me to settle on the print module, which is also better supported and offers many other options for output that may prove useful.
Next we need to decide on our PDF generation library, I am going to suggest you use wkhtmltopdf and explain why later, as I want to build something to compare first. Do this by visiting the wkhtmltopdf website and follow the instructions for your setup. Remember it will need to be installed on local and production sites. After installing you need to create an alias to the wkhtmltopdf executable into your Drupal libraries folder, i.e.:
1
| ln -s /usr/bin/wkhtmltopdf /var/www/sites/all/libraries/wkhtmltopdf |
If you are installing under Ubuntu, I ran into some issues with the official archive, I recommend installing manually from the links above.
Configuring print module
Lets start with general settings at
admin/config/user-interface/print/common
:- Logo Options, Footer Options: Turned off for my example.
- Keep the current theme CSS: Enable this for theme consistency and less work.
Let’s set how and where we want the links to display to reach our PDF / Print versions,
admin/config/user-interface/print/ui
.
I am setting mine to be displayed in a block so I can have more control over layout.
The overall configuration options for PDF output are at
admin/config/user-interface/print/pdf
I recommend these settings:
- Open PDF in: New browser window
- Paper size and orientation: Whatever is appropriate for you. For now I’m using A7 for my card, it’s not quite the same, but close enough for this demo.
- Caching: I am yet to determine quite how effective this is, but usual rules apply, keep off during development and on in production
- File name: I will use
[site:name] - [node:title]
, you can use tokens here.
Looking at wkhtmltopdf specifics,
As wkhtmltopdf is a more advanced tool, it’s a command line based configuration, about which you can find more details here.
admin/config/user-interface/print/pdf/wkhtmltopdf
. As wkhtmltopdf is a more advanced tool, it’s a command line based configuration, about which you can find more details here.
Here are some extras I added:
1
| --margin-bottom 0m --margin-left 0m --margin-top 0m --margin-right 0m --dpi 300 |
This ensures that we have no margins added by wkhtmltopdf and can rely on our CSS output. We are also rendering at 300dpi for print.
A quick note on images. There are options for setting image dpi rendering here, but of course if someone uploads a 72dpi image and it is upscaled to 300dpi, it will look poor. Getting this right is a combination of configuration and user training.
Creating the HTML
The print module works by recreating HTML markup as other output formats. I find it easier to create the markup and CSS that will result in this output first. In my case this is affecting the default output of my content type, but this could be kept separate through the use of view modes or the print module’s own print output. Below is my content type – I have added a couple of the extra fields that my content type needs, but not all of them:
If you want, the print module also supplies it’s own custom view mode so that we could duplicate the same display or use in combination with techniques mentioned above.
Creating Styles.
The print module comes with its own style sheet (
print.css
, found in the module folder) that you can use to create styles that only apply to the print module rendered versions of nodes. You will need to add a copy to you theme and add them to your theme’s .info
file in the usual way.
If you checked the option Keep the current theme CSS as mentioned above then the print module will use your main theme styles and then check the
print.css
file for any overrides that you only want in print module rendered output. This makes the most sense to me and feels like the tidiest option. If you don’t use this option then the print.css
file is the only style sheet that will affect your output. The rest of this example will assume it is enabled.
I am using a bootstrap sub theme which comes with its own markup that may be different from your theme. I have some custom fonts loaded through font-your-face and created a custom image style for the image, these field names also represent my fields added above. Image styles don’t let you define sizes in centimeters, to get the right size in pixels I used this tool.
We have a particular print size we are trying to accomplish. You can specify sizes in CSS in a variety of units, so in our case I am using centimeters.
Here is my CSS:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| //This is my main content area, will be different in other themes and in mine covers the regions that appear for logged in and anonymous users .node-type-event-card .main-container section.col-sm -9 , .node-type-event-card .main-container section.col-sm -12 { border : solid 1px #000000 ; width : 6.8 cm; height : 9.3 cm; padding : 0.681 cm; } //The card title .node-type-event-card .main-container h 1 .page-header { text-align : right ; margin : 0 ; font-size : 14px ; } //Sets card padding .node-type-event-card .main-container .content { padding : 20px ; } //Formats the field contents .node-type-event-card .main-container .content .field { color : black ; margin-top : 10px ; margin-bottom : 10px ; } .node-type-event-card .main-container .content .field-name-body { font-size : 12px ; text-align : center ; } .node-type-event-card .main-container .content .field-name-field-image img { width : 100% ; }
|
Not quite right.
This is because the print module is using our theme but a different tpl file,
print.tpl.php
. Copy it from the module and into your theme so we can make some changes. You can add --format
to specify the output format, i.e. print--pdf.tpl.php
.
The main reason we want to edit this is that the print module by default prints a bunch of links at the top of the page and some
hr
tags that we don’t want and can’t remove in the UI, so let’s tidy it up. Here is my final HTML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" < html xmlns = "http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language; ?>" version="XHTML+RDFa 1.0" dir="<? php print $language->dir; ?>"> < head > <? php print $head; ?> < base href='<?php print $url ?>' /> < title ><? php print $print_title; ?></ title > <? php print $scripts; ?> <? php if (isset($sendtoprinter)) print $sendtoprinter; ?> <? php print $robots_meta; ?> <? php if (theme_get_setting('toggle_favicon')): ?> < link rel = 'shortcut icon' href = '<?php print theme_get_setting(' favicon') ?>' type='image/x-icon' /> <? php endif; ?> <? php print $css; ?> </ head > < body > <? php if (!empty($message)): ?> < div class = "print-message" ><? php print $message; ?></ div >< p /> <? php endif; ?> <? php if ($print_logo): ?> < div class = "print-logo" ><? php print $print_logo; ?></ div > <? php endif; ?> <? php if (!isset($node->type)): ?> < h2 class = "print-title" ><? php print $print_title; ?></ h2 > <? php endif; ?> < div class = "print-content" ><? php print $content; ?></ div > < div class = "print-footer" ><? php print theme('print_footer'); ?></ div > <? php if ($sourceurl_enabled): ?> < div class = "print-source_url" > <? php print theme('print_sourceurl', array('url' => $source_url, 'node' => $node, 'cid' => $cid)); ?> </ div > <? php endif; ?> <? php print $footer_scripts; ?> </ body > </ html > |
And my CSS (
print.css
) is much the same as the previous CSS but reflects the page structure:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| // I have found that clearing the margins helps with the PDF generation body { margin : 0 ; padding : 0 ; } html { margin : 0 ; } .print-content .node { border : solid 1px #000000 ; width : 6.8 cm; height : 9.3 cm; padding : 0.681 cm; font-family : 'VT323' ; font-size : 20px ; } .print-content h 2 { text-align : right ; margin : 0 ; font-size : 14px ; } .print-content .content { padding : 20px ; } .print-content .content .field { color : black ; margin-top : 10px ; margin-bottom : 10px ; } .print-content .content .field-name-body { font-size : 12px ; text-align : center ; } .print-content .content .field-name-field-image img { width : 100% ; } |
Now, what’s wrong with the fonts? Unfortunately we can’t use custom fonts that are on a remote server (in this case Google Fonts). I’m not sure if this is an issue with the print module (looking at
print.tpl.php
, it loads styles in a different way) or if it’s a wkhtmltopdf issue. I found a few other potential paths you may be able to follow if you can’t download your font, but I will assume you can and show a foolproof method.
Download your fonts and add them locally, I’m putting them into
theme/fonts
, and add the following CSS at the top of your print.css file.
1
2
3
4
5
6
| @font-face { font-family : 'VT323' ; src : url ( '../fonts/VT323-Regular.ttf' ); font-style : regular; font-weight : 400 ; } |
Voila!
Well, we still have a margin, but that’s because we’re not using the right paper size. So, that aside, this is pretty great!
Why wkhtmltopdf?
Many of you may be familiar with TCPDF and dompdf – they are commonplace and reasonably lightweight. I tried TCPDF and here are the results:
Not as accurate as wkhtmltopdf out of the box, so I tried dompdf:
Hmm. This is because I am using custom fonts and documentation mentions that with changes I can get this working. However, wkhtmltopdf worked for me straight away with no tweaking or sifting through documentation. It aims to produce a complete copy of HTML so is generally 99% accurate. With my last project (which was far more complicated) I found a few problems when using a couple of CSS techniques that were more appropriate to screen, but even those were fixable.
Whilst wkhtmltopdf offers by far the best PDF output, setup involves installing an executable and this may make it unsuitable for many of you. However, I will make the assumption that if you are working with Drupal and need to achieve this level of layout complexity then you have access to your own server or VPS.
Have you tried any form of PDF (or other formats) generation with Drupal? I would love to hear tips and tweaks that you found.
Note: Only a member of this blog may post a comment.