0

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();
            }
        }
  • Have you checked whether the content stream instructions in your document start by first filling a rectangle covering the whole page with white? That is not uncommon. – mkl Feb 24 '21 at 14:51
  • @mkl the first doc is on "firm blank", it is an almost fully white jpg with a firm name on it. It may be important. Is it a kind of covering the whole page with white? Or mb I have to check smth else – Vladimir Safonov Feb 24 '21 at 15:00
  • If your document contain bitmap images drawn over the full page, they, too, will cover everything underneath unless they use transparency. JPEG, at least as implemented in iText, does not support transparency. – mkl Feb 24 '21 at 15:15
  • Then the asnwer to your first question is "yes". So, what should I do to achieve my goal? – Vladimir Safonov Feb 24 '21 at 15:18
  • @mkl before I started working at iText Software, I did some work with various image formats (hand-encoding bitmap images? done that!) and I can tell you this: the JPEG image format doesn't support transparency *at all*. The only thing you can do is have a flat color background that would blend into your page: eg. if your page background is white, then have JPGs created with a white background. And even then, due to JPEG compression artifacts, you might get noise. – Amedee Van Gasse Feb 24 '21 at 15:29
  • JPEG 2000 on the other hand (`.jp2`/`.j2k`) does support transparancy, and at least for iText 7, JPEG 2000 is supported - see https://kb.itextpdf.com/home/it7kb/faq/which-image-types-are-supported-by-itext – Amedee Van Gasse Feb 24 '21 at 15:31
  • @AmedeeVanGasse Yes, the JPEG format does not feature transparency. But there appear to be some hacks to give JPEGs transparency in certain contexts, whether by extending the format somewhat or by external means, at least in discussions about some questions here on SO that came up time and time again. So all I wanted to express by "at least as implemented in iText" is that iText supports no such hack; I didn't mean to imply that iText is implementing JPEG incompletely in this regard. – mkl Feb 24 '21 at 15:41
  • It seems that the problem is that text from my doc and image from my doc are both on the same layer (DirectContent or DirectContentUnder). If I manage somehow do divide them, I will be able to place my stamp in a baground. But for now I have no ideas how to place all text from PdfImportedPage to DirectContent and images to DirectContentUnder. – Vladimir Safonov Feb 24 '21 at 15:41
  • Image and text can be drawn in any order, so there may be text drawn over some image which in turn is drawn over some text which again is drawn over some image etc, and this all may well be done by design. Moving all text above all images may change the appearance considerably. Are you able to control the input documents? In particular cause them to _not_ include that background image which you can add just as well in your code.. – mkl Feb 24 '21 at 15:55
  • Other than that I'd propose you try to apply your "background" as a somewhat transparent foreground. The effect will of course differ somewhat from an actual background. – mkl Feb 24 '21 at 15:57
  • @mkl I can, but this feature has to work with old versions of the documents. And all of them have this image. But I can ensure, that there is the only one image behind all text. And dividing them is the decision. – Vladimir Safonov Feb 24 '21 at 16:00
  • Can you share an example? Or can you tell how exactly that image is stored in your source files? Immediately in the page content? In some form XObject? In some pattern? Inlined or as image resource? Or is it even sometimes one way, sometimes another? – mkl Feb 24 '21 at 18:58
  • @mkl I have updated my question. There is example now how the first doc is created. – Vladimir Safonov Feb 25 '21 at 07:40
  • Ok, a single bitmap is added, and it is added immediately to the page content. And you want essentially to have that image as background, then your new stamp, and then the text from the original PDF on the top, right? Is it ok if the stamp covers the background in the whole 463×132 rectangle you scale the stamp to fit? Or shall it somehow blend with the background image? – mkl Feb 25 '21 at 15:14
  • Use this exemple : https://stackoverflow.com/a/5673504/4017037 – stacky Feb 25 '21 at 16:02
  • @stacky *"Use this exemple :"* - How should that example help? Vladimir made clear in the comments that what he actually needs is to put his stamp issue _between_ the existing background and the existing text, not in the back of it all. – mkl Feb 25 '21 at 17:51
  • @mkl, yes, I want to have an image from original file as a background, then stamp, then text. And yes, it will be ok if my stamp covers the background image in the whole rectangle. – Vladimir Safonov Feb 25 '21 at 19:54
  • @VladimirSafonov Did my answer solve your problem? Or do you still have questions? – mkl Mar 19 '21 at 16:59

1 Answers1

1

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)

mkl
  • 90,588
  • 15
  • 125
  • 265