Home / Articles / Tools of the Trade, Part 1: Creating PDF documents with iText

Tools of the Trade, Part 1: Creating PDF documents with iText

Article Description

Have you ever wanted to create your own PDF documents without having to first shell out money for PDF-creation software? Good news: You can create those documents with nothing more than Java and iText. This freely available Java library provides the classes for easily constructing PDF documents and adding various kinds of content (such as paragraphs, lists, tables, and images) to those documents. This article by Jeff Friesen, the first in a three-part series that explores useful open-source tools, introduces you to iText and shows you how to create many interesting PDF documents without having to first spend money on PDF-creation software.

Class Tour

iText's many classes and interfaces organize into the main package com.lowagie.text and several subpackages corresponding to different kinds of document writers. For example, subpackage com.lowagie.text.pdf corresponds to the PDF document writer. This section tours several classes located in these two packages. Where appropriate, small applications are presented to demonstrate class usage.

Document

The com.lowagie.text.Document class describes a generic document: a container for various kinds of content and attributes (such as margins). The document is generic because its content is independent of any syntax (such as PDF syntax or RTF syntax).

Document provides three constructors for creating Document objects. The no-argument constructor, which is used in Listing 1, creates a Document with A4 as the default page size (iText is a European library, and A4 is the most common page size in Europe) and 36 as the default size of each margin (measured in points, where there are 72 points per inch). If you prefer a different page size or different margins, simply call a different constructor, which the code fragment below demonstrates:

Document document = new Document (PageSize.LETTER, 40, 40, 40, 40);

The code fragment creates a Document with 8-1/2-inch by 11-inch pages and 40-point margins. Check out the com.lowagie.text.PageSize class for a complete list of page-size constants.

Most of PageSize's constants represent portrait orientation. To achieve landscape orientation, all you need to do is make the page size's height smaller than its width. Because PageSize constants are instances of the com.lowagie.text.Rectangle class, you can easily obtain landscape orientation by invoking Rectangle's public Rectangle rotate() method, as follows:

Document document = new Document (PageSize.LETTER.rotate ());

If you ever need to determine the sizes of a document's margins, you can obtain that information by calling the following methods (each method returns a margin's size in point units):

  • public float bottomMargin()
  • public float leftMargin()
  • public float rightMargin()
  • public float topMargin()

Every document has a (0,0) origin located in the lower-left corner. The locations of the document's margins are relative to this origin. Should you ever need to know where the margins are located, you can obtain that information by calling the methods below (as with the previous methods, each method's return value is in point units):

  • public float bottom()
  • public float left()
  • public float right()
  • public float top()

In addition to letting you establish the page size, margins, and orientation, Document lets you specify document metadata, such as the name of the document's author, the document's title, the document's subject (the purpose of the document), and more. Adobe Reader presents a document's metadata via its Document Properties dialog box when you select Document Properties from the File menu. For example, Figure 2 reveals the metadata in the previously created descent.pdf.

Figure 2

Figure 2 There is not much metadata in descent.pdf.

Document provides methods public boolean addTitle(String title), public boolean addAuthor(String author), public boolean addSubject(String subject), public boolean addKeywords(String keywords), and public boolean addCreator(String creator) that applications call to supply a document's title, author, subject, keywords, and creator (or application) metadata. Furthermore, this class provides methods public boolean addCreationDate() and public boolean addProducer() that iText calls to supply a document's creation date and document producer (iText) metadata. Each method returns a Boolean true if successful.

Metadata methods must be called after establishing the document writer and prior to opening the document. If a metadata method is called before the document writer has been established, that method call is ignored and its data never appears in the document. If the metadata method is called after opening the document, the method throws a com.lowagie.text.DocumentException. The following code fragment reveals the proper place to call metadata methods:

Document doc = new Document ();
PdfWriter.getInstance (doc, new FileOutputStream ("descent.pdf"));
doc.addAuthor ("Jeff Friesen");
doc.addSubject ("Horror story");
doc.open ();

Documents can contain headers and footers. Document provides public void setHeader(HeaderFooter header) and public void setFooter(HeaderFooter footer) methods that let you establish the header and footer as instances of the com.lowagie.text.HeaderFooter class.

HeaderFooter objects are essentially Rectangle objects with text that is placed above or below every page. Invoke the public HeaderFooter(Phrase before, boolean numbered) constructor to create an object suitable only as a header; before is a phrase appearing before the page number, and numbered is true if the page will be numbered. Or you can invoke the public HeaderFooter(Phrase before, Phrase after) constructor to create an object that serves as either a header or a footer: before identifies the phrase appearing before the page number, and after identifies the phrase appearing after the page number.

After creating a HeaderFooter object, you can call its public void setAlignment(int alignment) method to align the header or footer. Pass one of Element's alignment constants (such as Element.ALIGN_RIGHT) as the value of alignment. You can also call public void setPageNumber(int pageN) to establish the page number.

The code fragment below shows how to create a HeaderFooter and add that object as a footer to a document:

Document doc = new Document ();
PdfWriter.getInstance (doc, new FileOutputStream ("doc.pdf"));
doc.open ();

HeaderFooter footer = new HeaderFooter (new Phrase ("- "),
                    new Phrase (" -"));
footer.setAlignment (Element.ALIGN_CENTER);
doc.setFooter (footer);

Of course, the main reason to create a document is to add content. Before you can do that, however, you must open the document. Call the public void open() method to accomplish that task. Keep in mind that attempts to add content, via the public boolean add(Element element) method (which returns Boolean true if the element is added to the document), prior to opening the document result in thrown DocumentExceptions. Once you finish adding content, don't forget to close the document by calling public void close().

Elements

iText refers to different kinds of content (paragraphs, images, tables, and so on) as elements because they are described by objects whose classes implement the com.lowagie.text.Element interface. This interface specifies a variety of constants (such as ALIGN_JUSTIFIED, which was used in Listing 1 to signify that a paragraph is to be justified) and methods (such as public String toString() for returning an element's text). Understanding iText requires an understanding of its elements. For the purposes of this article (and for brevity), we examine only some of these elements: chunk, phrase, paragraph, list, table, section, chapter, and image.

Chunk

The smallest significant element is known as a chunk. Chunks can hold single characters, groups of characters, and images (image-oriented chunks let you inline images in phrases and paragraphs—I do not discuss them in this article). With text-oriented chunks, all characters share the same color, font, and many other attributes.

Chunks are instances of the com.lowagie.text.Chunk class. Call one of seven constructors to create a Chunk object. For example, the following code fragment invokes the public Chunk(String content) constructor to create a five-character chunk:

Chunk chunk = new Chunk ("Hello");

Chunks can be underlined and struck through, and have their background colors changed. Invoke Chunk's public Chunk setUnderline(float thickness, float yPosition) method to underline or strike through a chunk's characters: thickness determines the line's thickness (in points), and yPosition determines the line's position (also in points) relative to the chunk's baseline. You can also invoke public Chunk setBackground(Color color) to color a chunk's background to the value specified by color. (These and many other Chunk methods return references to the current Chunk, so that you can chain multiple method calls into a single expression.) These methods are demonstrated in Listing 2.

Listing 2 ChunkDemo.java

// ChunkDemo.java

import java.awt.Color;
import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class ChunkDemo
{
  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc, new FileOutputStream ("chunkdemo.pdf"));
   doc.open ();

   // Create a one-word chunk and add it to the document.

   Chunk chunk = new Chunk ("Hello");
   doc.add (chunk);

   // Underline the word and add the chunk to the document.

   chunk.setUnderline (0.2f, -2f);
   doc.add (chunk);

   // Create a second one-word chunk, strike it through, and add the chunk
   // to the document.

   chunk = new Chunk ("Goodbye");
   chunk.setUnderline (0.5f, 3f);
   doc.add (chunk);

   // Change the striken chunk's background color and add the chunk to the
   // document.

   chunk.setBackground (Color.yellow);
   doc.add (chunk);

   doc.close ();
  }
}

Listing 2 adds four chunks to a document. The resulting document appears in Figure 3

Figure 3

Figure 3 Chunks can be underlined, struck through, and colored.

Chunks make it possible to introduce links into documents. Invoke Chunk's public Chunk setLocalGoto(String name) and public Chunk setLocalDestination(String name) methods to jump from position to position within the same document: setLocalGoto()identifies the starting position of a jump via its name argument, and setLocalDestination() identifies the destination position via its name argument. Consider the following code fragment:

Document doc = new Document ();
PdfWriter.getInstance (doc, new FileOutputStream ("locallinks.pdf"));
doc.open ();

// Create a link chunk and add it to the document.

Chunk chunk = new Chunk ("Click this link to goto second page.");
chunk.setUnderline (0.2f, -2f);
chunk.setLocalGoto ("test");
chunk.setLocalDestination ("test1");
doc.add (chunk);

// Skip rest of current page and begin a new page.

doc.add (chunk.NEXTPAGE);

// Create a link chunk and add it to the document.

chunk = new Chunk ("Click this link to goto first page.");
chunk.setUnderline (0.2f, -2f);
chunk.setLocalGoto ("test1");
chunk.setLocalDestination ("test");
doc.add (chunk);

doc.close ();

The code fragment creates a document consisting of two pages. Each page reveals a single chunk whose characters are underlined. Clicking any one of these characters takes the reader from that chunk to the other chunk. For example, chunk.setLocalGoto ("test"); is equivalent to the HTML <a href="#test">Click this link to goto second page.</a>. Click that text and you jump to the test destination, established by chunk.setLocalDestination ("test");—which is equivalent to <a name="test">Click this link to goto first page.</a>.

Notice the doc.add (chunk.NEXTPAGE); method call. Chunk's NEXTPAGE constant describes a chunk that ends the current page and begins a new page when added to the document.

Along with jumping from place to place within a given document, you can jump from one place in one document to another place in another document. Accomplish this task by establishing a destination in the target document, via setLocalDestination(), and establishing a link to this destination in the source document via Chunk's public Chunk setRemoteGoto(String filename, String name) method—where filename identifies the document's file, and name identifies the destination. The code fragment below creates a remotelink.pdf document that links to the second page in the previously created locallinks.pdf document:

Document doc = new Document ();
PdfWriter.getInstance (doc, new FileOutputStream ("remotelink.pdf"));
doc.open ();

// Create a link chunk and add it to the document.

doc.add (new Chunk ("Goto page #2 in locallinks.pdf").setRemoteGoto ("locallinks.pdf", "test"));

doc.close ();

Clicking remotelink.pdf's Goto page #2 in locallinks.pdf causes Adobe Reader to load locallinks.pdf and take you to the link on that document's second page.

Phrase and Paragraph

Chunks are appropriate for storing short sequences of text, typically no more than a few words. If you store a lengthy text sequence in a chunk, add that chunk to a document, and load the resulting PDF file in Adobe Reader, you will notice that part of the text is overwritten by the rest of the text. The effect is equivalent to having your printer repeatedly print text on the same line: garbage.

When a chunk's text encroaches on a document's right margin, Adobe Reader does the equivalent of issuing a carriage return without a linefeed: It continues rendering text on the same line and starting from the left margin. This behavior occurs because the PDF document writer does not output PDF syntax that tells Adobe Reader to wrap text onto the next line—the concepts of lines and leading (the distance between two lines, measured in points) do not apply to chunks. In contrast, the lines and leading concepts apply to phrases.

Phrases are instances of the com.lowagie.text.Phrase class that know how to wrap text onto lines. Call one of nine constructors to create a Phrase object. For example, the following code fragment invokes the public Phrase(String string) constructor to create a phrase:

Phrase phrase = new Phrase ("Here are the rules about traffic lights. ");

A Phrase is a container for multiple Chunks. Think of the previous code fragment as adding a Chunk that contains Here are the rules about traffic lights. to the Phrase. You can continue to add Chunks, Phrases, and a few other elements to a Phrase by invoking Phrase's public boolean add(Object o) method. (Invoking this method with a String argument causes Phrase to create a Chunk with that argument and then add that Chunk to itself.) As a result, portions of a phrase can be highlighted in some manner, as Listing 3 demonstrates.

Listing 3 PhraseDemo.java

// PhraseDemo.java

import java.awt.*;
import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class PhraseDemo
{
  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc, new FileOutputStream ("phrasedemo.pdf"));
   doc.open ();

   // Construct a phrase.

   Phrase p = new Phrase ("Here are the rules about traffic lights. ");

   // Add a chunk to phrase containing word Red -- bolded and colored red.

   Chunk ck = new Chunk ("Red");
   ck.setTextRenderMode (PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE, 1, 
              Color.red);
   p.add (ck);

   // Strings can also be added to Phrases.

   p.add (" means stop. ");

   // Add a chunk to phrase containing word Green -- bolded and colored
   // green.

   ck = new Chunk ("Green");
   ck.setTextRenderMode (PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE, 1, 
              Color.green);
   p.add (ck);

   // Even Phrases can be added to Phrases.

   p.add (new Phrase (" means go. "));

   // Add a chunk to phrase containing word Yellow -- bolded and colored
   // yellow.

   ck = new Chunk ("Yellow");
   ck.setTextRenderMode (PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE, 1, 
              Color.yellow);
   p.add (ck);

   p.add (" means proceed with caution.");
       
   // Add phrase to document.

   doc.add (p);
   
   doc.close ();
  }
}

Listing 3 creates a Phrase and then adds Chunks to that Phrase. Three Chunks have their contents highlighted (via bolding and coloring) by invoking Chunk's public Chunk setTextRenderMode(int mode, float strokeWidth, Color strokeColor) method. The first argument, mode, identifies the text-rendering mode via one of the text-rendering constants in the com.lowagie.text.pdf.PdfContentByte class. The text-rendering mode determines how to apply the final two arguments when rendering a character. For example, TEXT_RENDER_MODE_FILL_STROKE outlines and fills a character's visual representation in the current font by using a specific stroke width and a specific stroke color. The result of this rendering appears in Figure 4.

Figure 4

Figure 4 Phrases can contain highlighted chunks.

Several of Phrase's constructors let you specify a leading for determining the amount of space between lines. If you call a constructor that does not let you specify a leading, Phrase chooses 16 points as the default leading. You can always change this default by invoking public void setLeading(float leading). For example, placing p.setLeading (40.0f); immediately after Phrase p = new Phrase ("Here are the rules about traffic lights. "); in Listing 3 results in the increased leading shown in Figure 5.

Figure 5

Figure 5 Invoke an appropriate Phrase constructor or setLeading() to increase a phrase's leading.

Although phrases are useful for wrapping text across multiple lines, they cannot be aligned or indented. You cannot even add space above or below a phrase. The best you can do is to insert a new-line character, which has a tendency to disrupt the flow of text from line to line.

Unlike phrases, paragraphs— which are instances of the com.lowagie.text.Paragraph class—support alignment, indentation, and spacing. Call one of 10 constructors to create a paragraph. For example, the code fragment below invokes the public Paragraph(String string) constructor to create a paragraph:

Paragraph paragraph = new Paragraph ("Here are the rules about traffic lights. ");

Because Paragraph is a subclass of Phrase, you can think of a paragraph as a phrase with alignment, indentation, and spacing capabilities. Listing 4 provides a partial demonstration of these capabilities.

Listing 4 ParagraphDemo.java

// ParagraphDemo.java

import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class ParagraphDemo
{
  static String p1 = "Standard Widget Toolkit (SWT) is a GUI framework " +
           "that builds onto the Abstract Windowing Toolkit " +
           "(AWT) and competes with Swing. Because Swing " +
           "already exists, why introduce another GUI " +
           "framework? Let's find out.";

  static String p2 = "Java's first GUI framework was the Abstract " +
           "Windowing Toolkit (AWT). The AWT existed as a " +
           "platform-independent programming interface to " +
           "native components, such as buttons, labels, and " +
           "text fields. When told to create a button, for " +
           "example, the AWT contacted the underlying operating " +
           "system (OS) to create the button. As a result, " +
           "programs that used the AWT to create their GUIs " +
           "looked like other programs running on the " +
           "underlying OS.";

  static String p3 = "But AWT GUIs were limited. To achieve " +
           "cross-platform portability, the AWT could not take " +
           "advantage of those component features not found on " +
           "all platforms. Furthermore, components specific to " +
           "one or more platforms could not be used.";

  static String p4 = "User interface requirements became more demanding, " +
           "causing Java to depart from the AWT's " +
           "platform-independent interface to native " +
           "components. In AWT's place, Java embraced Swing, " +
           "which implemented components entirely in Java. " +
           "Advanced components that users demanded, such as " +
           "tables and trees, were introduced as part of Swing.";

  static String p5 = "Although Swing satisfied users, it introduced two " +
           "problems: GUIs looked like they had been created " +
           "in Java instead of looking like the GUIs of native " +
           "applications, and GUIs exhibited sluggish " +
           "performance.";

  static String p6 = "The first problem was addressed through the " +
           "development of the pluggable look-and-feel concept. " +
           "You could now tell a Java application to look like " +
           "a Windows application when running on Windows and a " +
           "Macintosh application when running on a Macintosh. " +
           "The second problem remained. Although Java has made " +
           "great strides in virtual machine optimizations to " +
           "enhance the performance of Swing-based Java " +
           "applications, it's still obvious to users when they " +
           "are running those applications.";

  static String p7 = "IBM addressed the second problem by holding on to " +
           "the AWT conceptual model (where the implementation " +
           "of components is delegated to the underlying OS) " +
           "and extending it to include the advanced components " +
           "that developers require. That company released this " +
           "extended AWT conceptual model as SWT -- a GUI " +
           "library for IBM's open-source Eclipse project.";

  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc, new FileOutputStream ("paragraphdemo.pdf"));
   doc.open ();

   // Construct the paragraphs.

   Paragraph [] paras =
   {
     new Paragraph (p1),
     new Paragraph (p2),
     new Paragraph (p3),
     new Paragraph (p4),
     new Paragraph (p5),
     new Paragraph (p6),
     new Paragraph (p7)
   };

   // Align and indent paragraphs, also establish the amount of space to
   // leave between paragraphs.

   for (int i = 0; i &lt; paras.length; i++)
   {
      paras [i].setAlignment (Element.ALIGN_JUSTIFIED);
      paras [i].setIndentationLeft (i * 15.0f);
      paras [i].setIndentationRight ((paras.length - i) * 15.0f);
      paras [i].setSpacingAfter (15f);
      doc.add (paras [i]);
   }

   doc.close ();
  }
}

Listing 4 invokes the public void setAlignment(int alignment) method with Element's ALIGN_JUSTIFIED constant to justify each paragraph, so that there are no jagged edges. The public void setIndentationLeft(float indentation) and public void setIndentationRight(float indentation) methods are called to indent each paragraph on the left and right sides by a specific number of points. For either method, the indentation value is relative to the appropriate margin (left or right). To establish some white space after each paragraph (so that each paragraph stands out better from its predecessor and successor), Listing 4 invokes the public void setSpacingAfter(float spacing) method, where spacing determines how many points of white space are used to separate paragraphs. Alternatively, public void setSpacingBefore(float spacing) could be called to establish some white space before each paragraph. Figure 6shows the effects of these methods on the document's seven paragraphs.

Figure 6

Figure 6 Seven paragraphs flow across the page.

List

iText lets you introduce ordered and unordered lists into a document via the com.lowagie.text.List and com.lowagie.text.ListItem classes. The idea is to first create a List, add ListItems to that List, and finally add the List to the Document.

Create a List by invoking public List(boolean numbered, float symbolIndent). (There are two other constructors you can call, if desired.) The value passed to numbered determines whether numbers (true) or symbols (false) will be displayed. A numbered list typically indicates an ordered list; a non-numbered list indicates the opposite. The value passed to symbolIndent indicates the width (in points) of the field that presents the numbers or symbols.

Once a List has been created, call ListItem's various constructors (such as public ListItem(String string)) to create ListItems and then add them to the list by calling List's public boolean add(Object o) method. List- and list item-creation are demonstrated in Listing 5.

Listing 5 ListDemo.java

// ListDemo.java

import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class ListDemo
{
  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc, new FileOutputStream ("listdemo.pdf"));
   doc.open ();

   // Create a numbered list with a 30-point field for the numbers.

   List list = new List (true, 30);

   // Add some items to this list.

   list.add (new ListItem ("Do this step first."));
   list.add (new ListItem ("Do this step next."));
   list.add (new ListItem ("Do this step last."));

   // Add the list to the document.

   doc.add (list);

   // Add a separator.

   doc.add (Chunk.NEWLINE);

   // Create a symboled list with a 30-point field for the symbols.

   list = new List (false, 30);

   // Add some items to this list.

   list.add (new ListItem ("Orange"));
   list.add (new ListItem ("Apple"));
   list.add (new ListItem ("Cherry"));
   list.add (new ListItem ("Banana"));

   // Add the list to the document.

   doc.add (list);

   doc.close ();
  }
}

Listing 5 creates an ordered list followed by an unordered list. Furthermore, it visually separates both lists by invoking doc.add (Chunk.NEWLINE);, which adds a chunk consisting of a new-line character to the document. The result appears in Figure 7.

Figure 7

Figure 7 Ordered and unordered lists.

If you want to continue playing with lists, I recommend that you explore the GreekList, RomanList, ZapfDingbatsList, and ZapfDingbatsNumberList classes. These classes introduce different kinds of symbols and locate in the com.lowagie.text package.

Table

iText makes it possible to introduce tables into documents via the generic com.lowagie.text.Table and com.lowagie.text.Cell classes. With PDF documents, you can alternatively work with the com.lowagie.text.pdf.PdfPTable and com.lowagie.text.pdf.PdfPCell classes. Because iText documentation indicates that Table is no longer supported, and because this article focuses on PDF documents, I believe it is more important to study PdfPTable and PdfPCell, so let us become acquainted with those classes.

Table creation begins with a call to one of PdfPTable's three constructors, such as public PdfPTable(int numColumns). The following code fragment calls this constructor to create a PdfPTable object that describes a three-column table:

PdfPTable table = new PdfPTable (3);

After creating a PdfPTable, you can start adding cells by invoking any of PdfPTable's five addCell()methods. Cells are added on a row-by-row basis: All the row 1 cells are added first, next come all the row 2 cells, and so on.

Suppose you want to add a title to the table and have this title span all columns. You can accomplish that task by creating a PdfPCell object with the title text, by next invoking PdfPCell's public void setColspan(int colspan) method with the number of columns to span, and finally by invoking PdfPTable's public void addCell(PdfPCell cell) method to add the cell to the table. The code fragment below demonstrates:

PdfPCell cell = new PdfPCell (new Paragraph ("Welcome to my table"));
cell.setColspan (3);
table.addCell (cell);

After adding the title, you can add rows of data. For example, the code fragment below invokes the public void addCell(String text) method to add a row's worth of columns to the previously created table:

table.addCell ("Row 1, Column 1");
table.addCell ("Row 1, Column 2");
table.addCell ("Row 1, Column 3");

Listing 6 introduces a more interesting example.

Listing 6 TableDemo1.java

// TableDemo1.java

import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class TableDemo1
{
  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc, new FileOutputStream ("tabledemo1.pdf"));
   doc.open ();

   // Create a 2-column table.

   PdfPTable table = new PdfPTable (2);

   // Create and add a title across both columns.

   PdfPCell cell = new PdfPCell (new Paragraph ("New Mustang Features"));
   cell.setColspan (2);
   table.addCell (cell);

   // Add header cells for these columns.

   table.addCell ("Feature");
   table.addCell ("Description");

   // Add the first row.

   table.addCell ("Splash screens");
   table.addCell ("Show a splash screen during application startup. " +
           "Supports GIF, JPEG, and PNG image formats. Includes " +
           "support for transparency and animation as supported " +
           "by the image format. Lets you plug into the splash " +
           "screen via a simple API, to display runtime messages, " +
           "application loading progress, and so on.");

   // Add the second row.

   table.addCell ("System tray API");
   table.addCell ("Add a Java application to a system's app-launching " +
           "panel/toolbar. Create a \"tray icon\", add the icon " +
           "to the \"system tray area,\" and listen for various " +
           "events.");

   // Add the table to the document.

   doc.add (table);

   doc.close ();
  }
}

This example creates a two-column table that consists of a title that spans both columns, a pair of column headers, and two rows describing the splash screen and system tray API features that will most likely be added to the next major Java release. The resulting table appears in Figure 8.

Figure 8

Figure 8 An unattractive four-row by two-column table.

The table shown in Figure 8 doesn't look very nice. Everything is squeezed together, and there is no color. Fortunately, we can make several changes that will result in a more attractive table.

The first two changes deal with the table's overall width and the width of each column. Making the table wider and shrinking the leftmost column allows more text to appear on each line of the rightmost column. The table's height shrinks, resulting in a table that looks more spread out.

Change the overall width by calling PdfPTable's public void setWidthPercentage(float widthPercentage) method. That method establishes the overall width as a percentage of the available page width (the space between the margins). Example: table.setWidthPercentage (90.0f); changes the overall width from the 80% default to 90% of the available page width.

Change each column's width by invoking the public PdfPTable(float[] relativeWidths) constructor: 0.5f indicates a column half as wide as a standard column, 1.0f indicates a standard column width, 2.0f gives a column with twice the standard column width, and so on. Example: PdfPTable table = new PdfPTable (new float [] { 1.0f, 2.0f }); doubles the width of the rightmost column, relative to the leftmost column.

We can make three additional changes to improve the table: establish a cell's horizontal alignment, establish a cell's background color, and establish a cell's padding:

  • The first change requires a call to PdfPCell's public void setHorizontalAlignment(int horizontalAlignment) method with an appropriate alignment constant (such as Element.ALIGN_CENTER).
  • The second change requires a call to PdfPCell's public void setBackgroundColor(Color value) method (which is inherited from the Rectangle superclass).
  • The final change requires a call to PdfPCell's public void setPadding(float padding) method, where padding determines the amount of space to leave between a cell's content and the cell's border.

All of the five previously mentioned changes are demonstrated in Listing 7. Apart from those changes (and the inclusion of a blank row), the resulting table is pretty much identical to Listing 6's table.

Listing 7 TableDemo2.java

// TableDemo2.java

import java.awt.Color;
import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class TableDemo2
{
  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc, new FileOutputStream ("tabledemo2.pdf"));
   doc.open ();

   // Create a 2-column table. Instead of the default 80% of the available
   // page, have the table take up 90% of the available page. Also, make
   // the second column twice as wide as the first column.

   PdfPTable table = new PdfPTable (new float [] { 1.0f, 2.0f });
   table.setWidthPercentage (90.0f);

   // Create and add a title across both columns.

   PdfPCell cell = new PdfPCell (new Paragraph ("New Mustang Features"));
   cell.setColspan (2);
   cell.setHorizontalAlignment (Element.ALIGN_CENTER);
   cell.setBackgroundColor (new Color (128, 255, 128));
   cell.setPadding (10.0f);
   table.addCell (cell);

   // Add header cells for these columns.

   cell = new PdfPCell (new Paragraph ("Feature"));
   cell.setHorizontalAlignment (Element.ALIGN_CENTER);
   cell.setBackgroundColor (new Color (255, 255, 0));
   cell.setPadding (10.0f);
   table.addCell (cell);

   cell = new PdfPCell (new Paragraph ("Description"));
   cell.setHorizontalAlignment (Element.ALIGN_CENTER);
   cell.setBackgroundColor (new Color (0, 255, 255));
   cell.setPadding (10.0f);
   table.addCell (cell);

   // Add the first row.

   table.addCell ("Splash screens");
   table.addCell ("Show a splash screen during application startup. " +
           "Supports GIF, JPEG, and PNG image formats. Includes " +
           "support for transparency and animation as supported " +
           "by the image format. Lets you plug into the splash " +
           "screen via a simple API, to display runtime messages, " +
           "application loading progress, and so on.");

   // Add a separator row -- for neatness.

   table.addCell (" ");
   table.addCell (" ");

   // Add the second row.

   table.addCell ("System tray API");
   table.addCell ("Add a Java application to a system's app-launching " +
           "panel/toolbar. Create a \"tray icon\", add the icon " +
           "to the \"system tray area,\" and listen for various " +
           "events.");

   // Add the table to the document.

   doc.add (table);

   doc.close ();
  }
}

I think I have managed to create a more attractive table. Because I have never considered myself to possess artistic talent, you will have to be the judge. Check out Figure 9.

Figure 9

Figure 9 A more attractive table.

Section and Chapter

Chunks, phrases, paragraphs, lists, and tables are sufficient for organizing the content of simple documents such as invoices and letters. For more complex documents, such as books, you need higher levels of organization. iText's com.lowagie.text.Section and com.lowagie.text.Chapter classes meet that need. Use those classes to introduce sections and chapters into documents.

Section and Chapter have an interesting relationship. Although Section is the superclass of Chapter (making chapters a special kind of section), and although Chapter contributes almost no methods, you cannot directly create Section objects—only Chapter objects.

Create a Chapter object by calling one of Chapter's three constructors, such as public Chapter(Paragraph title, int number): title identifies the chapter's title, which appears on both Adobe Reader's Bookmarks pane and the chapter's document page, and number identifies the chapter's number. The following code fragment accomplishes this task:

Paragraph title = new Paragraph ("Chapter 1: What is Java?");
Chapter chapter = new Chapter (title, 1);

After creating a Chapter object, call one of Chapter's inherited addSection() methods, such as public Section addSection(Paragraph title) (title identifies the section's title), to introduce a Section into the Chapter and return a reference to that Section. This is demonstrated in the following code fragment:

Section section = chapter.addSection (new Paragraph ("History of Java"));
section.add (new Paragraph ("Java has had an interesting history. In " +
              "fact, Java's original name was Oak, " +
              "named after an Oak tree growing outside " +
            "an office window."));

Following its introduction of a section, the code fragment shows that you can add content (such as a paragraph) to that section. Once you have added enough content and have added to the chapter all of the sections that you want, add the chapter to the document: doc.add (chapter), for example.

Listing 8 combines the previous code fragments with other code to create a document consisting of two chapters—one section for the first chapter, and two sections for the second chapter.

Listing 8 SectionChapterDemo.java

// SectionChapterDemo.java

import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class SectionChapterDemo
{
  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc,
               new FileOutputStream ("sectionchapterdemo.pdf"));
   doc.open ();

   // Create the first chapter: What is Java? Set number depth to 0 so that
   // no number appears to the left of the chapter when viewed in the
   // outline.

   Paragraph title = new Paragraph ("Chapter 1: What is Java?");
   Chapter chapter = new Chapter (title, 1);
   chapter.setNumberDepth (0);

   // Add the chapter's introductory paragraph.

   chapter.add (new Paragraph ("Welcome to Java. It was created by Sun " +
                 "Microsystems"));

   // Add the chapter's solitary section.

   Section section = chapter.addSection (new Paragraph ("History of Java"));
   section.add (new Paragraph ("Java has had an interesting history. In " +
                 "fact, Java's original name was Oak, " +
                 "named after an Oak tree growing outside " +
                 "an office window."));

   // Add the first chapter to the document.

   doc.add (chapter);

   // Create the second chapter: Expressions and Statements.

   title = new Paragraph ("Chapter 2: Expressions and Statements");
   chapter = new Chapter (title, 2);

   // Add the chapter's introductory paragraph.

   chapter.add (new Paragraph ("Expressions and statements are " +
                 "fundamental to all meaningful programs."));

   // Add the chapter's first section.

   section = chapter.addSection (new Paragraph ("What are expressions?"));
   section.add (new Paragraph ("Expressions are a program's engines."));

   // Add the chapter's second section.

   section = chapter.addSection (new Paragraph ("What are statements?"));
   section.add (new Paragraph ("Statements control the execution of " +
                 "expressions."));

   // Add the second chapter to the document.

   doc.add (chapter);

   doc.close ();
  }
}

Listing 8 presents an interesting bit of code: chapter.setNumberDepth (0);. That method call, with the 0 argument, prevents the chapter number from appearing to the left of the chapter title in the outline on the Bookmarks pane. Figure 10 shows the chapter number to the left of the outline's second chapter entry, but the chapter number does not appear to the left of the first chapter entry.

Figure 10

Figure 10 Each chapter automatically begins on a new page.

Figure 10 also shows that each chapter has been expanded on the Bookmarks pane. You see all the section titles (such as 1. History of Java) in addition to the chapter titles. You can override this default behavior by calling Section's public void setBookmarkOpen(boolean bookmarkOpen) method with a false argument value. Example: chapter.setBookmarkOpen (false); prevents the chapter associated with chapter from being expanded; its sections are not displayed.

Earlier, you were shown how to create a Chapter by calling a constructor that takes a title and a chapter number, and you learned that the title appears on both the Bookmarks pane and the chapter's document page. Suppose that you want something different to appear on the Bookmarks pane. You can accomplish that task by invoking Section's public void setBookmarkTitle(String bookmarkTitle) method. For example, chapter.setBookmarkTitle ("What is Java?"); changes the title on Figure 10's Bookmarks pane from Chapter 1: What is Java? to What is Java?.

Image

A document without images is somewhat bland. Fortunately, iText supports adding GIF, JPEG, PNG, and other kinds of images to documents by way of its com.lowagie.text.Image class.

Image is an abstract class. Although you cannot create Image objects, you can invoke any one of its static getInstance() methods to load an image and return an object whose class subclasses Image. For example, the following code fragment invokes public static Image getInstance(String filename) to load an image from the file identified by filename:

Image image = Image.getInstance ("trees_meet_sky.jpg");

After loading an image, you can either add it directly to your document or set some attributes prior to adding the image. For example, you can left-align (along the left margin), center-align (between the left and right margins), or right-align (along the right margin) an image by calling the public void setAlignment(int alignment) method with one of the Image.LEFT, Image.MIDDLE, or Image.RIGHT constants.

Listing 9 demonstrates obtaining an image and aligning that image along the left margin, in the middle between the left and right margins, and along the right margin.

Listing 9 ImageDemo1.java

// ImageDemo1.java

import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class ImageDemo1
{
  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc, new FileOutputStream ("imagedemo1.pdf"));
   doc.open ();

   // Introduce an image and add it to the document. The default alignment
   // is left.

   Image image = Image.getInstance ("trees_meet_sky.jpg");
   doc.add (image);

   // Add a caption to this image.

   doc.add (new Paragraph ("Left-aligned image"));

   // Change the image's alignment to center alignment and add a new copy
   // of that image to the document.

   image.setAlignment (Image.MIDDLE);
   doc.add (image);

   // Add a caption to the image. Make sure the caption is center-aligned,
   // so that it appears under the image.

   Paragraph p = new Paragraph ("Center-aligned image");
   p.setAlignment (Element.ALIGN_CENTER);
   doc.add (p);

   // Change the image's alignment to right alignment and add a new copy of 
   // that image to the document.

   image.setAlignment (Image.RIGHT);
   doc.add (image);

   // Add a caption to the image. Make sure the caption is right-aligned,
   // so that it appears under the image.

   p = new Paragraph ("Right-aligned image");
   p.setAlignment (Element.ALIGN_RIGHT);
   doc.add (p);

   doc.close ();
  }
}

Figure 11 reveals a left-aligned image (the default alignment), the same image center-aligned, and the same image aligned along the right margin.

Figure 11

Figure 11 Images can be left-aligned, center-aligned, or right-aligned.

Along with left-aligning, center-aligning, or right-aligning an image, the setAlignment() method can be used to indicate that text should wrap around an image via the Image.TEXTWRAP constant or appear on top of an image via the Image.UNDERLYING constant (for example, image.setAlignment (Image.TEXTWRAP);).

If an image is left-aligned and you decide to wrap text around the image, you should reserve some space on the image's right to separate the image from the text. Accomplish that task by invoking the public void setIndentationRight(float f) method, where f determines the number of points of space to reserve. Similarly, if the image is right-aligned, call the public void setIndentationLeft(float f) method to reserve some space on the image's left.

Listing 10 demonstrates wrapping text around an image and writing text over an underlying image.

Listing 10 ImageDemo2.java

// ImageDemo2.java

import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class ImageDemo2
{
  static String para1 =

  "Marigolds, members of the Compositae family, are hardy flowers grown " +
  "in Europe and America. Most marigolds have long stalks and leaves that " +
  "are deeply cut. Furthermore, they stand from one to two feet high.";

  static String para2 =

  "The flower's appearance is typically orange or yellow, although brown " +
  "and reddish flowers also occur. Marigolds exhibt an odor, which is " +
  "not enjoyed by some people. Fortunately, for them, scientists have " +
  "developed odorless marigolds.";

  static String para3 =

  "Marigolds can be started from seed placed in ordinary soil. Plant " +
  "them about one foot apart. They often bloom in mid to late summer.";

  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc, new FileOutputStream ("imagedemo2.pdf"));
   doc.open ();

   // Obtain an image, make it possible to wrap text around the image, and
   // add some space between the image and text (on the right side).

   Image image = Image.getInstance ("three_yellow_marigolds.jpg");
   image.setAlignment (Image.TEXTWRAP);
   image.setIndentationRight (10.0f);
   doc.add (image);

   Paragraph p = new Paragraph (para1);
   p.setAlignment (Element.ALIGN_JUSTIFIED);
   p.setSpacingAfter (p.leading ());
   doc.add (p);

   p = new Paragraph (para2);
   p.setAlignment (Element.ALIGN_JUSTIFIED);
   p.setSpacingAfter (p.leading ());
   doc.add (p);

   p = new Paragraph (para3);
   p.setAlignment (Element.ALIGN_JUSTIFIED);
   doc.add (p);

   doc.add (Chunk.NEWLINE);
   doc.add (Chunk.NEWLINE);

   // Obtain an image and make it possible to write text on the image.

   image = Image.getInstance ("three_yellow_marigolds.jpg");
   image.setAlignment (Image.UNDERLYING);
   doc.add (image);

   p = new Paragraph (para1);
   p.setAlignment (Element.ALIGN_JUSTIFIED);
   p.setSpacingAfter (p.leading ());
   doc.add (p);

   p = new Paragraph (para2);
   p.setAlignment (Element.ALIGN_JUSTIFIED);
   p.setSpacingAfter (p.leading ());
   doc.add (p);

   p = new Paragraph (para3);
   p.setAlignment (Element.ALIGN_JUSTIFIED);
   doc.add (p);

   doc.close ();
  }
}

Figure 12 reveals text wrapped around the right side of a left-aligned image. This figure also shows text that has been written over an underlying image.

Figure 12

Figure 12 Text can be wrapped around an image or written on top of an underlying image.

Suppose that you want to scale an image up or down. iText provides the public void scalePercent(float percent) method to accomplish that task. The percent value determines whether the image is scaled down (the value is less than 100.0) or scaled up (the value is greater than 100.0). The following code fragment scales up an image by 150%:

image.scalePercent (150.0f);

Suppose that you want to rotate an image. You can accomplish rotation by calling public void setRotation(float r) to rotate an image by a specific number of radians or by calling public void setRotationDegrees(float deg) to carry out the rotation in terms of degrees. With either method, a positive value causes the image to rotate in a counterclockwise direction; a negative value results in a clockwise rotation. The following code fragment rotates an image clockwise by 45 degrees:

image.setRotationDegrees (-45.0f);

Listing 11 demonstrates image scaling and rotation.

Listing 11 ImageDemo3.java

// ImageDemo3.java

import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class ImageDemo3
{
  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc, new FileOutputStream ("imagedemo3.pdf"));
   doc.open ();

   // Introduce an image and add it to the document.

   Image image = Image.getInstance ("sun_through_trees.jpg");
   doc.add (image);

   // Scale down image 50%, rotate image 30 degrees counter-clockwise, and
   // add image to document.

   image.scalePercent (50.0f);
   image.setRotationDegrees (30.0f);
   doc.add (image);

   doc.close ();
  }
}

After obtaining and adding a normal-size image to the document, Listing 11 scales down the image by 50% and rotates it 30 degrees counterclockwise before adding this variant to the document. The images are shown in Figure 13.

Figure 13

Figure 13 Images can be scaled and rotated.

Font and FontFactory

Along with images, fonts can enhance documents. iText provides a com.lowagie.text.Font class that lets you work with fonts from six default font families: Times, Symbol, ZapfDingbats, Helvetica, Times-Roman, and Courier. Create a font from any of these families by calling one of 10 constructors, such as public Font(int family, float size, int style). For example, new Font (Font.HELVETICA, 20, Font.BOLD) creates a 20-point bold Helvetica font.

You will typically create a Font object and pass it to an appropriate Paragraph, Phrase, or some other constructor. Listing 12 demonstrates how to use fonts in conjunction with a Paragraph and a Phrase to improve a document's appearance.

Listing 12 FontDemo.java

// FontDemo.java

import java.awt.Color;
import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class FontDemo
{
  static String [] lines =
  {
   "o be, or not to be: that is the question:",
   "Whether 'tis nobler in the mind to suffer",
   "The slings and arrows of outrageous fortune,",
   "Or to take arms against a sea of troubles,",
   "And by opposing end them? To die: to sleep;",
   "No more; and by a sleep to say we end",
   "The heart-ache and the thousand natural shocks",
   "That flesh is heir to, 'tis a consummation",
   "Devoutly to be wish'd. To die, to sleep;",
   "To sleep: perchance to dream: ay, there's the rub;",
   "For in that sleep of death what dreams may come",
   "When we have shuffled off this mortal coil,",
   "Must give us pause: there's the respect",
   "That makes calamity of so long life;",
   "For who would bear the whips and scorns of time,",
   "The oppressor's wrong, the proud man's contumely,",
   "The pangs of despised love, the law's delay,",
   "The insolence of office and the spurns",
   "That patient merit of the unworthy takes,",
   "When he himself might his quietus make",
   "With a bare bodkin? who would fardels bear,",
   "To grunt and sweat under a weary life,",
   "But that the dread of something after death,",
   "The undiscover'd country from whose bourn",
   "No traveller returns, puzzles the will",
   "And makes us rather bear those ills we have",
   "Than fly to others that we know not of?",
   "Thus conscience does make cowards of us all;",
   "And thus the native hue of resolution",
   "Is sicklied o'er with the pale cast of thought,",
   "And enterprises of great pith and moment",
   "With this regard their currents turn awry,",
   "And lose the name of action."
  };

  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc, new FileOutputStream ("fontdemo.pdf"));
   doc.open ();

   Paragraph title = new Paragraph ("Hamlet Soliloquy",
                    new Font (Font.HELVETICA, 20,
                         Font.BOLD));
   title.setAlignment (Element.ALIGN_CENTER);
   title.setSpacingAfter (15.0f);
   doc.add (title);

   Paragraph p = new Paragraph ();
   p.add (new Phrase ("T", new Font (Font.TIMES_ROMAN, 18, Font.BOLD,
                    new Color (0, 0, 192))));
   p.add (lines [0]);
   p.setAlignment (Element.ALIGN_CENTER);
   doc.add (p);

   for (int i = 1; i &lt; lines.length; i++)
   {
      p = new Paragraph (lines [i]);
      p.setAlignment (Element.ALIGN_CENTER);
      doc.add (p);
   }

   doc.close ();
  }
}

Although Listing 12 makes minimal use of fonts, that use manages to liven up an otherwise bland-looking Shakespearean soliloquy—by providing a large and bolded title, and by modifying the family, style, size, and color of the first character on the first line of that soliloquy. See this for yourself by examining Figure 14.

Figure 14

Figure 14 Fonts liven up text through family, style, size, and color.

Font does not let you create fonts from any but the six default font families. To overcome this limitation, you must work with the com.lowagie.text.FontFactory class.

The FontFactory class consists entirely of static methods (and constants). Several methods exist to create a font based on font information in some font file and (typically) other information. For example, public static Font getFont(String fontname, float size) attempts to create a font based on the contents of the font file identified by fontname and a size specified by size. The following code fragment invokes that method to return a 16-point version of the comic font that is stored on my platform in the c:\windows\fonts\comic.ttf file:

Font f = FontFactory.getFont ("c:\\windows\\fonts\\comic.ttf", 16);

The code fragment above reveals a problem—hard-coding a platform-specific path in the source code. We could eliminate the hard-coding by using a constant, but then we would have to place that constant in every location where we issue getFont() (which would clutter source code). Fortunately, FontFactory provides a better solution: registration methods.

You can register a font by calling public static void register(String path), where path identifies the path and name of the font file. You need to register a font only once. Thereafter, when calling a getFont() method, you only specify the font's name: no path or .ttf file extension is specified. The following code fragment registers the jokerman font file, and acquires an 18-point version of that font by passing jokerman to getFont():

FontFactory.register ("c:\\windows\\fonts\\jokerman.ttf");
Font f = FontFactory.getFont ("jokerman", 18);

As you explore FontFactory, you will discover other registration methods. For example, public static int registerDirectory(String dir) lets you register all fonts in a specific directory: FontFactory.registerDirectory ("c:\\windows\\fonts"); registers all fonts in my c:\windows\fonts directory. Although registerDirectory() obviates the need to individually register fonts via register(), it can take some time to complete the registration. The amount of time depends on the number of fonts in the directory.

If you ever need to know which font families and individual fonts have been registered, you can find this information by calling the public static Set getRegisteredFamilies() and public static Set getRegisteredFonts() methods. Each method returns a Set that you iterate over to obtain the needed information.

Listing 13 demonstrates FontFactory's font-registration and font-acquisition methods.

Listing 13 FontFactoryDemo.java

// FontFactoryDemo.java

import java.io.*;
import java.util.Set;
import java.util.Iterator;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class FontFactoryDemo
{
  public final static String FONT_PATH = "c:\\windows\\fonts\\";

  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter.getInstance (doc, new FileOutputStream ("fontfactorydemo.pdf"));
   doc.open ();

   // Obtain and output a set of registered font family names.

   Set set = FontFactory.getRegisteredFamilies ();

   Paragraph title = new Paragraph ("Default Font Family Names",
                    new Font (Font.HELVETICA, 20,
                         Font.BOLD | Font.UNDERLINE));
   title.setAlignment (Element.ALIGN_CENTER);
   title.setSpacingAfter (15.0f);
   doc.add (title);

   List list = new List (false, 20);

   Iterator i = set.iterator ();
   while (i.hasNext ())
     list.add (new ListItem ((String) i.next ()));

   doc.add (list);

   doc.add (Chunk.NEWLINE);

   // Obtain and output a set of registered font names.

   set = FontFactory.getRegisteredFonts ();

   title = new Paragraph ("Default Font Names",
               new Font (Font.HELVETICA, 20, Font.BOLD |
                    Font.UNDERLINE));
   title.setAlignment (Element.ALIGN_CENTER);
   title.setSpacingAfter (15.0f);
   doc.add (title);

   list = new List (false, 20);

   i = set.iterator ();
   while (i.hasNext ())
     list.add (new ListItem ((String) i.next ()));

   doc.add (list);

   doc.add (Chunk.NEWLINE);

   // Output a paragraph in 16-point comic true type font.

   Font f = FontFactory.getFont (FONT_PATH + "comic.ttf", 16);

   Paragraph p = new Paragraph ("This paragraph appears in 16-point comic.",
                  f);

   doc.add (p);

   // Register the jokerman true type font.

   FontFactory.register (FONT_PATH + "jokerman.ttf");

   // Output a paragraph in 18-point jokerman font.

   f = FontFactory.getFont ("jokerman", 18);

   p = new Paragraph ("This paragraph appears in 18-point jokerman.", f);

   doc.add (p);

   doc.close ();
  }
}

The document that Listing 13 creates is shown in Figure 15.

Figure 15

Figure 15 The font factory lets you obtain fonts from nondefault font families.

PdfWriter

I would be remiss if I did not talk about com.lowagie.text.pdf.PdfWriter before leaving this section. That class describes a document writer for outputting PDF syntax: PdfWriter writes the PDF syntax for every element added to a Document to an OutputStream.

PdfWriter presents methods that you can call to add value to your document. Before calling these methods, however, you need to obtain an instance of PdfWriter. Accomplish that task by saving the reference returned from a PdfWriter.getInstance() method call to a PdfWriter variable (for example, PdfWriter writer = PdfWriter.getInstance (doc, new FileOutputStream ("doc.pdf"));).

One method that you will want to call to set certain preferences for Adobe Reader or another PDF document viewer is public void setViewerPreferences(int preferences). Preferences range from whether or not to position a document's window in the center of the screen to determining how many pages to display at once.

One preference that is quite useful: Display the Bookmarks pane as soon as a document is opened. That way, you don't have to first click the Bookmarks tab to display that pane. As with other preferences, this preference is described by a PdfWriter constant—PageModeUseOutlines, to be exact. The following code fragment shows how to invoke setViewerPreferences() with that constant to set the preference:

writer.setViewerPreferences (PdfWriter.PageModeUseOutlines);

Two other methods that you will want to call are public PdfContentByte getDirectContent() and public PdfContentByte getDirectContentUnder(). These methods return PdfContentByte objects, whose methods let you exactly position and draw text and graphics on the topmost and bottommost layers of each document page. (iText divides a document's pages into four layers. Each layer's content covers the content on all layers below. You cannot access the middle two layers, which store the text and graphics generated by iText's high-level objects.)

Listing 14 creates a document with a solitary page serving as a title page. After making a call to getDirectContent(), to return a PdfContentByte object for the topmost layer, it calls that object's methods to draw a rectangle around the inside of the margin area, and draw a horizontally and vertically centered title.

Listing 14 PdfWriterDemo.java

// PdfWriterDemo.java

import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

class PdfWriterDemo
{
  public static void main (String [] args) throws Exception
  {
   Document doc = new Document ();
   PdfWriter writer = PdfWriter.getInstance (doc,
                 new FileOutputStream ("pdfwriterdemo.pdf"));

   doc.open ();

   // Obtain a PdfContentByte object that lets us draw on the topmost
   // layer.

   PdfContentByte cb = writer.getDirectContent ();

   // Draw a rectangle inside the page's margins.

   cb.rectangle (doc.left (), doc.bottom (), doc.right ()-doc.left (),
          doc.top ()-doc.bottom ());

   cb.stroke ();

   // Draw and center a title.

   cb.beginText ();

   BaseFont bf = BaseFont.createFont (BaseFont.HELVETICA,
                     BaseFont.CP1252,
                     BaseFont.NOT_EMBEDDED);
   cb.setFontAndSize (bf, 36);

   cb.showTextAligned (PdfContentByte.ALIGN_CENTER, "Title Page",
             (doc.right ()-doc.left ()) / 2 + doc.leftMargin (),
             (doc.top ()-doc.bottom ()) / 2 + doc.topMargin (),
             0);

   cb.endText ();

   doc.close ();
  }
}

Several PdfContentByte methods are demonstrated in Listing 14. For starters, cb.rectangle (doc.left (), doc.bottom (), doc.right ()-doc.left (),doc.top ()-doc.bottom ()); outputs PDF syntax defining a rectangle that outlines the inside of the margins. The cb.stroke (); method call outputs syntax that paints this rectangle on the page.

Remaining method calls, from cb.beginText() to cb.endText(), are concerned with the establishment of a font and font size, and drawing text (via that font) that is centered horizontally and vertically on the page. All these method calls combine to produce the title page that appears in Figure 16.

Figure 16

Figure 16 The title page consists of centered text within a border drawn inside the margins.

4. Planetary PDF | Next Section Previous Section