Part 1 and 2 of the web application demonstrated at JavaPolis, were examples of PDF documents generated from scratch containing data from a database. Whenever I need to write a Servlet producing PDF with iText, I start with a small standalone application first: a prototype that doesn’t involve a webserver. I recommend you to do the same: standalone applications are far more easy to debug, and you can focus on the iText functionality, avoiding the extra complexity of writing the Servlet, deploying it, and so forth. That’s also what I did for the examples we discussed on the filmfestival page.
For the first two examples extend the abstract class MakePdf. It has one member variable:
private FilmfestivalQuery query;
And one abstract method:
public abstract void writeTo(OutputStream out) throws DocumentException, SQLException;
Subclasses of this abstract class will set the member variable in their constructor and implement the writeTo() method, using the 5 steps we already discussed on a previous page).
The advantage of this method is that you can decide to write the PDF to a FileOutputStream in your standalone example, and to a ServletOutputStream or (to avoid some browser-related issue) a ByteArrayOutputStream in your Servlet.
The abstract class also has a lot of static final values we’re going to use in our examples: colors, dimensions, fixed text,...
As we already mentioned, the MakeList constructor set the query variable:
public MakeList(FilmfestivalQuery q) { this.query = q; }
We already used the FilmfestivalQuery class to make some simple examples. We construct an instance of this class in the main method:
public static void main(String[] args) { FilmfestivalDatabase database = new FilmfestivalDatabase(); try { database.open(); FilmfestivalQuery q = new FilmfestivalQuery( FilmfestivalDatabase.getDays(), false, // only press false, // no press false, // comp false, // eXploreZone true // alternative screenings ); FileOutputStream out = new FileOutputStream(PATH + "list.pdf"); MakePdf pdf = new MakeList(q); pdf.writeTo(out); } catch (Exception e) { e.printStackTrace(); } database.close(); }
The first parameter of the FilmfestivalQuery constructor is a List with String values referring to a date: “2006-10-06”, “2006-10-10”, and so on. The other parameters are booleans:
| true | false | |
|---|---|---|
| onlyPress | show press screenings only | show all screenings |
| noPress | omit all press screenings | show all screenings |
| comp | only show movies that are in competition | show all movies |
| eXploreZone | only show movies that are selected for the eXploreZone Award | show all movies |
| alter | show all screenings (also those on other days) | show current screenings only |
The business logic in the writeTo() method is pretty straightforward. We immediately recognize the 5 steps of PDF creation with iText:
public void writeTo(OutputStream out) throws DocumentException, SQLException { // step 1 Document document = new Document(); // step 2 PdfWriter.getInstance(document, out); // step 3 document.open(); // step 4 for (int i = 0; i < query.getNumberOfDays(); i++) { document.add(getTable(query.getDay(i), query.getMovies(i))); document.newPage(); } // step 5 document.close(); }
In step 4, we ask the query for the number of days that are involved, we use the method getDay to get a String representation of the day (in this case something like “2006-10-06”; but you could adapt the code to get a nicer date format). Method getMovies() returns a List with Movie objects. We will use this date and movie list to create a PdfPTable in method getTable():
The code of the method that creates the PdfPTable is similar to the code we wrote before to create a simple program overview. Nevertheless, we introduce some new concepts:
private static PdfPTable getTable(String day, java.util.List movies) throws DocumentException { // a 100% wide 1 column table PdfPTable table = new PdfPTable(1); table.setWidthPercentage(100f); // a header cell with black background and white font for the date Font f = new Font(); f.setColor(WHITE); PdfPCell cell = new PdfPCell(new Phrase(day, f)); cell.setBackgroundColor(BLACK); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); // nested tables for every movie for (Iterator i = movies.iterator(); i.hasNext(); ) { add((FilmfestivalMovie)i.next(), table); } // we're done for today ;-) return table; }
As you can see, we have set the width percentage of the table to 100 (the default is 80); and the master table has only 1 column. We loop over the movies in the list and add each movie to the master table with another method. As a matter of fact, each movie will be in a separate nested table.
In the add() method we construct a 3-column table, and we’ll add it as a nested table to the parent table. The first row will be a cell with colspan 3. As we’ll soon see, it contains yet another table (this example demonstrates the principle of deep nesting). The next row has an empty cell, a cell with the director(s) and one with the screening(s). These last two cell can contain a Paragraph or a List object depending on the number of director(s) and/or screening(s).
Note that this method introduces some more cell functionality: we can remove the borders of a cell, and tell the cell to use the ascender/descender of the content. This makes sure the content is positioned nicely inside the cell, by adapting the cell padding depending on the text of the first/last line and its font.
public static void add(FilmfestivalMovie movie, PdfPTable parenttable) throws DocumentException { // we construct a 3 column table PdfPTable table = new PdfPTable(3); table.setWidths(WIDTHS); // the first cell with the full title spans all the columns PdfPCell cell = new PdfPCell(); cell.addElement(fullTitle(movie)); cell.setColspan(3); cell.setBorder(PdfPCell.NO_BORDER); setColor(movie, cell); table.addCell(cell); cell = new PdfPCell(); cell.setBorder(PdfPCell.NO_BORDER); cell.setUseAscender(true); cell.setUseDescender(true); table.addCell(cell); cell = new PdfPCell(); cell.addElement(directors(movie)); cell.setBorder(PdfPCell.NO_BORDER); cell.setUseAscender(true); cell.setUseDescender(true); table.addCell(cell); cell = new PdfPCell(); cell.addElement(screenings(movie)); cell.setBorder(PdfPCell.NO_BORDER); cell.setUseAscender(true); cell.setUseDescender(true); table.addCell(cell); parenttable.addCell(table); }
Let’s have a look at the helper methods to add the full title, the directors and the screenings.
First we have the method fullTitle(FilmfestivalMovie movie); it returns an Element of type PdfPTable.
private static Element fullTitle(FilmfestivalMovie movie) throws DocumentException { // a table with 3 cells PdfPTable table = new PdfPTable(3); table.setWidths(INNER); table.setWidthPercentage(100); // the title(s) Paragraph p = new Paragraph(); p.add(new Phrase(movie.getTitle(), BOLD)); p.setLeading(16); // maybe an alternative title if (movie.getA_title().trim().length() > 0) { p.add(new Phrase(" (" + movie.getA_title() + ")")); } PdfPCell cell = new PdfPCell(); cell.addElement(p); cell.setBorder(PdfPCell.NO_BORDER); cell.setUseAscender(true); cell.setUseDescender(true); table.addCell(cell); // eXploreZone? cell = new PdfPCell(); cell.setBorder(PdfPCell.NO_BORDER); if (movie.isExplorezone()) { cell.setBackgroundColor(WHITE); cell.setUseAscender(true); cell.setUseDescender(true); cell.addElement(new Paragraph("eXplore")); } table.addCell(cell); // Duration / shortfilm cell = new PdfPCell(); cell.setBorder(PdfPCell.NO_BORDER); cell.setBackgroundColor(WHITE); cell.setUseAscender(true); cell.setUseDescender(true); StringBuffer buf = new StringBuffer(); buf.append(movie.getDuration()); buf.append('\''); if (movie.getExtra() > 0) { buf.append(" + KF"); buf.append(movie.getExtra()); buf.append('\''); } p = new Paragraph(buf.toString()); p.setAlignment(Element.ALIGN_CENTER); cell.addElement(p); table.addCell(cell); return table; }
As a matter of fact, fullTitle() adds more than just the title to the table:
The method setColor uses yet another Movie method: getCategory_id():
private static void setColor(FilmfestivalMovie movie, PdfPCell cell) { cell.setBackgroundColor(COLOR[movie.getCategory_id()]); }
The method movie.getDirectors() returns a list of String-objects. If there is no director, we create a Paragraph with various directors (if it’s a screening with a selection of short films by different directors, I don’t always add all the names to the database). The method also returns a Paragraph if there’s only one director, but if there are more than 1, a List is returned.
private static Element directors(FilmfestivalMovie movie) { ArrayList directors = movie.getDirectors(); if (directors.size() == 0) { Paragraph p = new Paragraph("various directors"); p.setLeading(16); return p; } if (directors.size() == 1) { Paragraph p = new Paragraph((String)directors.get(0)); p.setLeading(16); return p; } List list = new List(List.UNORDERED, 10); ListItem li; for (Iterator i = directors.iterator(); i.hasNext(); ) { li = new ListItem((String)i.next()); li.setLeading(16); list.add(li); } return list; }
Finally, we have the methods movie.getScreening() and movie.getScreenings(); the former method returns a single FilmfestivalScreening object; the latter returns an empty list if you alter == false and a list of all screenings (except for the one returned by getScreening()) if alter == true.
If we don’t need all the screenings, we just return a Paragraph. Otherwise, a List is returned with the current screening as the first item.
private static Element screenings(FilmfestivalMovie movie) {
// the specific screening
Paragraph p = new Paragraph(movie.getScreening().toString(), BOLDSMALL);
p.setLeading(16);
ArrayList screenings = movie.getScreenings();
if (screenings.size() == 0) {
return p;
}
// the alternative screenings
List list = new List(List.UNORDERED, 10);
list.add(new ListItem(p));
ListItem li;
FilmfestivalScreening screening;
for (Iterator i = screenings.iterator(); i.hasNext(); ) {
screening = (FilmfestivalScreening) i.next();
li = new ListItem(screening.toString(), SMALL);
li.setLeading(12);
list.add(li);
}
return list;
}
Put all these methods together, compile and execute the code, and you’ll get the PDF document list.pdf as a result. Try changing the parameters of the FilmfestivalQuery-constructor, and see what happens.
In this example, we have learned more about a series of basic building blocks in iText. For more info: see part 2 of the book iText in Action. Note that this example was integrated into a Servlet deployed on itext.ugent.be. Maybe I’ll explain how it’s done in another article.
Meanwhile, you know more about the FilmfestivalMovie and FilmfestivalScreening objects, and you’ll be able to use this knowledge in the calendar example.