8
votes

Here is our answer:

We are currently using wkhtmltopdf to generate a PDF from a given html template.

Some background information:

We are using Sulu CMF to build our back end, which is based on Symfony2. The KnpSnappy Bundle is used as a Symfony wrapper for wkhtmltopdf.

How we generate PDFs:

As many PDFs share the same header and footer we have created a BasePDFBundle which offers a PDFManager to build the PDF on the fly by a given TWIG template. Per default a generic header and footer (usually with the customer's name and logo) is included.

The footer Problem / Page numbers in the footer (or header):

It is very useful to add page numbers to a PDFs eg. for orders, however most of our content is added dynamically (eg a product list). As the styling of the PDF can change and the content itself is dynamically added there had to be a quick and easy way to add the current and the total page to the generated PDF. Here is what we did:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
    <base href="{{ app.request.schemeAndHttpHost }}" />
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <link rel="stylesheet" href="{{ asset('bundles/pdfbase/css/pdfstyles.css') }}"/>
</head>
<body class="footer">
    <div class="footer-container">
        <div class="footer-widget">
            <b>FooBar Company Name</b>
        </div>
        <div class="text-align-right">
            <span class="page"></span>/<span class="topage"></span>
        </div>
    </div>

    <script type="text/javascript">
     (function() {
       // get all url parameters and transform it to a list
       // pdf generator adds a param for current and the total page sum
       // add the value to the html elements
       var urlParams = document.location.search.substring(1).split('&');
       var urlParamsList = {};
       var pagingElements = ['topage', 'page'];

       for (var i in urlParams) {
         var param = urlParams[i].split('=', 2);
         urlParamsList[param[0]] = unescape(param[1]);
       }

       for (var i in pagingElements) {
         var elem = document.getElementsByClassName(pagingElements[i]);
         for (var j = 0; j < elem.length; ++j) {
           elem[j].textContent = urlParamsList[pagingElements[i]];
         }
       }
     })();
    </script>
 </body>

Yes the variable names of page and topage could be better, however they are the same as the KnpSnappy wrapper uses when merging twig templates to the final PDF template. This is the easiest way to get the current and total page number because you can let the wrapper do all the calculations.

In the end you simply have to replace the text of html tags and thats it!

Differences between your local machine and server:

As wkhtmltopdf opens a virtual browser to "render" the twig templates this could lead to errors in your pdf generation on your server. We found out it is not a good idea to use event tags like <body onload="doSomething()"> you rather should trigger your javascript code like we did it in the example above.

1
Am I missing a question? Or is this an answer seeking a question?Kevin Brown
well the question is directly answered =)notsure
The key point is to not use onload="doSomething". All the examples I've seen were using that approach and it didn't work. This does. Thanks!codemonkey

1 Answers

0
votes

If you are using KnpSnappy as wrapper of wkhtmltopdf then you can setup various options for your pdf.

see "Footers And Headers:" section in wkhtmltopdf documentation, here http://wkhtmltopdf.org/usage/wkhtmltopdf.txt

[page] Replaced by the number of the pages currently being printed

[topage] Replaced by the number of the last page to be printed

Following is sample of Symfony2 controller, check footer-html option in $pdfOptions array, where I have used both placeholder to print page number in footer of each page of pdf file.

<?php

namespace Rm\PdfBundle\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;

/**
 * Pdf controller
 */
class PdfController extends Controller
{
    /**
     * Serve pdf
     *
     * @Route("/article/{slug}", name="rm_pdf_view")
     * @Method({"GET"})
     */
    public function viewAction(Request $request, $slug)
    {

        // build html
        $html = $this->renderView('RmPdfBundle:Pdf:view.html.twig', array(
            'data'  => $yourDataToTemplate,
        ));


        // prepare pdf options
        $pdfOptions = array(

            'footer-html'      => '<p>Page : [page] of [pageTo]</p>',

            'footer-font-size' => '10',
            'page-size'        => 'A4',
            'orientation'      => 'Portrait',
            'margin-top'       => 10,
            'margin-bottom'    => 20,
            'margin-left'      => 15,
            'margin-right'     => 15,
            );

        // file name of pdf
        $pdfFileName = "nameOfYourPdfFile.pdf";

        // server pdf file to user using knp_snappy.pdf service
        return new Response(
            $this->get('knp_snappy.pdf')->getOutputFromHtml($html, $pdfOptions),
            200,
            array(
                'Content-Type'          => 'application/pdf',
                'Content-Disposition'   => 'attachment; filename="'.$pdfFileName.'"',
            )
        );

    }
}