Dealing with large tables (iText 5)

The table in figure 4.11 has a header with a date. If you download the example and generate the PDF on your own computer, you’ll see that the table with all the movies spans more than one page for most of the days. The table is nicely split, but unfortunately the header isn’t repeated. In this section, you’ll fix this, and also add a footer while you’re at it.

Repeating headers and footers

Figure 4.12 is another overview of movie screenings on a specific day. The date is shown in the first row. The second row consists of headers that describe the content of the columns: Location, Time, Run Length, Title, and so on. The same information is also added as a footer.

Repeating headers and footers

Figure 4.12 Repeating headers and footers

Listing 4.18 HeaderFooter1.java

Listing 4.18 HeaderFooter1.java


This may seem strange, adding the footer before you start adding any other content, but this is the twist: first you use the setHeaderRows() method to tell iText how many rows are part of the header, and you add the row count of the footer. In listing 4.18, you set the value to 3 ©. Then you use the setFooterRows() method to tell iText how many of the header rows are actually footer rows. If there are none, there’s no need to do this, but in this example you set the value to 1 ©.

The result is that the first two rows will be added first, followed by the real content of the table. When a new page is necessary, or when there’s no more real data, the third row—the footer row—will be added. If the table continues on another page, the header is repeated. And so is the footer at the end of the table, unless you’ve used setSkipLastFooter(true). This method is useful if you want to add a footer saying, "This table continues on the next page." It’s obvious that you don’t want to add this text if there is no more data in the table. There’s also a setSkipFirstHeader() method for a similar reason: if you want the header to say something like, "This is the continuation of the table on the previous page."

All the screenshots so far have showed only one page, or part of a page. But how does iText split a table that runs over to a new page?

Splitting tables

What do you want iText to do if a row doesn’t fit on the page? Do you want iText to start the row on a new page? Or do you want iText to add as much data from that row on the current page, and add the rest to the next page? Both options are possible, and they’re demonstrated in figure 4.13.

To get the effect in figure 4.12, you add three rows: a black row with the date O, then a light gray row twice © (once for the header and once for the footer).

Different ways to split a table

Figure 4.13 Different ways to split a table

In the first example, the second Terminator movie doesn’t fit on the upper page. The complete row is forwarded to the next page. This is the default behavior of iText.

In the lower example, most of the data is added to the current page, but one country is printed on the next page.

Listing 4.19 HeaderFooter2.java

Listing 4.19 HeaderFooter2.java

The HeaderFooter2 example extends the HeaderFooter1 example, and it reuses the getTable() method, so the table is constructed in exactly the same way. But with the setSplitLate() method, you decide whether iText should wait to split the cells of the first row that doesn’t fit the page.

By default, iText only splits rows that are forwarded to the next page but that still don’t fit because the row height exceeds the available page height. You can avoid this by using the following line of code:

tmp17C187_thumb

This is a dangerous line, because now not one row will be split. Rows that are too high to fit on a page will be dropped from the table!

Dealing with large tables isn’t only about repeating headers and footers, or about splitting tables. It’s also about managing the memory that is used by a table, making sure that you don’t consume more memory than is available in the Java Virtual Machine (JVM).

Memory management for LargeElement implementations

In topic 2, you learned that the topic and Section objects implement the LargeElement interface. Just like PdfPTable, you risk consuming a lot of memory adding many Elements to a LargeElement before adding the LargeElement to a Document.

When an object is added to a Document, you can decide to make it eligible for garbage collection (removal from memory). The problem with objects such as PdfPTable and topic is that you can’t add the object to the Document until you’ve completed it; or can you? In section 3.2, you constructed a large table that consisted of several small tables that were glued to each other, but that doesn’t work if you want repeating headers and footers that are drawn in the correct place automatically. What you need is to write part of the table to the PdfWriter and its corresponding OutputStream, then find a way to flush that part from memory. You should be able to do this without unwanted side effects affecting the headers, footers, and, in the case of topics, indentations, titles, and so on.

The LargeElement interface was created to help you solve this problem. Classes implementing this interface need to implement three methods: setComplete(), isComplete(), and flushContent(). The isComplete() and flushContent() methods are used by iText internally. The only method that is important for you is the set-Complete() method.

Listing 4.20 MemoryTests.java

Listing 4.20 MemoryTests.java

If you know you’ll be adding a LargeElement, and you don’t plan to reuse it, you have to inform iText that you haven’t finished adding content. In listing 4.20, you’re adding the table to the document every ten movies, but you’ve told iText that it isn’t complete yet. Internally, iText will use the method isComplete(). If this method returns true, the flushContent() method will be called.

In previous examples, all the rows were added to the table and they were kept in memory until the table was added to the document. In listing 4.20, rows are written to the PdfWriter at an earlier stage. Once the cell and row objects are rendered to PDF, they’re deleted, so that the JVM can remove them from the memory. Once you’ve finished adding rows, you flag the table as completed, and you add the remaining rows to the document.

The checkpoints and resetMaximum() methods in listing 4.20 write information about the memory use to a text file. By inspecting this file, you can discover that a table with the information and posters for 120 movies consumes about 4 MB. If you add the table to the document before it’s completed (for instance, every 10 movies), the maximum memory needed by the JVM amounts to about 160 KB. This is a huge difference; using the LargeElement interface can help you fine-tune your application if you’re dealing with a large volume of data.

The setComplete() method is only useful if you’re adding the table with the Doc-ument.add() method. In the next section, you’ll add a PdfPTable to the direct content. This will give you more power, but also more responsibility: you’ll need to tell iText where you want to position every part of the table.

Next post:

Previous post: