Decorating tables using table and cell events (iText 5)

Two methods that are present in the API documentation for PdfPTable and PdfPCell were overlooked in topic 4: PdfPTable.setTableEvent() and PdfPCell.setCell-Event(). The former method expects an implementation of the PdfPTableEvent interface as its parameter; the latter expects a PdfPCellEvent implementation. These interfaces can be used to define a custom layout for tables and cells; for instance, a custom background or custom borders for a table and its cells. You’ll use these events on a table you created in the previous topic.

Implementing the PdfPTableEvent interface

Suppose you want to add a background color to every other row of a table, as shown in figure 5.1.

Table with alternating row backgrounds

Figure 5.1 Table with alternating row backgrounds

One way to achieve this would be to change the background color of the default cell for each row so that odd rows don’t have a background color, and the background of even rows is yellow. This would work, but you must consider a possible side effect. If you look at figure 5.1, you’ll see that you’re able to fit 27 rows on one page, not including header and footer rows. The table continues on the next page. If you alternate the background color of the default cell, the background of row 28 in the table (an even row) will be colored, in spite of the fact that it’s row 1 on the next page (an odd row). Maybe you don’t want this—maybe you want the first row after the header to be a row without a background color. If that’s the case, you should implement the tableLay-out() method and use a table event.


Listing 5.1 AlternatingBackground.java

Listing 5.1 AlternatingBackground.java

Take a look at the parameters of the tableLayout() method:table—The PdfPTable object to which the event is added. Don’t use this method to change the contents of the table; the table has already been rendered at the moment the tableLayout() method is invoked. Consider this object to be read-only. ■ widths—A two-dimensional array of float values. A table with m rows and n columns results in an array with a maximum dimension of m x (n + 1). The X coordinate of the left border of the first cell in row ris widths[r][0]; the right border of the last cell in this row is widths[r][n + 1], provided that all cells in the row have colspan 1. Setting a different colspan can result in a lower number of elements in the row array. This is the case for the first row in figure 5.1 (the header row). The array widths[0] has only two elements: widths[0][0] is the X coordinate of the left border of the table; widths[0][1] is the X coordinate of the right border.

■ heights—An array with float values. You can see 30 rows in figure 5.1. The heights array passed to the table event when this part of the table is drawn will contain 31 values. These values are the Y coordinates of the borders of the rows: heights[0] is the Ycoordinate of the upper border of the table; heights[30] is the Y coordinate of the lower border.

■ headerRows—An int with the same value as table.getHeaderRows(). If you also have footer rows, you should use O to retrieve the correct number of header and footer rows. This parameter dates from the time when the footer row functionality wasn’t available yet.

rowStart—This int value will always be 0 if you add the table with docu-ment.add(). If you use writeSelectedRows(), it will be identical to the parameter with the same name passed to this method: the row number of the first row that is drawn.

■ canvases—An array of PdfContentByte objects. There are four of them, and you encountered them in section 4.4.1: PdfPtable.BASECANVAS, PdfPtable.BACK-GROUNDCANVAS, PdfPtable.LINECANVAS, and PdfPtable.TEXTCANVAS.

In listing 5.1 you loop over the rows, starting with the second row after the header, in steps of two rows. Every row can have a different number of columns. Using the widths and the heights arrays, you define a rectangle encompassing the complete row. Finally, you draw a yellow rectangle to the BASECANVAS. You chose the base canvas because you don’t want to cover background colors that may be defined for some cells. There aren’t any cells with backgrounds in this example, except in the header and footer rows, but this way you can easily reuse this code for other tables.

For the event to take effect, you need to use the setTableEvent() method.

Listing 5.2 AlternatingBackground.java

Listing 5.2 AlternatingBackground.java

Thanks to the information that is passed to the tableLayout() method, you can write text and shapes to the direct content to change the appearance of a table and its cells. A similar mechanism exists for PdfPCell objects.

Implementing the PdfPCellEvent interface

In figure 5.1, you list a number of screenings and include the run length of each movie. Suppose you wanted to add visual information that is identical to the textual info, but that can be read in a glance. This is done in figure 5.2: by looking at the background of the cell with the duration, you immediately get an indication of the run length of the movie.

Cells with custom background and extra info added using cell events

Figure 5.2 Cells with custom background and extra info added using cell events

The width of column 3 in figure 5.2 corresponds to 240 minutes. That’s 100 percent. For a two-hour movie (50 percent of four hours), you draw a rectangle in the background that takes half the width of that cell. If a movie has a duration less than 90 minutes, you draw a green rectangle. Movies with a duration greater than 120 minutes are drawn in dark red. Movies with a run length between 90 and 120 minutes get an orange rectangle. All of this is done in the cellLayout() implementation.

Listing 5.3 RunLengthEvent.java

Listing 5.3 RunLengthEvent.javaListing 5.3 RunLengthEvent.java

Observe that the cellLayout() method is a lot easier to understand than the table-Layout() method. There are only three parameters:

■ cell—The PdfPCell object to which the event is added. This is just for readonly purposes! Do not try to change the content of this cell—it won’t have any effect. Once the method of the cell event is triggered, the cell has already been rendered.

■ rect—The Rectangle object defining the borders of the cell.

■ canvas—An array of PdfContentByte objects with the same elements as described in sections 4.4.1 and 5.1.1.

Suppose you’re planning to project the extended version of the Lord of the Rings trilogy. The run length of part 3 is 250 minutes, pauses not included, so the background of the duration cell for The Return of the King will exceed the cell borders. By using cell events, you can extend the background color beyond the cell borders.

NOTE The layout methods give you access to direct content layers of the complete page, along with coordinates that are helpful if you want to know the position of the table or cell that was added. It’s up to you to use these coordinates, or not. You can’t change the content and the appearance defined for the original table or cell objects. These objects are already rendered to the page when the layout method is called.

Cell events are declared to a PdfPCell using the setCellEvent() method.

Listing 5.4 RunLengthEvent.java

Listing 5.4 RunLengthEvent.java

In listing 5.4, you use the copy constructor of PdfPCell to create a new cell with the same characteristics as the default cell of the table. You use the setPhrase() method to add content in text mode—this corresponds to the ColumnText.setText() method. Before you add the cell to the table, you add the cell events. First the Run-Length event, with the behavior explained in listing 5.3, then an event named press. This is an instance of PressPreview, a cell event that adds the words "PRESS PREVIEW" if the screening is a press preview.

NOTE Events are cumulative. The PressPreview event doesn’t replace the RunLength event. The layout methods of both classes will be called if the screening is a press preview. If you want to replace an existing cell event by a new one, you need to remove the old event first. This can be done by setting the event to null, like this: cell.setCellEvent(null);.

Here is the PressPreview class.

Listing 5.5 RunLengthEvent.java

Listing 5.5 RunLengthEvent.java

Many things that can be done with table events can be done in an easier way with cell events. But cell events can never replace all the table events you need. Usually, you’ll combine the power of table events with the ease of use of cell events.

Combining table and cell events

The table in figure 5.3 mimics the cell spacing you get from using the HTML cellspacing attribute for the <table> tag. There’s more than one way to achieve this look.

You need a table event to draw the outer border of the complete table, but you can choose what type of event to use to draw the cell borders.

MIMICKING HTML CELL SPACING

You can either use the widths and heights arrays from the tableLayout() method to draw these inner borders. Or you can use a cell event for each cell, in which case you get the coordinates of the border as a Rectangle object. Listing 5.6 combines table and cell events.

Mimicking cell spacing using cell and table events

Figure 5.3 Mimicking cell spacing using cell and table events

Listing 5.6 PressPreviews.java

Listing 5.6 PressPreviews.javaListing 5.6 PressPreviews.java

Note that you’re setting the cell event for the default cell so the behavior is valid for all the cells of the table in this particular case.

In the examples so far in this topic, you’ve used table and cell events for PdfPTable objects that were added with document.add(). This functionality also works if you write a table to the direct content using the writeSelectedRows() method.

TABLE AND CELL EVENTS AND WRITESELECTEDROWS()

Figure 5.4 shows a calendar sheet created in almost the same way as the calendar you made in the previous topic (see figure 4.14). The PdfPTable with the information about the month was added at an absolute position.

The only difference between the two examples is the style used for the table and its cells. In the previous topic, you used standard PdfPTable and PdfPCell methods. In this example, you’ll use table and cell events to obtain special effects, such as rounded corners. You’ll use TableBackground, CellBackground, and RoundRectangle.

 A variation on the calendar example, now with rounded corners

Figure 5.4 A variation on the calendar example, now with rounded corners

Listing 5.7 PdfCalendar.java

Listing 5.7 PdfCalendar.java

Listing 5.7 PdfCalendar.java

Listing 5.7 PdfCalendar.java

After creating the table, you set the table event to draw the background of the table, and you make sure the default cells get a rounded rectangle as their border. O doesn’t apply to the cell with the month ©. The getMonthCell() method returns a PdfPCell object with the name of the month. O also doesn’t apply to the cells created with getDayCell(). These cells get a white background with rounded corners ©. Sundays and special days (holidays) get a colored border ©.

There’s a similar mechanism that allows you to write custom functionality for Chunk, Paragraph, and topic and Section objects. The layout methods to achieve this are bundled in the PdfPageEvent interface.

Next post:

Previous post: