Vladimir Safonov
Vladimir Safonov

Reputation: 67

Background image itextpdf 5.5

I'm using itextpdf 5.5, can't change it to 7. I have the problem with background image. I have a document (text and tables) without stamp and I want to add stamp to it.

This is how I download existing doc.

PdfReader pdfReader = new PdfReader("/doc.pdf");  
PdfImportedPage page = writer.getImportedPage(pdfReader, 1);
PdfContentByte pcb = writer.getDirectContent();
pcb.addTemplate(page, 0,0);

And this is how I download stamp image and add it to my doc.

PdfContentByte canvas = writer.getDirectContentUnder();
URL resource = getClass().getResource(getStamp());
Image background = new Jpeg(resource);
background.scaleToFit(463F, 132F);
background.setAbsolutePosition(275F, 100F);
canvas.addImage(background);

But when I download my document - I don't see the stamp. I tried to change getDirectContent() to getDirectContentUnder() when I download my doc but this leads to the opposite situation - my stamp isn't in background.

My first doc is generated this way.

 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
 Document document = new Document(PageSize.A4);
   try {
        PdfWriter writer = PdfWriter.getInstance(document, outputStream);
        document.open();
        Paragraph title = new Paragraph(formatUtil.msg("my.header"), fontBold);
        title.setAlignment(Element.ALIGN_CENTER);
        document.add(title);
       
        Template tmpl = fmConfig.getConfiguration().getTemplate("template.ftl");
        Map<String, Object> params = new HashMap<>();
        StringWriter writer = new StringWriter();
        params.put("param", "param");
        tmpl.process(params, writer);
        document.add(new Paragraph(writer.toString(), fontCommon));
        PdfPTable table = new PdfPTable(2);
        document.add(table);
        PdfContentByte canvas = writer.getDirectContentUnder();
        Image background = new Jpeg(getClass().getResource("background.jpg"));
        background.scaleAbsolute(PageSize.A4);
        background.setAbsolutePosition(0,0);
        canvas.addImage(background);
        } finally {
            if (document.isOpen()) {
                document.close();
            }
        }

Upvotes: 0

Views: 965

Answers (1)

mkl
mkl

Reputation: 95898

In comments it became clear that the task was to add some content (a bitmap image) to a PDF so that it is over the background (another bitmap image) added to the UnderContent during generation and under the text in the original DirectContent.

iText does not contain high-level code for such content manipulation. While the structure of iText generated PDFs would allow for such code, PDFs generated or manipulated by other PDF libraries may have a different structure; the structure in iText generated PDFs may even be changed during post-processing using other libraries. Thus, it is understandable that no high-level feature for this is provided by iText.

To implement the task nonetheless, therefore, we have to base our code on lower level iText APIs. In this context we can make use of the PdfContentStreamEditor helper class from this answer which already abstracts some details away. (That question originally is about iTextSharp for C# but further down in the answer Java versions of the code also are provided.)

In detail, we extend the PdfContentStreamEditor to remove the former UnderContent (and provide it as list of instructions). Now we can in a first step apply this editor to your file and in a second step add this former UnderContent plus an image over it to the intermediary file. (This could also be done in a single step but that would require a more complex, less maintainable editor class.)

First the new UnderContentRemover content stream editor class:

public class UnderContentRemover extends PdfContentStreamEditor {
    /**
     * Clears state of {@link UnderContentRemover}, in particular
     * the collected content. Use this if you use this instance for
     * multiple edit runs.
     */
    public void clear() {
        afterUnderContent = false;
        underContent.clear();
        depth = 0;
    }

    /**
     * Retrieves the collected UnderContent instructions
     */
    public List<List<PdfObject>> getUnderContent() {
        return new ArrayList<List<PdfObject>>(underContent);
    }

    /**
     * Adds the given instructions (which may previously have been
     * retrieved using {@link #getUnderContent()}) to the given
     * {@link PdfContentByte} instance.
     */
    public static void write (PdfContentByte canvas, List<List<PdfObject>> operations) throws IOException {
        for (List<PdfObject> operands : operations) {
            int index = 0;

            for (PdfObject object : operands) {
                object.toPdf(canvas.getPdfWriter(), canvas.getInternalBuffer());
                canvas.getInternalBuffer().append(operands.size() > ++index ? (byte) ' ' : (byte) '\n');
            }
        }
    }

    protected void write(PdfContentStreamProcessor processor, PdfLiteral operator, List<PdfObject> operands) throws IOException {
        String operatorString = operator.toString();
        if (afterUnderContent) {
            super.write(processor, operator, operands);
            return;
        } else if ("q".equals(operatorString)) {
            depth++;
        } else if ("Q".equals(operatorString)) {
            depth--;
            if (depth < 1)
                afterUnderContent = true;
        } else if (depth == 0) {
            afterUnderContent = true;
            super.write(processor, operator, operands);
            return;
        }
        underContent.add(new ArrayList<>(operands));
    }

    boolean afterUnderContent = false;
    List<List<PdfObject>> underContent = new ArrayList<>();
    int depth = 0;
}

(UnderContentRemover)

As you see, its write method stores the leading instructions forwarded to it in the underContent list until it finds the restore-graphics-state (Q) instruction matching the initial save-graphics-state instruction (q). After that it instead forwards all further instructions to the parent write implementation which writes them to the edited page content.

We can use this for the task at hand as follows:

PdfReader pdfReader = new PdfReader(YOUR_DOCUMENT);
List<List<List<PdfObject>>> underContentByPage = new ArrayList<>();
byte[] sourceWithoutUnderContent = null;
try (   ByteArrayOutputStream outputStream = new ByteArrayOutputStream()    ) {
    PdfStamper pdfStamper = new PdfStamper(pdfReader, outputStream);

    UnderContentRemover underContentRemover = new UnderContentRemover();
    for (int i = 1; i <= pdfReader.getNumberOfPages(); i++) {
        underContentRemover.clear();
        underContentRemover.editPage(pdfStamper, i);
        underContentByPage.add(underContentRemover.getUnderContent());
    }

    pdfStamper.close();
    pdfReader.close();

    sourceWithoutUnderContent = outputStream.toByteArray();
}

Image background = YOUR_IMAGE_TO_ADD_INBETWEEN;
background.scaleToFit(463F, 132F);
background.setAbsolutePosition(275F, 100F);

pdfReader = new PdfReader(sourceWithoutUnderContent);
byte[] sourceWithStampInbetween = null;
try (   ByteArrayOutputStream outputStream = new ByteArrayOutputStream()    ) {
    PdfStamper pdfStamper = new PdfStamper(pdfReader, outputStream);

    for (int i = 1; i <= pdfReader.getNumberOfPages(); i++) {
        PdfContentByte canvas = pdfStamper.getUnderContent(i);
        UnderContentRemover.write(canvas, underContentByPage.get(i-1));
        canvas.addImage(background);
    }

    pdfStamper.close();
    pdfReader.close();

    sourceWithStampInbetween = outputStream.toByteArray();
}
Files.write(new File("PdfLikeVladimirSafonov-WithStampInbetween.pdf").toPath(), sourceWithStampInbetween);

(AddImageInBetween test testForVladimirSafonov)

Upvotes: 1

Related Questions