10

I'm trying to figure out how to add a "Page X of Y" footer to each page in a PDF document, which I'm generating using iText 7.

Using an IEventHandler to generate the "Page X" part seems fairly straightforward - it's the "of Y" bit that I'm struggling with. I want to avoid generating the whole document twice in order to find out how many pages it has, as this would be a significant performance hit!

I've found a solution for this in iText 5 here: PDF Page Numbering in Java & iText, but iText 7 is a complete rewrite of iText with a totally different interface and so far I've been unable to find any similar iText 7 examples.

Community
  • 1
  • 1
Dan King
  • 3,412
  • 5
  • 24
  • 23
  • 3
    Could someone please create an "iText7" tag I can add to this, as I don't have sufficient reputation! :-( The "iText" tag isn't particularly helpful when searching for iText7 questions and answers, as iText 5 and iText 7 are completely different beasts! – Dan King Jul 26 '16 at 11:30
  • According to http://meta.stackexchange.com/questions/85358/what-are-the-guidelines-for-using-version-specific-tags and http://meta.stackoverflow.com/questions/299598/should-there-generally-be-only-one-tag-for-a-language-rather-than-multiple-esp, an `itext7` tag *might* be useful. I'm not against it. However, StackOverflow moderators would be very swift to squash such a tag, without knowing the details. – Amedee Van Gasse Jul 26 '16 at 11:46
  • I just created the tag, let's see how long it will live. To be honest, I'm not optimistic. – Amedee Van Gasse Jul 26 '16 at 11:55
  • 1
    Thanks @AmedeeVanGasse! The first link you posted has this in the correct answer: "I would only use version tags when the question content is irrevocably tied to a specific version of something and can never be relevant to earlier versions or later versions" - which definitely applies in this case as iText 5 and 7 are completely different to one another. The other question has a similar answer. So here's hoping...! :-) – Dan King Jul 26 '16 at 13:52
  • I hope the iText7 tag lasts! The new version is not only much, much more powerful, but it wildly different to use. People are going to have a lot of questions about it (I know I do!) – Draque Thompson Aug 10 '16 at 12:40
  • Does this answer your question? [PDF Page Numbering in Java & iText](https://stackoverflow.com/questions/11205777/pdf-page-numbering-in-java-itext) – Ravindra Gullapalli May 04 '21 at 13:21

1 Answers1

12

The answer you found for iText 5 references the MovieCountries1 example. This example has been rewritten for iText 7 as Listing_05_20_MovieCountries1. Its pivotal code:

protected PdfFont bold;
protected PdfFont italic;
protected PdfFont normal;

protected PdfFormXObject template;

public void manipulatePdf(String dest) throws IOException, SQLException {
    PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
    Document doc = new Document(pdfDoc, new PageSize(PageSize.A4), true);
    doc.setMargins(54, 36, 36, 36);

    bold = PdfFontFactory.createFont(FontConstants.HELVETICA_BOLD);
    italic = PdfFontFactory.createFont(FontConstants.HELVETICA_OBLIQUE);
    normal = PdfFontFactory.createFont(FontConstants.HELVETICA);

    template = new PdfFormXObject(new Rectangle(550, 803, 30, 30));
    PdfCanvas canvas = new PdfCanvas(template, pdfDoc);

    HeaderHandler headerHandler = new HeaderHandler();
    pdfDoc.addEventHandler(PdfDocumentEvent.START_PAGE, headerHandler);

    ... ADD CONTENT ...

    canvas.beginText();
    try {
        canvas.setFontAndSize(PdfFontFactory.createFont(FontConstants.HELVETICA), 12);
    } catch (IOException e) {
        e.printStackTrace();
    }
    canvas.moveText(550, 803);
    canvas.showText(Integer.toString(pdfDoc.getNumberOfPages()));
    canvas.endText();
    canvas.release();

    doc.close();
}

public class HeaderHandler implements IEventHandler {
    protected String country;

    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfPage page = docEvent.getPage();
        int pageNum = docEvent.getDocument().getPageNumber(page);
        PdfCanvas canvas = new PdfCanvas(page);
        canvas.beginText();
        try {
            canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.HELVETICA), 12);
        } catch (IOException e) {
            e.printStackTrace();
        }
        canvas.moveText(34, 803);
        canvas.showText(country);
        canvas.moveText(450, 0);
        canvas.showText(String.format("Page %d of", pageNum));
        canvas.endText();
        canvas.stroke();
        canvas.addXObject(template, 0, 0);
        canvas.release();
    }

    public void setHeader(String country) {
        this.country = country;
    }
}

You'll find rewrites of many other samples there, too.


As @Bruno remarked in a comment, there even is a slightly different example which has been genuinely created for iText 7 (in contrast to the example above which is the port of an iText 5 example).

It's an example accompanying the chapter 7 of the iText 7: building blocks tutorial. It uses showTextAligned() to make sure the "Page X of" nicely matches with the "Y", no matter how many digits X and Y have, cf. its end-of-page event listener method:

public void handleEvent(Event event) {
    PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
    PdfDocument pdf = docEvent.getDocument();
    PdfPage page = docEvent.getPage();
    int pageNumber = pdf.getPageNumber(page);
    Rectangle pageSize = page.getPageSize();
    PdfCanvas pdfCanvas = new PdfCanvas(
        page.newContentStreamBefore(), page.getResources(), pdf);
    Canvas canvas = new Canvas(pdfCanvas, pdf, pageSize);
    Paragraph p = new Paragraph()
        .add("Page ").add(String.valueOf(pageNumber)).add(" of");
    canvas.showTextAligned(p, x, y, TextAlignment.RIGHT);
    pdfCanvas.addXObject(placeholder, x + space, y - descent);
    pdfCanvas.release();
}

(C07E03_PageXofY.java, event handler class PageXofY)

Mattew Eon
  • 1,722
  • 1
  • 21
  • 38
mkl
  • 90,588
  • 15
  • 125
  • 265
  • 2
    That's really useful - thanks @mkl! You missed out one of the key parts though - where it sets the total page count: `canvas.showText(Integer.toString(pdfDoc.getNumberOfPages()));` – Dan King Jul 27 '16 at 10:20
  • I've been playing with my own adapted version of this example, and I think there's a couple of mistakes in it. `canvas.moveText(450,0)` puts the "Page X of" text at the very bottom of the page with no margin, which looks a little odd, but more importantly, `canvas.moveText(550,803)` further up, puts the page count in the top right hand corner of the document, instead of after the "Page X of" text. – Dan King Aug 05 '16 at 10:51
  • 1
    *`canvas.moveText(450,0)` puts the "Page X of" text at the very bottom of the page with no margin* - No, you forget to take the `canvas.moveText(34, 803)` two lines before that into account. Thus, "Page %d of" is effectively drawn at `(484, 803)` which is clearly the header region. *`canvas.moveText(550,803)` further up, puts the page count in the top right hand corner of the document, instead of after the "Page X of" text* - the top right corner *is* after the "Page X of" text. – mkl Aug 05 '16 at 11:01
  • Ah - OK - my mistake. I took out the "country" part as I didn't want that, and hadn't twigged that moveText() was relative. – Dan King Aug 05 '16 at 11:11
  • I'm struggling to understand the relationship between the canvas and the template (PdfFormXObject). The template is defined with location 550,803, but it then seems to be necessary to move to that location on the canvas (which encapsulates(?) the template) before writing the page count. What's actually going on here? Is it more useful to think of the template as a window onto the canvas? – Dan King Aug 05 '16 at 14:19
  • 1
    *Is it more useful to think of the template as a window onto the canvas?* - That sounds like a good idea. The canvas of a form xobject is an endless plain (well, actually it is limited but very very large) and the form xobject defines a rectangle on the canvas the content of which will eventually be visible. – mkl Aug 05 '16 at 14:48
  • Hi, I am writing the final chapter of the [iText 7: building blocks](http://developers.itextpdf.com/content/itext-7-building-blocks) tutorial. The text isn't finished yet, but you can already find the [PageXofY](http://developers.itextpdf.com/content/itext-7-building-blocks/examples/chapter-7#2581-c07e03_pagexofy.java) example in the examples section of chapter 7. It's slightly different from @mkl's code. I use `showTextAligned()` to make sure the "Page X of" nicely matches with the "Y", no matter how many digits X and Y have. – Bruno Lowagie Aug 16 '16 at 08:04