Adding content with PdfStamper Part 1 (iText 5)

Up until now, we’ve created new documents using the five steps in the iText document-creation process. In this topic we’ll add content to an existing document using PdfStamper.

PdfStamper uses a different mechanism, as demonstrated in the manipulateWith-Stamper() method.

Listing 6.11 SelectPages.java

Listing 6.11 SelectPages.java

You’ve already seen part of this example in listing 6.3. It’s an example that creates a new PDF document containing only a selection of pages from the original document. In O, you create a PdfReader that will read the 8 pages of the timetable PDF, but you immediately tell the reader that you’re only interested in pages 4 to 8. In ©, you create a PdfStamper object. As soon as you close the stamper, a new document will be created. It will contain only 5 pages. You can add content between the constructor and the close() method.

Adding content at absolute positions

Let’s start with the "Hello World" examples with paper size Letter in landscape format from topic 1. There were two versions of this example. Let’s add the words "Hello people!"


Listing 6.12 StampText.java

Listing 6.12 StampText.java Adding text to an existing document

Figure 6.7 Adding text to an existing document

The getOverContent() method is similar to the getDirectContent() method discussed in topic 3. It returns a PdfContentByte object that allows you to write to a new layer that goes on top of the existing content of the page you choose. There’s also a getUnderContent() method, which is the equivalent of getDirectContentUnder().

NOTE The methods getOverContent() and getUnderContent() give you the option to write to the direct content on a layer that goes on top of or below the existing content. They don’t give you access to the layer with the existing content. You can’t use these methods to replace existing content, nor to complete it. It’s not possible to say: "I want to add the words ‘Hello people!’ after the words ‘Hello World’." You can only add those words to the layer above or below the existing content at an absolute position whose coordinates you know.

The media box of the file that was used as the basis for hello3.pdf was 792 pt x 612 pt. I’ve added the extra text at the coordinates (36,54 0). That’s near the top-left corner. The file used as the basis for hello1.pdf had a media box measuring 612 pt x 792 pt, but the page had a rotation of 90 degrees. The difference between these two ways of creating a page in landscape is made transparent: iText took the rotation into account and rotated the coordinate system. If you don’t want this, you can tell iText to ignore the fact that the page is rotated. That’s what happened with hello2.pdf in figure 6.7.

In the next code snippet, the extra text was added at the same coordinates as in listing 6.12, but the rotation of the page isn’t taken into account. This is prevented with the setRotateContents() method.

tmp89-9_thumb[1]

We could now repeat everything that we covered in topic 3, and explain how to draw lines, shapes, and text to the PdfContentByte layers obtained with getOverCon-tent() and getUnderContent(), but it’s a better idea to look at practical examples.

Creating a PDF in multiple passes

In section 5.4.2, we solved the "page X of Y" problem by using page events and a PdfTemplate object. One of the problems inherent to this solution is that you don’t know the number of pages when you create and position the placeholder. You create a small canvas up front, but you can only add the page number once the document is completely finished. You don’t know in advance how much space will be needed to draw this number. Will the document eventually have 9 pages or 9999? You could guess the number of digits beforehand and reserve enough space for them accordingly, but you won’t always be able to make the right guess.

That’s why you might consider an alternative way to add page numbers. The document shown in figure 6.8 is made in two passes.

In the first pass, the document is created without a header. The header, and—if necessary—a footer and a watermark, can be added in a second pass. Note that it isn’t necessary to create two files on disk. If the file size isn’t huge, and the memory available in your JVM allows it, you can easily keep the file created during the first pass in memory.

Adding a page X of Y header to an existing document

Figure 6.8 Adding a page X of Y header to an existing document

tmp89-11_thumb[1]

Instead of writing the document to a FileOutputStream in the first pass, you keep the file in memory using a ByteArrayOutputStream (see section 1.3.2). In the second pass, you use the bytes from this OutputStream to create a PdfReader instance.

FAQ PdfStamper always creates a new PDF file, but how can I manipulate the existing file? You can’t use the same physical file used by PdfReader to create a FileOutputStream for PdfStamper. Common sense tells us that changing a file while you’re still reading it risks corrupting the file. There are different ways to work around this. Some applications read a file into memory before changing it; you could read the original file into a byte array and create a PdfReader object as demonstrated in listing 6.13. Other applications work with temporary files; once you’ve finished "stamping," you could replace the original file with the new one. Finally, you could also create the new file in memory using a ByteArrayOutputStream, and then overwrite the original file using these bytes. The "best choice" depends on the context. As a rule of thumb, I prefer temporary files for applications that run on the desktop; in a web environment, I create all files in memory.

In section 6.2, you added an existing PDF as the background of a newly created PDF using page events. But suppose you’re given an existing PDF, and you need to add company stationery after the fact. That’s what the next example is about.

Adding company stationery to an existing document

Figure 6.9 looks very similar to figure 6.4, but now you have an existing file, original.pdf, to which you want to add the file stationary.pdf, with the file stamped_stationery.pdf being the result.

To achieve this, you need to import a page from one PDF and add it as the background to another PDF.

Adding stationery to an existing document

Figure 6.9 Adding stationery to an existing document

Listing 6.15 StampStationery.java

Listing 6.15 StampStationery.java

Here you obtain a PdfImportedPage object from PdfStamper with the getImported-Page() method. This method writes the resources necessary to render the imported page to the writer associated with the stamper.

This technique is often used to add watermarks to existing document. You can easily adapt the example to add an Image with the addImage() method instead of an imported page. All the methods from topic 3 are at your disposal.

NOTE This example combines PdfStamper with PdfImportedPage. All the interactive features present in the document that’s being manipulated with PdfStamper are preserved, but the interactive features that were present on the page that’s being imported are lost.

As discussed in the introduction of this topic, PDF isn’t a format that can be used for word processing. You can’t insert a couple of lines between two existing paragraphs on a page. You can only insert complete pages. That’s what you’re going to do in the next example.

Inserting pages into an existing document

In section 5.2.4, you were faced with a problem concerning the TOC of a document. You were only able to create the table of contents (TOC) once the document was finished. But you wanted to display the TOC before the rest of the content, not after. In listing 5.12, you reordered the pages.

Listing 6.16 offers an alternative solution: you could create the PDF in two passes and add the TOC in the second pass by inserting extra pages. You could, for instance, create a ColumnText object containing a series of Paragraphs, then you add these Paragraphs to a number of pages that are inserted into the existing document.

Listing 6.16 InsertPages.java

Listing 6.16 InsertPages.java

There’s a significant difference between what you did in topic 3 and how you create the ColumnText object here in O. Normally, you have to pass a PdfContentByte object with the constructor. In this case, you don’t have a reference to the direct content yet: you use null as the parameter. You wait to set the canvas until ©. In G you try to fit the content inside a rectangle. If the content doesn’t fit on page one, you insert a second page, and so on.

Listing 6.17 InsertPages.java

Listing 6.17 InsertPages.java

There’s nothing new in the listing. It’s almost identical to what you did in listing 6.11, but now you’re using selectPages() to reorder the pages. The document created by PdfStamper will start on page 3 of the original document, go on until page 41, and then add pages 1 and 2 at the end of the document.

These are practical examples that can be used to solve common problems with the help of PdfStamper, and using the concept of writing to the direct content as discussed in topic 3. In the next section, we’ll look at a totally different concept. We’ll talk about interactive forms.

Next post:

Previous post: