65

I'm developing an electronic invoicing system, and one of our features is generating PDFs of the invoices, and mailing them. We have multiple templates for invoices, and will create more later, so we decided to use HTML templates, generate HTML document, and then convert it to PDF. But we're facing a problem with wkhtmltopdf, that as far as I know (I've been Googleing for days to find the solution) we cannot simply both use HTML as header/footer, and show page numbers in them.

In a bug report (or such) ( http://code.google.com/p/wkhtmltopdf/issues/detail?id=140 ) I read that with JavaScript it is achievable this combo. But no other information on how to do it can be found on this page, or elsewhere.

It is, of course not so important to force using JavaScript, if with wkhtmltopdf some CSS magic could work, it would be just as awesome, as any other hackish solutions.

Thanks!

Tamás Barta
  • 1,617
  • 1
  • 19
  • 23

9 Answers9

92

Actually it's much simpler than with the code snippet. You can add the following argument on the command line: --footer-center [page]/[topage].

Like richard mentioned, further variables are in the Footers and Headers section of the documentation.

aknuds1
  • 65,625
  • 67
  • 195
  • 317
makro
  • 929
  • 1
  • 6
  • 3
  • 20
    That's right, but this way you can't use HTML, as I remember back working on this project. – Tamás Barta Nov 22 '12 at 00:54
  • BROKEN LINK: http://madalgo.au.dk/~jakobt/wkhtmltoxdoc/wkhtmltopdf-0.9.9-doc.html – marienke Oct 21 '13 at 09:53
  • it pushes background to the bottom. I have a bottom line in my page background and I need show paging over this line. but in order to show pages this way I need to add margin-bottom 5 parameter and it increases my pdf page height and pushes bottom line to the next page. – Oleg Abrazhaev Jun 15 '17 at 09:38
  • have fixed it with 'footer-spacing' => -4, 'footer-font-size' => 10, – Oleg Abrazhaev Jun 15 '17 at 09:44
  • just a little example where the argument comes, it took me several minutes to figure out its not in the end `.\'Program Files (x86)'/wkhtmltopdf/bin/wkhtmltopdf.exe --footer-center [page]/[topage] C:\faktury\f-2.html C:\-faktury\f-wk2.pdf` – David Louda Feb 12 '21 at 12:18
50

Among a few other parameters, the page number and total page number are passed to the footer HTML as query params, as outlined in the official docs:

... the [page number] arguments are sent to the header/footer html documents in GET fashion.

Source: http://wkhtmltopdf.org/usage/wkhtmltopdf.txt

So the solution is to retrieve these parameters using a bit of JS and rendering them into the HTML template. Here is a complete working example of a footer HTML:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <script>
        function substitutePdfVariables() {

            function getParameterByName(name) {
                var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
                return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
            }

            function substitute(name) {
                var value = getParameterByName(name);
                var elements = document.getElementsByClassName(name);

                for (var i = 0; elements && i < elements.length; i++) {
                    elements[i].textContent = value;
                }
            }

            ['frompage', 'topage', 'page', 'webpage', 'section', 'subsection', 'subsubsection']
                .forEach(function(param) {
                    substitute(param);
                });
        }
    </script>
</head>
<body onload="substitutePdfVariables()">
    <p>Page <span class="page"></span> of <span class="topage"></span></p>
</body>
</html>

substitutePdfVariables() is called in body onload. We then get each supported variable from the query string and replace the content in all elements with a matching class name.

theDmi
  • 17,546
  • 6
  • 71
  • 138
35

To show the page number and total pages you can use this javascript snippet in your footer or header code:

  var pdfInfo = {};
  var x = document.location.search.substring(1).split('&');
  for (var i in x) { var z = x[i].split('=',2); pdfInfo[z[0]] = unescape(z[1]); }
  function getPdfInfo() {
    var page = pdfInfo.page || 1;
    var pageCount = pdfInfo.topage || 1;
    document.getElementById('pdfkit_page_current').textContent = page;
    document.getElementById('pdfkit_page_count').textContent = pageCount;
  }

And call getPdfInfo with page onload

Of course pdfkit_page_current and pdfkit_page_count will be the two elements that show the numbers.

Snippet taken from here

Dan Mazzini
  • 1,005
  • 10
  • 19
  • 21
    PDFKit is just a Ruby wrapper for wkhtmltopdf – Dan Mazzini Aug 24 '11 at 15:02
  • Oops. Apologies for not actually checking your link. – thirtydot Aug 24 '11 at 15:07
  • Thank you for replying! I've created two spans with the given IDs, but somehow the JavaScript does not run, since nothing changes. But that's another issue, you solved my problem to find out how wkhtmltopdf passes page numbers to headers! :) – Tamás Barta Aug 25 '11 at 15:13
  • Ohh, I just mistyped something as usual... Works perfectly! Thanks again! – Tamás Barta Aug 25 '11 at 15:23
  • BROKEN LINK: http://madalgo.au.dk/~jakobt/wkhtmltoxdoc/wkhtmltopdf-0.9.9-doc.html – marienke Oct 21 '13 at 10:06
  • 1
    hi all, i tried this answer, but in my case i am adding it in header space, but it displays only once. In my case, pdf generated is having 6 pages from html. please help – Sushant Jun 10 '14 at 14:03
  • 2
    @Sushant, you need to use the --header-html parameter when calling wkhtmltopdf to supply a url that will be added to the start of each page. There is a slightly more complete example on http://wkhtmltopdf.org/usage/wkhtmltopdf.txt . – Yourpalal Jun 26 '15 at 03:39
  • 2
    I think we are talking about wkhtmltopdf... Why PDFKit mentioned here. If someone is not using Ruby then these codes are useless. – Shakeel Ahmed May 06 '18 at 14:39
25

From the wkhtmltopdf documentation (http://madalgo.au.dk/~jakobt/wkhtmltoxdoc/wkhtmltopdf-0.9.9-doc.html) under the heading "Footers and Headers" there is a code snippet to achieve page numbering:

<html><head><script>
function subst() {
  var vars={};
  var x=document.location.search.substring(1).split('&');
  for(var i in x) {var z=x[i].split('=',2);vars[z[0]] = unescape(z[1]);}
  var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];
  for(var i in x) {
    var y = document.getElementsByClassName(x[i]);
    for(var j=0; j<y.length; ++j) y[j].textContent = vars[x[i]];
  }
}
</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>

There are also more available variables which can be substituted other than page numbers for use in Headers/Footers.

Tamás Barta
  • 1,617
  • 1
  • 19
  • 23
Richard Pursehouse
  • 1,109
  • 13
  • 21
  • 2
    BROKEN LINK: http://madalgo.au.dk/~jakobt/wkhtmltoxdoc/wkhtmltopdf-0.9.9-doc.html – marienke Oct 21 '13 at 09:53
  • 2
    By itself, that snippet does nothing. The page you linked to includes the Javascript code which actually populates the `span`s in the snippet. – joshuahhh May 06 '17 at 01:25
  • 1
    hey It doesn't work for me.. I do same thing but it shows undefined in those values – Dhara May 15 '17 at 11:15
  • If using XHTML, wrap the script in `//<![CDATA['` `//]]>` tags. I'm using thymeleaf in a spring application and it wouldn't accept the javascript without these tags. – Juangui Jordán Jan 26 '18 at 10:38
  • @Dhara: It will only work in the header or the footer. It will not work in the body. – Ishmaeel Nov 05 '19 at 08:13
  • 1
    ProTip: If you are not using certain information like the `topage`, `webpage`, `section`, `subsection` and `subsubsection` then you should remove it. We are generating fairly large PDFs (7,000+ pages) and were running into a segmentation fault at ~1,000 pages. After a thorough investigation, it came down to removing those unused variables. We haven't seen the segmentation fault since. – Joshua Pinter Feb 25 '21 at 12:47
  • @JoshuaPinter how do you remove those variables? (Using C# if it matters) – A Petrov Oct 29 '21 at 11:39
  • 1
    @APetrov Literally just remove them from `var x=['frompage','topage','page','webpage','section','subsection','subsubsection'];` and they won't be processed in the `for` loop that follows it. – Joshua Pinter Oct 29 '21 at 14:25
2

Safe approach, even if you are using XHTML (for example, with thymeleaf). The only difference with other's solution is the use of // tags.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <script>
        /*<![CDATA[*/
        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', 'topage'];
            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 onload="subst()">
    <div class="page-counter">Page <span class="page"></span> of <span class="topage"></span></div>
</body>

Last note: if using thymeleaf, replace <script> with <script th:inline="javascript">.

Juangui Jordán
  • 6,091
  • 2
  • 35
  • 31
2

My example shows how to hide some text on a particular page, for this case it shows the text from page 2 onwards

<span id='pageNumber'>{#pageNum}</span>
<span id='pageNumber2' style="float:right; font-size: 10pt; font-family: 'Myriad ProM', MyriadPro;"><strong>${siniestro.numeroReclamo}</strong></span>
<script>
    var elem = document.getElementById('pageNumber');
    document.getElementById("pageNumber").style.display = "none";
       if (parseInt(elem.innerHTML) <= 1) {
           elem.style.display = 'none';
           document.getElementById("pageNumber2").style.display = "none";
       }
</script>
0

Right From the wkhtmltopdf Docs

Updated for 0.12.6.

Footers And Headers:
Headers and footers can be added to the document by the --header-* and --footer* arguments respectively. In header and footer text string supplied to e.g. --header-left, the following variables will be substituted.

  • [page] Replaced by the number of the pages currently being printed
  • [frompage] Replaced by the number of the first page to be printed
  • [topage] Replaced by the number of the last page to be printed
  • [webpage] Replaced by the URL of the page being printed
  • [section] Replaced by the name of the current section
  • [subsection] Replaced by the name of the current subsection
  • [date] Replaced by the current date in system local format
  • [isodate] Replaced by the current date in ISO 8601 extended format
  • [time] Replaced by the current time in system local format
  • [title] Replaced by the title of the of the current page object
  • [doctitle] Replaced by the title of the output document
  • [sitepage] Replaced by the number of the page in the current site being converted
  • [sitepages] Replaced by the number of pages in the current site being converted

As an example specifying --header-right "Page [page] of [topage]", will result in the text "Page x of y" where x is the number of the current page and y is the number of the last page, to appear in the upper left corner in the document.

Headers and footers can also be supplied with HTML documents. As an example one could specify --header-html header.html, and use the following content in header.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>

ProTip

If you are not using certain information like the webpage, section, subsection, subsubsection, then you should remove them. We are generating fairly large PDFs and were running into a segmentation fault at ~1,000 pages.

After a thorough investigation, it came down to removing those unused variables. No we can generate 7,000+ page PDFs without seeing the Segmentation Fault.

Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
0

I have not understood the command line en finally I find the solution to put this information directly in the controller without any JS en command line.

In my controller when I call the format.pdf I just put the line footer:

    format.pdf do
      render :pdf => "show",
      page_size: 'A4',
      layouts: "pdf.html",
      encoding: "UTF-8",
      footer: {
        right: "[page]/[topage]",
        center: "Qmaker",
      },
      margin:  {   top:15,
        bottom: 15,
        left: 10,
        right: 10}
    end
-1

The way it SHOULD be done (that is, if wkhtmltopdf supported it) would be using proper CSS Paged Media: http://www.w3.org/TR/css3-gcpm/

I'm looking into what it will take now.

rainabba
  • 3,804
  • 35
  • 35
  • 1
    I also looked at CSS paged media, but the headers and footers are rendered individually per page, so they cannot have any of these information. The dilemma was: how to solve the problem of having to create and position headers and footers on every page with CSS. I couldn't find an answer, so wkhtmltopdf's header and footer mechanism looked an easier way. – Tamás Barta Jan 25 '14 at 01:41
  • 6
    This is hardly an answer is it, if wkhtmltopdf doesn't support it? – aknuds1 Oct 04 '16 at 18:42