iText 7 - Different footer on last page - Null pointer in PdfDictionary

浪尽此生 提交于 2019-12-31 03:45:12

问题


I am trying to implement a footer in iText7, the footer should be different on the last page of the document, I have added an event handler which is called when the document is closed but trying to then loop over the pages causes a null pointer exception:

java.lang.NullPointerException
at com.itextpdf.kernel.pdf.PdfDictionary.get(PdfDictionary.java:482)
at com.itextpdf.kernel.pdf.PdfDictionary.get(PdfDictionary.java:152)
at com.itextpdf.kernel.pdf.PdfPage.newContentStream(PdfPage.java:777)
at com.itextpdf.kernel.pdf.PdfPage.newContentStreamBefore(PdfPage.java:212)

Here is the code I am trying to implement:

private void onCloseDocument(
        final PdfDocument document, final float xpos, final float ypos)
{

    for (int i = 1; i <= document.getPdfDocument().getNumberOfPages(); i++)
    {
        final PdfPage page = document.getPdfDocument().getPage(i);
        final PdfCanvas canvas =
                new PdfCanvas(page.newContentStreamBefore(), page.getResources(), document.getPdfDocument());
        canvas.beginText();
        canvas.setFontAndSize(document.getFont(), FONT_SIZE);
        canvas.setLeading(14.4F);

        canvas.moveText(xpos, ypos);

        if (i == document.getPdfDocument().getNumberOfPages())
        {
            canvas.showText("Last page");
        }
        else
        {
            canvas.showText("Another page");
        }

        canvas.endText();
        canvas.stroke();
        canvas.release();
    }
}

Initially I thought this worked as I was inserting a single area break to get the pdf to split over two pages, however when I add more content and the pages increase naturally they I get this exception, similarly I have now tested by trying to insert multiple area breaks and I get the same exception.

Note* I have found that if I set immediateFlush to false when creating the document then I do not get this issue.

Is there a major performance hit\downside to doing this?


回答1:


Your approach

As you have found out yourself,

if I set immediateFlush to false when creating the document then I do not get this issue.

The cause is that itext usually attempts to write data to the output stream and free memory as early as possible to not leave too big a memory footprint. Thus, your approach

I have added an event handler which is called when the document is closed [...] trying to then loop over the pages

during that loop usually will try and manipulate pages whose contents have already been flushed and removed from the page object.

Setting immediateFlush to false prevents this. The down side is that you use more memory because now much more of the pdf is kept in memory until the end.

An alternative approach

An alternative approach would use the normal END_PAGE event listener pattern. You can give such an event listener some method to signal to it which page will be the last. When the event listener is triggered, it can check whether it is currently processing that page, and in that case it can draw an alternative footer, e.g. like this:

class TextFooterEventHandler implements IEventHandler {
    Document doc;
    PdfPage lastPage = null;

    public TextFooterEventHandler(Document doc) {
        this.doc = doc;
    }

    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfCanvas canvas = new PdfCanvas(docEvent.getPage());
        Rectangle pageSize = docEvent.getPage().getPageSize();
        canvas.beginText();
        try {
            canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.HELVETICA_OBLIQUE), 5);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (lastPage == docEvent.getPage()) {
            canvas.moveText((pageSize.getRight() - doc.getRightMargin() - (pageSize.getLeft() + doc.getLeftMargin())) / 2 + doc.getLeftMargin(), pageSize.getTop() - doc.getTopMargin() + 10)
                .showText("VERY SPAECIAL LAST PAGE HEADER")
                .moveText(0, (pageSize.getBottom() + doc.getBottomMargin()) - (pageSize.getTop() - doc.getTopMargin()) - 20)
                .showText("VERY SPAECIAL LAST PAGE FOOTER")
                .endText()
                .release();
        } else {
            canvas.moveText((pageSize.getRight() - doc.getRightMargin() - (pageSize.getLeft() + doc.getLeftMargin())) / 2 + doc.getLeftMargin(), pageSize.getTop() - doc.getTopMargin() + 10)
                .showText("this is a header")
                .moveText(0, (pageSize.getBottom() + doc.getBottomMargin()) - (pageSize.getTop() - doc.getTopMargin()) - 20)
                .showText("this is a footer")
                .endText()
                .release();
        }
    }
}    

(inner class in CreateSpecificFooters)

You can use it like this:

PdfDocument pdfDoc = new PdfDocument(new PdfWriter(new File("differentLastPageFooter.pdf")));
Document doc = new Document(pdfDoc);
TextFooterEventHandler eventHandler = new TextFooterEventHandler(doc);
pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE, eventHandler);
for (int i = 0; i < 12; i++) {
    doc.add(new Paragraph("Test " + i + " Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."));
}

// the currently last page is also the last page of the document, so inform the event listener 
eventHandler.lastPage = pdfDoc.getLastPage();
doc.close();

(CreateSpecificFooters test testDifferentLastPageFooter)

You may have observed that in contrast to my original comment

you can give that event listener some method to signal to it that the next event to come will be for the special case.

I here do not signal "the next event to come" to be special but instead "the next event to come for the given page object". The cause is that iText 7 page events are triggered with some delay, so when I give the signal as above, the event for the page before the last might not have been triggered yet.

By the way, I based this example on the iText samples class com.itextpdf.samples.sandbox.events.TextFooter. The samples class VariableHeader in the same package appears to indicate that my original thought (signaling "the next event to come" to be special) should have worked. The difference is, though, that in that example pages are switched by feeding AreaBreak objects to the document. This seems to trigger events early. In general, though, you cannot tell when pages shall switch, so in general you should also give the event listener the page to verify it has the correct page to treat specially.




回答2:


I have not used itext before, but the javadoc for onCloseDocument in itext5 says:

Called when the document is closed. Note that this method is called with the page number equal to the last page plus one.

could it be that you simply have to change your loop from

for (int i = 1; i <= document.getPdfDocument().getNumberOfPages(); i++)
{

to

for (int i = 1; i < document.getPdfDocument().getNumberOfPages(); i++)
{

? (note the i < ... instead of i <= )



来源:https://stackoverflow.com/questions/50236085/itext-7-different-footer-on-last-page-null-pointer-in-pdfdictionary

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!