Search code examples
javaitextbytearrayoutputstreampdfstamper

Itext PdfStamper getOverContent returns null when ByteArrayOutputStream used


I am working on a pdf file. I need to add a watermark on an exist pdf file. That's why, I wrote following code. When I used FileOutputStream it works perfectly, but I need to use ByteArrayOutputStream because my data are come from database and I need to save it as byte array. Problem starts in here. When I use ByteArrayOutputStream getOvercontent method of PdfStamper returns null. How can I handle this? Thanks in advance.

byte[] bytes = getAsByteArray();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(bytes.length);
PdfReader reader = new PdfReader(bytes);
int numberOfPages= reader.getNumberOfPages();
outputStream.write(bytes, 0, bytes.length);
PdfStamper stamper = new PdfStamper(reader, outputStream);
Font font = font(Font.NORMAL, 135, Color.RED);
for (int i = 0; i < numberOfPages; i++) {
     //over Content is null
     PdfContentByte over = stamper.getOverContent(i);
     Phrase p = new Phrase("WATERMARK", font);
     PdfGState gs = new PdfGState();
     gs.setFillOpacity(0.5f);
     over.setGState(gs);
     ColumnText.showTextAligned(over, Element.ALIGN_CENTER, p, 330, 450, 45);
     over.saveState();
}

And the old version of code is this.

byte[] bytes = FileUtils.readFileToByteArray(new File("myPdf.pdf"));
PdfReader reader = new PdfReader(bytes);
int numberOfPages= reader.getNumberOfPages();
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("MyPdfOut.pdf"));
Font f = font(Font.NORMAL, 135, Color.RED);
for (int i = 1; i <= numberOfPages; i++) {
    PdfContentByte over = stamper.getOverContent(i);
    Phrase p = new Phrase("WATERMARK", f);
    PdfGState gs = new PdfGState();
    gs.setFillOpacity(0.5f);
    over.setGState(gs);
    ColumnText.showTextAligned(over, Element.ALIGN_CENTER, p, 330, 450, 45);
    over.saveState();
}

Solution

  • This is forbidden:

    ByteArrayOutputStream outputStream = new ByteArrayOutputStream(bytes.length);
    outputStream.write(bytes, 0, bytes.length);
    PdfStamper stamper = new PdfStamper(reader, outputStream);
    

    You create an OutputStream, named outputStream, and it seems that you copy a full-blown existing PDF to that OutputStream (using write()). Then you seem to have the intention to add another full-blown PDF to that OutputStream by passing that same OutputStream to the PdfStamper instance. That should result in a corrupt PDF file.

    This will also result in illegal PDF syntax:

     PdfContentByte over = stamper.getOverContent(i);
     Phrase p = new Phrase("WATERMARK");
     PdfGState gs = new PdfGState();
     gs.setFillOpacity(0.5f);
     over.setGState(gs);
     ColumnText.showTextAligned(over, Element.ALIGN_CENTER, p, 330, 450, 45);
     over.saveState();
    

    You have a saveState() operator, but you never use restoreState(). For every saveState(), you should have a restoreState(). Your saveState() is in the wrong position! It's as if you don't know what saveState() and restoreState() are about.

    Finally, you start counting a 0, but the first page is page 1, not page 0. Hence for (int i = 0; i < numberOfPages; i++) is wrong. It should be for (int i = 1; i <= numberOfPages; i++). This error is what causes over to be null.

    This is an attempt to fix your code:

    // create a Phrase with a certain font
    Font font = font(Font.NORMAL, 135, Color.RED);
    Phrase p = new Phrase("WATERMARK", font);
    // Create a reader and a stamper
    PdfReader reader = new PdfReader(getAsByteArray());
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    PdfStamper stamper = new PdfStamper(reader, outputStream);
    int numberOfPages= reader.getNumberOfPages();
    for (int i = 1; i <= numberOfPages; i++) {
         PdfContentByte over = stamper.getOverContent(i);
         over.saveState();
         PdfGState gs = new PdfGState();
         gs.setFillOpacity(0.5f);
         over.setGState(gs);
         ColumnText.showTextAligned(over, Element.ALIGN_CENTER, p, 330, 450, 45);
         over.restoreState();
    }