waspinator
waspinator

Reputation: 6816

Display header and footer using CakePdf

I'm trying to render a pdf using CakePdf 3.5.3 using the wkhtmltopdf engine with html headers on every page showing custom text as well as the page number.

https://wkhtmltopdf.org/usage/wkhtmltopdf.txt describes the process as passing the --header-html command line argument and using the following javascript and html:

  <!DOCTYPE html>
  <html><head><script>
  function subst() {
      var vars = {};
      var query_strings_from_url = document.location.search.substring(1).split('&');
      for (var query_string in query_strings_from_url) {
          if (query_strings_from_url.hasOwnProperty(query_string)) {
              var temp_var = query_strings_from_url[query_string].split('=', 2);
              vars[temp_var[0]] = decodeURI(temp_var[1]);
          }
      }
      var css_selector_classes = ['page', 'frompage', 'topage', 'webpage', 'section', 'subsection', 'date', 'isodate', 'time', 'title', 'doctitle', 'sitepage', 'sitepages'];
      for (var css_class in css_selector_classes) {
          if (css_selector_classes.hasOwnProperty(css_class)) {
              var element = document.getElementsByClassName(css_selector_classes[css_class]);
              for (var j = 0; j < element.length; ++j) {
                  element[j].textContent = vars[css_selector_classes[css_class]];
              }
          }
      }
  }
  </script></head><body style="border:0; margin: 0;" onload="subst()">
  <table style="border-bottom: 1px solid black; width: 100%">
    <tr>
      <td class="section"></td>
      <td style="text-align:right">
        Page <span class="page"></span> of <span class="topage"></span>
      </td>
    </tr>
  </table>
  </body></html>

I created src/Template/Layout/pdf/default.ctp and src/Template/Layout/pdf/header.ctp

default.ctp

<!DOCTYPE html>
<html lang="en">
<head>
    <?= $this->Html->charset() ?>
    <title>
        <?= $this->fetch('title') ?>
    </title>
    <?= $this->Html->meta('icon') ?>

</head>
<body>
    <?= $this->Flash->render() ?>
    <div>
        <?= $this->fetch('content') ?>
    </div>
</body>
</html>

header.ctp

<!DOCTYPE html>
<html lang="en">
<head>
    <script>
        function subst() {
            var vars = {};
            var query_strings_from_url = document.location.search.substring(1).split('&');
            for (var query_string in query_strings_from_url) {
                if (query_strings_from_url.hasOwnProperty(query_string)) {
                    var temp_var = query_strings_from_url[query_string].split('=', 2);
                    vars[temp_var[0]] = decodeURI(temp_var[1]);
                }
            }
            var css_selector_classes = ['page', 'frompage', 'topage', 'webpage', 'section', 'subsection', 'date', 'isodate', 'time', 'title', 'doctitle', 'sitepage', 'sitepages'];
            for (var css_class in css_selector_classes) {
                if (css_selector_classes.hasOwnProperty(css_class)) {
                    var element = document.getElementsByClassName(css_selector_classes[css_class]);
                    for (var j = 0; j < element.length; ++j) {
                        element[j].textContent = vars[css_selector_classes[css_class]];
                    }
                }
            }
        }
    </script>
    <title></title>
</head>
<body onload="subst()">
<?= $some_view_variable ?>
Page <span class="page"></span> of <span class="topage"></span>
</body>
</html>

This is how I configure CakePdf in my controller.

        Configure::write('CakePdf', [
            'engine' => [
                'className' => 'CakePdf.WkHtmlToPdf',
                'binary' => "/usr/local/bin/wkhtmltopdf",
                'options' => [
                    'header-html' => APP . 'Template' . DS . 'Layout' . DS . 'pdf' . DS . 'header.ctp',
                ],
            ],
            
            'margin' => [
                'bottom' => 0,
                'left' => 0,
                'right' => 0,
                'top' => 0
            ],
            'pageSize' => 'Letter',
            'orientation' => 'portrait'
        ]);

The header information is not shown in the generated PDF. I'm also not sure how to pass viewVars to the header template.

Upvotes: 0

Views: 787

Answers (1)

Greg Schmidt
Greg Schmidt

Reputation: 5099

My understanding is that the header must be in a .html file, it must be raw HTML (e.g. not a Cake template), and I've not found a way to specify the header-html option. Here's the hack I've used to work around it:

$view = new View();
$dir = TMP . 'html';
if (!is_dir($dir)) {
    mkdir($dir);
}   

while (true) {
    // WkHtmlToPdf requires .html extension on these files, but tempnam can't do that.
    $file = tempnam($dir, 'header');
    if (rename($file, $file . '.html')) {
        $file .= '.html';
        break;
    } else {
        unlink($file);
    }   
}   

file_put_contents($file, '<!DOCTYPE HTML><html lang=\'en-US\'><body style=\'font-family: "Times New Roman"\'>' . $view->element($element) . '</body></html>');

// Hack to access protected member: we want --header-html on the command line.
// This is easier than extending the class for a single such usage.
$closure = function($file) {
    $this->_header = ['html' => 'file://' . $file];
};
$hack = \Closure::bind($closure, $pdf, 'CakePdf\Pdf\CakePdf');
$hack($file);
$this->_tmp[] = $file;

This is, of course, all off in a separate function, so it's easy to replace if I find a better way. :-)

Upvotes: 2

Related Questions