iText 7 - 最后一页上的不同页脚 - PdfDictionary 中的空指针

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

我正在尝试在 iText7 中实现页脚,页脚在文档的最后一页上应该有所不同,我添加了一个事件处理程序,该处理程序在文档关闭时调用但随后尝试遍历页面导致空指针异常:

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)

这是我要实现的代码:

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();
    }
}

最初我认为这是可行的,因为我插入了一个区域分隔符以使 pdf 分成两页,但是当我添加更多内容并且页面自然增加时,我得到了这个异常,同样我现在已经测试过通过尝试插入多个区域中断,我得到了相同的异常。

注意* 我发现如果在创建文档时将 immediateFlush 设置为 false,则不会出现此问题。

这样做有什么主要表现hit\downside吗?

我以前没有用过 itext,但是 javadoc for onCloseDocument in itext5 说:

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

难道你只需要改变你的循环

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

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

? (注意 i < ... 而不是 i <= )

你的方法

正如您自己发现的那样,

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

原因是itext通常会尝试将数据写入输出流并尽早释放内存,以免留下太大的内存占用空间。因此,你的方法

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

在该循环期间,通常会尝试操作其内容已被刷新并从页面对象中删除的页面。

immediateFlush 设置为 false 可以防止这种情况。不利的一面是你使用更多的内存,因为现在更多的 pdf 保存在内存中直到结束。

另一种方法

另一种方法是使用正常的 END_PAGE 事件侦听器模式。您可以为这样的事件侦听器提供一些方法来向它发出信号,哪个页面将是最后一页。当事件侦听器被触发时,它可以检查它当前是否正在处理该页面,在这种情况下它可以绘制一个替代页脚,例如像这样:

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();
        }
    }
}    

CreateSpecificFooters中的class)

你可以这样使用:

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 测试 testDifferentLastPageFooter)

您可能已经注意到,与我最初的评论形成对比

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

我在这里并不表示“下一个事件即将到来”是特殊的,而是“下一个事件将发生给定页面对象”。原因是 iText 7 页面事件的触发有一些延迟,所以当我发出上述信号时,最后一页的事件可能还没有被触发。

顺便说一句,我基于 iText 示例 class com.itextpdf.samples.sandbox.events.TextFooter 构建了这个示例。同一包中的示例 class VariableHeader 似乎表明我最初的想法( 发出“下一个事件即将到来”的信号 )应该有效。不过,不同之处在于,在该示例中,页面是通过将 AreaBreak 对象提供给文档来切换的。这似乎很早就触发了事件。不过,一般来说,您无法判断页面何时切换,因此通常您还应该向事件侦听器提供页面以验证它是否具有正确的页面以进行特殊处理。