How do I set header and footer in a PDF, from HTML, with knp-snappy-bundle?

Xavi Montero picture Xavi Montero · Nov 30, 2017 · Viewed 9k times · Source

Long story short

With knp-snappy-bundle I can't generate a header in the PDF, while I can actually generate a footer.

Is it a bug, a feature, or I'm doing something wrong?

Details

1. Environment

I'm testing the knp-snappy-bundle, I've also installed the wkhtmltopdf binary from h4cc. This is part of my composer.json:

"h4cc/wkhtmltopdf-amd64": "^0.12.3",
"knplabs/knp-snappy-bundle": "^1.5",

The resulting binary of wkhtmltopdf says this:

$ vendor/bin/wkhtmltopdf-amd64 --version
wkhtmltopdf 0.12.3 (with patched qt)

2. Without headers or footers, it works

I've setup a controller that uses the knp-snappy-bundle and it works:

This is my PdfController:

public function downloadPdfAction( string $docName ) : Response
{
    $pdf = $this->getPdf( $docName );

    return new PdfResponse( $pdf, $this->getFilename( $docName ) );
}

private function getPdf( string $docName ) : string
{
    $html = $this->renderView( 'AppBundle:documents:' . $docName . '.pdf.twig' );

    $options = [];

    $generator = $this->getPdfGenerator();
    $pdf = $generator->getOutputFromHtml( $html, $options );

    return $pdf;
}

private function getPdfGenerator() : Pdf
{
    $generator = $this->get( 'knp_snappy.pdf' );

    return $generator;
}

It basically:

  • Has a downloadPdf action that
      1. Gets a PDF document by its name, passed in as a parameter. In this example we'll use 'helloWorld'.
      1. Returns a new Response created with the content of the PDF, using the PdfResponse class in the bundle.
  • To get the PDF
      1. it renders a view with the twigengine.
      1. gets the service (splitted into another function getPdfGenerator()).
      1. uses the service to getOutputFromHtml() with no options passed-in.

This is my helloWorld.pdf.twig:

<html>
    <body>
        <div class="pdfPageBody">
            <h1>
                Hello, World!
            </h1>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </div>
    </body>
</html>

And here it is the resulting PDF, exactly as expected:

enter image description here

3. With headers and footers, it fails!

So I now add the header and footer. To do so, I just add a couple of twigs, render the HTMLs into a couple of variables, and pass them in into the options of the Pdf renderer:

private function getPdf( string $docName ) : string
{
    $html = $this->renderView( 'AppBundle:documents:' . $docName . '.pdf.twig' );

    $header = $this->renderView( 'AppBundle:documents:header.pdf.twig' );
    $footer = $this->renderView( 'AppBundle:documents:footer.pdf.twig' );

    $options = [
        'header-html' => $header,
        'footer-html' => $footer,
    ];

    $generator = $this->getPdfGenerator();
    $pdf = $generator->getOutputFromHtml( $html, $options );

    return $pdf;
}

The header and footer are identical one to each other, except for the contained text:

This is my header.pdf.twig:

<html>
    <body>
        <div style="border: 5px dashed crimson; color: maroon; background-color: darksalmon">
            This is a header
        </div>
    </body>
</html>

And the footer.pdf.twig:

<html>
    <body>
        <div style="border: 5px dashed crimson; color: maroon; background-color: darksalmon">
            This is a footer
        </div>
    </body>
</html>

And wow!!! The footer gets rendered but the header does not!!

This is what I get:

enter image description here

To be noted in the image:

  1. There is "something" in the header. I can see the text of the page content like "clipped".
  2. The footer does not fully render, as it hides all the bottom border-lines, and the text base-line is aligned to the bottom edge of the page.

Soooo, hence my question!!

  • What should I do to get the header rendered as well?
  • Should'nt it be that I could see the header rendered in the PDF with this simple setup? Why does it not appear?

Answer

Xavi Montero picture Xavi Montero · Dec 1, 2017

Solved!

Well, it seems that wkhtmltopdf is really strict to consider the doctype and it does weird things if not.

Inspired here: https://stackoverflow.com/a/28343079/1315009

So, I changed all the twigs to include <!DOCTYPE html>:

helloWorld.pdf.twig

<!DOCTYPE html>
<html>
    <body>
        <div class="pdfPageBody">
            <h1>
                Hello, World!
            </h1>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </div>
    </body>
</html>

header.pdf.twig

<!DOCTYPE html>
<html>
    <body>
        <div style="border: 5px dashed crimson; color: maroon; background-color: darksalmon">
            This is a header
        </div>
    </body>
</html>

footer.pdf.twig

<!DOCTYPE html>
<html>
    <body>
        <div style="border: 5px dashed crimson; color: maroon; background-color: darksalmon">
            This is a footer
        </div>
    </body>
</html>

Final result

I finally got this:

enter image description here

It happens to have:

  1. The header, with no weird clipping on the body
  2. The footer.

Hope to help!