The successful Java developer requires extensive and accurate knowledge of the language and its APIs. A toolbox full of useful tools that help the developer solve problems in an efficient manner also contributes to success.
I have found three very useful open-source tools that have helped me with my Java career. Because I enjoy working with these tools and because I think they will also benefit your own Java career, I wrote this three-part series to introduce each tool. This article launches that series by focusing on iText.
Create PDF Documents with iText
Have you ever wanted to create Adobe PDF documents without having to spend money on PDF-creation software? (You can create five free PDF documents at Adobe's Web site, but you must pay a subscription fee to create documents beyond the first five.) Thanks to Java and a utility called iText, you can create PDF documents for free. iText even lets you create HTML, RTF, and XML documents (also for free).
This article introduces you to iText from the perspective of PDF-document creation. After showing you how to acquire iText, establish an appropriate directory structure, and set the CLASSPATH environment variable, I present a simple Java application that uses iText to create an elementary PDF document. The article then takes you on a tour of iText classes that you will frequently access from your Java applications, along with several small Java applications that demonstrate class usage. To show how iText helps you with big PDF projects, the article concludes by showing you how to create a multichapter PDF document on the solar system's planets. That project's more extensive Java application references several of the classes that were explored in the tour and reveals how iText capabilities combine to create a significant PDF document.
iText Intro
iText, created by Bruno Lowagie and Paulo Soares, is an open-source Java library for creating PDF, HTML, RTF, and XML documents. Although you can use iText without paying a fee, this library is licensed to protect the software, its developers, and its users. Learn more about licenses on iText's Download & Compile page.
For working with this article's code, I recommend that you download itext-1.3.jar instead of first downloading and then compiling the iText source code. After clicking the itext-1.3.jar link, you are taken to the SourceForge site, from which you can select an appropriate mirror to download the Jar file. You should also click the itext-docs-1.3.tar.gz link to download iText's library documentation.
After downloading iText's Jar and documentation files, establish a directory structure to organize these files and help you manage your iText projects. I chose the following directory structure on my C: drive:
c:\itext\docs c:\itext\projects c:\itext\itext-1.3.jar
The iText directory serves as my home directory for docs (the library's documentation directory—extract itext-docs-1.3.tar.gz's directories and files into this directory), projects (various iText projects directory), and the iText Jar file.
Having established a directory structure, only one other task needs to be accomplished: Set the CLASSPATH. That environment variable must include iText's Jar file and the current directory. For Windows 98 SE, the command to set CLASSPATH is set classpath=%classpath%;c:\itext\itext-1.3.jar;. If you are using a different version of Windows or a non-Windows operating system, study your OS documentation to find out how to set this environment variable.
Now that we have established our directory structure and set the CLASSPATH environment variable, we can explore a Java application that uses iText to create a simple PDF document. Take a look at Listing 1.
Listing 1 Descent.java
// Descent.java
import java.io.*;
import com.lowagie.text.*;
import com.lowagie.text.pdf.*;
class Descent
{
static String para =
"Lightning flashed across the sky. Thunder rumbled in the distance. " +
"The wind began to moan, as if in pain. The good people of the village " +
"huddled together in the common room. Fear glistened in their eyes and " +
"their hearts pounded with terror. Evil was descending into their " +
"valley, and there was nothing they could do to save their lives ...";
public static void main (String [] args) throws Exception
{
Document doc = new Document ();
PdfWriter.getInstance (doc, new FileOutputStream ("descent.pdf"));
doc.open ();
Paragraph p = new Paragraph (para);
p.setAlignment (Element.ALIGN_JUSTIFIED);
doc.add (p);
doc.close ();
}
}
Listing 1 creates a PDF document consisting of a single paragraph (that we will pretend was taken from a larger horror story called Descent). The source code reveals five steps that iText applications perform to create PDF documents:
- Create an instance of the Document class: Document doc = new Document (); accomplishes that task. The Document class describes a document's page size (Letter, Legal, A4, and so on), margins, and other important attributes. It is also a container for a document's chapters, sections, images, paragraphs, and other content.
- Create a document writer that writes the equivalent syntax for a document's content to a specific OutputStream (this article always uses PdfWriter to output PDF syntax; other document writers output HTML, RTF, and XML syntax): PdfWriter.getInstance (doc, new FileOutputStream ("descent.pdf")); creates a PDF document writer that writes PDF syntax to file descent.pdf via a FileOutputStream.
- Open the document; doc.open (); is all that is required.
- Add content to the document. There are many ways to do this. Paragraph p = new Paragraph (para); p.setAlignment (Element.ALIGN_JUSTIFIED); doc.add (p); creates a Paragraph that houses a paragraph of text, tells the PDF document writer to ensure that the Paragraph's text is justified on both sides, and adds the Paragraph to the previously created Document. As content is added, the PDF document writer works behind the scenes to send the equivalent PDF syntax to the file.
- Close the document; doc.close (); accomplishes this task. Closing the document is important because it flushes and closes the OutputStream instance to which the document writer is writing syntax.
Now that we have examined the structure of our first iText application, we are almost ready to compile the application's source code. First, however, we must create a Descent directory underneath the projects directory, copy Listing 1 into a Descent.java file, and store that file in Descent.
After accomplishing the previous tasks, compile Descent.java (javac Descent.java). Assuming that you have properly set the CLASSPATH environment variable, you should find a Descent.class file in the Descent directory.
We can now create our first PDF document. Invoke java Descent to run the application. You should observe a descent.pdf file in the Descent directory. View that document by launching your copy of Adobe Reader (or some other suitable PDF document viewer) and opening descent.pdf. You should see something similar to Figure 1.
Figure 1 Your first iText-created PDF document.
Congratulations. You have just created your first PDF document, and it did not cost you anything. The following section builds upon the knowledge you have just acquired by taking you on a tour of important iText classes.
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 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 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 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 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 < 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 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 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 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 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 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 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 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 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 < 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 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 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 The title page consists of centered text within a border drawn inside the margins.
Planetary PDF
This article has covered lots of material. Your head is probably spinning from exploring many of the capabilities offered by iText. Perhaps you are asking yourself how to combine these capabilities to create useful PDF documents. If so, I have an answer: a more extensive PDF project that brings together many of the capabilities that were presented earlier.
The project I have in mind is to write an application that creates a PDF document called planets.pdf, which tours the planets in our solar system. It contains a title page, an introduction, one chapter per planet, and an appendix that compares each planet's orbit, diameter, and mass. Figure 17 shows what I have in mind.
Figure 17 The planetary PDF document.
Don't worry. You do not have to create the application that generates Figure 17's PDF document. I have done all of that work for you. You can download the code here, and the application's source code appears in Listing 15. As you examine that source code, you will discover that there is nothing you have not seen before.
Listing 15 Planets.java
// Planets.java
import java.awt.Color;
import java.io.*;
import com.lowagie.text.*;
import com.lowagie.text.pdf.*;
class Planets
{
public static void main (String [] args) throws Exception
{
Document doc = new Document ();
PdfWriter writer;
writer = PdfWriter.getInstance (doc,
new FileOutputStream ("planets.pdf"));
writer.setViewerPreferences (PdfWriter.PageModeUseOutlines);
doc.open ();
// Establish a footer that shows the page number between a pair dashes.
HeaderFooter footer = new HeaderFooter (new Phrase ("- "),
new Phrase (" -"));
footer.setAlignment (Element.ALIGN_CENTER);
doc.setFooter (footer);
// Create the title page.
PdfContentByte cb = writer.getDirectContent ();
cb.rectangle (doc.left (), doc.bottom (), doc.right () - doc.left (),
doc.top ()-doc.bottom ());
cb.stroke ();
cb.beginText ();
BaseFont bf = BaseFont.createFont (BaseFont.HELVETICA,
BaseFont.CP1252,
BaseFont.NOT_EMBEDDED);
cb.setFontAndSize (bf, 36);
cb.showTextAligned (PdfContentByte.ALIGN_CENTER, "The Planets",
(doc.right ()-doc.left ()) / 2 + doc.leftMargin (),
(doc.top ()-doc.bottom ()) / 2 + doc.topMargin (),
0);
cb.endText ();
// Establish a title font for all chapter titles.
Font titleFont = new Font (Font.HELVETICA, 18, Font.BOLD,
new Color (0, 0, 128));
// Create the Introduction chapter.
Paragraph title = new Paragraph ("Introduction", titleFont);
title.setAlignment (Element.ALIGN_CENTER);
title.setSpacingAfter (18.0f);
Chapter chapter = new Chapter (title, 0);
chapter.setNumberDepth (0);
Paragraph p = new Paragraph ("The solar system is an amazing place. " +
"We are learning so much about the " +
"planets and their moons, thanks to the " +
"efforts of spacecraft and their human " +
"controllers. This document provides a " +
"brief introduction to our solar " +
"system's nine planets.");
p.setAlignment (Element.ALIGN_JUSTIFIED);
chapter.add (p);
doc.add (chapter);
// Create the Mercury chapter.
title = new Paragraph ("Mercury", titleFont);
title.setAlignment (Element.ALIGN_CENTER);
title.setSpacingAfter (18.0f);
chapter = new Chapter (title, 1);
chapter.setNumberDepth (0);
chapter.setBookmarkOpen (false);
chapter.setBookmarkTitle ("Chapter 1: Mercury");
p = new Paragraph ("Mercury, the closest planet to the sun and the " +
"eighth largest planet, is named after the Roman " +
"god of commerce, travel, and thievery.");
p.setAlignment (Element.ALIGN_JUSTIFIED);
chapter.add (p);
Image image = Image.getInstance ("mercury.gif");
image.setAlignment (Image.ALIGN_MIDDLE);
chapter.add (image);
chapter.add (new Paragraph ("Vital statistics:"));
List list = new List (false, 30);
list.add (new ListItem ("Orbit: 57,910,000 kilometers"));
list.add (new ListItem ("Diameter: 4,880 kilometers"));
list.add (new ListItem ("Mass: 3.3e23 kilograms"));
chapter.add (list);
doc.add (chapter);
// Create the Venus chapter.
title = new Paragraph ("Venus", titleFont);
title.setAlignment (Element.ALIGN_CENTER);
title.setSpacingAfter (18.0f);
chapter = new Chapter (title, 2);
chapter.setNumberDepth (0);
chapter.setBookmarkTitle ("Chapter 2: Venus");
p = new Paragraph ("Venus, the second planet from the sun and the " +
"sixth largest planet, is named after the Roman " +
"goddess of love and beauty.");
p.setAlignment (Element.ALIGN_JUSTIFIED);
chapter.add (p);
image = Image.getInstance ("venus.gif");
image.setAlignment (Image.ALIGN_MIDDLE);
chapter.add (image);
chapter.add (new Paragraph ("Vital statistics:"));
list = new List (false, 30);
list.add (new ListItem ("Orbit: 108,200,000 kilometers"));
list.add (new ListItem ("Diameter: 12,103.6 kilometers"));
list.add (new ListItem ("Mass: 4.869e24 kilograms"));
chapter.add (list);
doc.add (chapter);
// Create the Earth chapter.
title = new Paragraph ("Earth", titleFont);
title.setAlignment (Element.ALIGN_CENTER);
title.setSpacingAfter (18.0f);
chapter = new Chapter (title, 3);
chapter.setNumberDepth (0);
chapter.setBookmarkTitle ("Chapter 3: Earth");
p = new Paragraph ("Earth, the third planet from the sun and the " +
"third largest planet, has a name not derived " +
"from mythology.");
p.setAlignment (Element.ALIGN_JUSTIFIED);
chapter.add (p);
image = Image.getInstance ("earth.gif");
image.setAlignment (Image.ALIGN_MIDDLE);
chapter.add (image);
chapter.add (new Paragraph ("Vital statistics:"));
list = new List (false, 30);
list.add (new ListItem ("Orbit: 149,600,000 kilometers"));
list.add (new ListItem ("Diameter: 12,756.3 kilometers"));
list.add (new ListItem ("Mass: 5.972e24 kilograms"));
chapter.add (list);
doc.add (chapter);
// Create the Mars chapter.
title = new Paragraph ("Mars", titleFont);
title.setAlignment (Element.ALIGN_CENTER);
title.setSpacingAfter (18.0f);
chapter = new Chapter (title, 4);
chapter.setNumberDepth (0);
chapter.setBookmarkTitle ("Chapter 4: Mars");
p = new Paragraph ("Mars, the fourth planet from the sun and the " +
"seventh largest planet, is named after the Roman " +
"god of war.");
p.setAlignment (Element.ALIGN_JUSTIFIED);
chapter.add (p);
image = Image.getInstance ("mars.gif");
image.setAlignment (Image.ALIGN_MIDDLE);
chapter.add (image);
chapter.add (new Paragraph ("Vital statistics:"));
list = new List (false, 30);
list.add (new ListItem ("Orbit: 227,940,000 kilometers"));
list.add (new ListItem ("Diameter: 6,794 kilometers"));
list.add (new ListItem ("Mass: 6.4219e23 kilograms"));
chapter.add (list);
doc.add (chapter);
// Create the Jupiter chapter.
title = new Paragraph ("Jupiter", titleFont);
title.setAlignment (Element.ALIGN_CENTER);
title.setSpacingAfter (18.0f);
chapter = new Chapter (title, 5);
chapter.setNumberDepth (0);
chapter.setBookmarkTitle ("Chapter 5: Jupiter");
p = new Paragraph ("Jupiter, the fifth planet from the sun and the " +
"largest planet, is named after the Roman king of " +
"the gods.");
p.setAlignment (Element.ALIGN_JUSTIFIED);
chapter.add (p);
image = Image.getInstance ("jupiter.gif");
image.setAlignment (Image.ALIGN_MIDDLE);
chapter.add (image);
chapter.add (new Paragraph ("Vital statistics:"));
list = new List (false, 30);
list.add (new ListItem ("Orbit: 778,330,000 kilometers"));
list.add (new ListItem ("Diameter: 142,984 kilometers (equatorial)"));
list.add (new ListItem ("Mass: 1.9e27 kilograms"));
chapter.add (list);
doc.add (chapter);
// Create the Saturn chapter.
title = new Paragraph ("Saturn", titleFont);
title.setAlignment (Element.ALIGN_CENTER);
title.setSpacingAfter (18.0f);
chapter = new Chapter (title, 6);
chapter.setNumberDepth (0);
chapter.setBookmarkTitle ("Chapter 6: Saturn");
p = new Paragraph ("Saturn, the sixth planet from the sun and the " +
"second-largest planet, is named after the Roman " +
"god of agriculture.");
p.setAlignment (Element.ALIGN_JUSTIFIED);
chapter.add (p);
image = Image.getInstance ("saturn.gif");
image.setAlignment (Image.ALIGN_MIDDLE);
chapter.add (image);
chapter.add (new Paragraph ("Vital statistics:"));
list = new List (false, 30);
list.add (new ListItem ("Orbit: 1,429,400,000 kilometers"));
list.add (new ListItem ("Diameter: 120,536 kilometers (equatorial)"));
list.add (new ListItem ("Mass: 5.68e26 kilograms"));
chapter.add (list);
doc.add (chapter);
// Create the Uranus chapter.
title = new Paragraph ("Uranus", titleFont);
title.setAlignment (Element.ALIGN_CENTER);
title.setSpacingAfter (18.0f);
chapter = new Chapter (title, 7);
chapter.setNumberDepth (0);
chapter.setBookmarkTitle ("Chapter 7: Uranus");
p = new Paragraph ("Uranus, the seventh planet from the sun and the " +
"third largest planet (by diameter), is named " +
"after the Greek deity of the Heavens.");
p.setAlignment (Element.ALIGN_JUSTIFIED);
chapter.add (p);
image = Image.getInstance ("uranus.gif");
image.setAlignment (Image.ALIGN_MIDDLE);
chapter.add (image);
chapter.add (new Paragraph ("Vital statistics:"));
list = new List (false, 30);
list.add (new ListItem ("Orbit: 2,870,990,000 kilometers"));
list.add (new ListItem ("Diameter: 51,118 kilometers (equatorial)"));
list.add (new ListItem ("Mass: 8.683e25 kilograms"));
chapter.add (list);
doc.add (chapter);
// Create the Neptune chapter.
title = new Paragraph ("Neptune", titleFont);
title.setAlignment (Element.ALIGN_CENTER);
title.setSpacingAfter (18.0f);
chapter = new Chapter (title, 8);
chapter.setNumberDepth (0);
chapter.setBookmarkTitle ("Chapter 8: Neptune");
p = new Paragraph ("Neptune, the eighth planet from the sun and the " +
"fourth largest planet, is named after the Roman " +
"god of the sea.");
p.setAlignment (Element.ALIGN_JUSTIFIED);
chapter.add (p);
image = Image.getInstance ("neptune.gif");
image.setAlignment (Image.ALIGN_MIDDLE);
chapter.add (image);
chapter.add (new Paragraph ("Vital statistics:"));
list = new List (false, 30);
list.add (new ListItem ("Orbit: 4,504,000,000 kilometers"));
list.add (new ListItem ("Diameter: 49,532 kilometers (equatorial)"));
list.add (new ListItem ("Mass: 1.0247e26 kilograms"));
chapter.add (list);
doc.add (chapter);
// Create the Pluto chapter.
title = new Paragraph ("Pluto", titleFont);
title.setAlignment (Element.ALIGN_CENTER);
title.setSpacingAfter (18.0f);
chapter = new Chapter (title, 9);
chapter.setNumberDepth (0);
chapter.setBookmarkTitle ("Chapter 9: Pluto");
p = new Paragraph ("Pluto, usually the farthest planet from the sun " +
"and the smallest planet, is named after the " +
"Roman god of the underworld.");
p.setAlignment (Element.ALIGN_JUSTIFIED);
chapter.add (p);
image = Image.getInstance ("pluto.gif");
image.setAlignment (Image.ALIGN_MIDDLE);
chapter.add (image);
chapter.add (new Paragraph ("Vital statistics:"));
list = new List (false, 30);
list.add (new ListItem ("Orbit: 5,913,520,000 kilometers (average)"));
list.add (new ListItem ("Diameter: 2274 kilometers"));
list.add (new ListItem ("Mass: 1.27e22 kilograms"));
chapter.add (list);
doc.add (chapter);
// Create the Appendix chapter.
title = new Paragraph ("Planetary Comparison", titleFont);
title.setAlignment (Element.ALIGN_CENTER);
title.setSpacingAfter (18.0f);
chapter = new Chapter (title, 0);
chapter.setNumberDepth (0);
chapter.setBookmarkTitle ("Appendix A: Planetary Comparison");
p = new Paragraph ("The following table compares the nine solar " +
"system planets in terms of orbit, diameter, and " +
"mass. It only shows two planet entries because " +
"I want you to add the remaining planet entries " +
"to this table (as an exercise). Also, feel free " +
"to expand this table with additional columns " +
"that present other statistics, if desired.");
p.setAlignment (Element.ALIGN_JUSTIFIED);
p.setSpacingAfter (24.0f);
chapter.add (p);
PdfPTable table = new PdfPTable (4);
table.addCell ("Planet");
table.addCell ("Orbit (km)");
table.addCell ("Diameter (km)");
table.addCell ("Mass (kg)");
table.addCell ("Mercury");
table.addCell ("57,910,000");
table.addCell ("4,880");
table.addCell ("3.3e23");
table.addCell ("Venus");
table.addCell ("108,200,000");
table.addCell ("12,103.6");
table.addCell ("4.869e24");
chapter.add (table);
doc.add (chapter);
doc.close ();
}
}
After reviewing the source code, you might be wondering why chapter.setBookmarkOpen (false);—part of the code that creates the first chapter—is present. This method call exists to suppress the display of a chapter's sections on the Bookmarks pane. But Listing 15 adds no sections to any chapter. What gives?
The method call is a hint that the source code is unfinished. I believe the best way for you to understand this iText tool is to continue developing Listing 15. For example, after adding the remaining seven rows to the table in the final chapter (the appendix), add some color to that table. Also, add a section to each of the planet chapters that presents information on (and images of) the various moons that orbit the planet. You might even want to discuss the various spacecraft that have visited the planet. Only your imagination limits what you can do to this document with iText.
Conclusion
Useful tools can help you become a successful Java developer. This article inaugurated a three-part series that introduces three open-source Java tools I have found to be very useful. The focus of this article: iText.
iText is a Java library for creating PDF documents. This article's introduction to that library started you on a journey that you will want to continue by exploring the iText Tutorial. As you become better acquainted with iText through that tutorial, you will learn how to use iText in combination with servlets to deliver PDF documents to Web browsers, how to directly control what gets written to the output stream from inside a PDF document writer, how to encrypt documents, how to introduce watermarks into documents, and more.
The second article in this series will introduce you to JGraph.
