问题
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