Printing in Java, Part 2

Print your first page and render complex documents

This month, we will put into practice what we learned in Part 1 of this five-part series on printing in Java. In Part 1, I presented the two printing models: the Printable and the Pageable. I also discussed how to use the Book class to create documents. You will start Part 2 by printing your first page; then you will move on to render more complex documents with Java 2D. In addition, I will explain how to use fonts and all their related classes. And, as promised last month, I will examine the issues concerning printing on different platforms. Note that all examples were compiled and executed using Sun Java 1.3 on both Windows 2000 and Linux.

Print your first page

In Part 1, you learned that the print system in Java has two distinct models: the Printable and the Pageable. Though Printable can print simple documents, it features several limitations, the major one being that all pages must share the same format. The Pageable model, on the other hand, offers much more flexibility. Used in conjunction with the Book class, it can create multipage documents, with each page formatted differently. Listing 1 shows how to print using the Printable model.

Note: All examples, as well as their accompanying source code, can be downloaded from the Resources section below.

Listing 1

Listing 1 will print a half-inch by half-inch grid using the default margin setting, usually one inch for the top, bottom, left, and right margins. Please note that if you try to execute this example with Java 1.2, the margins will not fit; the left margin will be slightly larger than one inch. A bug in Java 1.2's print API causes this glitch.

Now, let's go through the steps required for printing with the Printable model:

  1. Create a PrinterJob object. This object controls the print process by displaying page and print dialogs, and initiating the print action.
  2. Display the proper dialogs, either print or page dialogs.
  3. Create a class that implements the Printable interface's print() method.
  4. Validate the page number to be rendered.
  5. Render your page using the Graphics parameter.
  6. If the page renders, return the PAGE_EXISTS value; if the page does not render, return the NO_SUCH_PAGE value.

Use Pageables and Books to print

As you learned from the previous example, printing with Printable is a straightforward process, but doesn't offer much flexibility when it comes to handling more complex documents. For these types of tasks, the Pageable and the Book classes come in handy. As explained in Part 1 of this series, the Book class allows you to choose whether each Book's page will use a different page painter or the same page painter as other pages. To see the Pageable and the Book at work, let's begin with this example:

Listing 2

This example will print a two-page document using a painter for the cover page and another painter for the document itself. The cover page will print on a portrait-sized sheet, while the second page will be set up to print in a landscape format. Notice that in Listing 2, each painter prints one page; I don't validate the page parameter. To use a painter to print more than one page, consider the next example:

Listing 3

In Listing 3, the code in line 44 adds a third page using the same painter as the first page. In lines 164 through 171, the if statement checks for the right page to render. Since this is a simple example, the code to validate the rendered page is also easy. But, as you discovered from the previous examples, paging a document can be complex. The print framework that I introduce later in this series will allow you to concentrate on the task at hand instead of completing fancy calculations to fit your document on a page.

Now that you know how to render pages using the print API, it might be useful to present a dialog with which the user can choose the destination printer. You also might want to give the user an opportunity to change the page format and margins. The Java Print API offers two dialog windows, the printer dialog and the page dialog.

Use the print and the page setup dialogs

In previous versions of Java, you were required to show the print dialog box only. Starting with Java 1.2, you can display either the print or the page dialog. This enables you to create unattended print jobs, an ability that becomes useful when you want to set a server to print documents. Using dialogs on a desktop-type application promotes user-friendliness. Let's take a look at the print dialog, as shown in Figure 1.

Figure 1. Print dialog

With the print dialog box, the user can select a printer and modify its configuration. The user can also set the number of copies to print and select the page range. Using this dialog, you transfer control of the print job to the user. The dialog box provides the only place where the user can decide to proceed with the print job or cancel it.

Below, you will find a small code excerpt that displays the print dialog:

   if (printJob.printDialog()) {
                                       try {
                                       catch (Exception PrintException) {

The printDialog() method, part of the PrinterJob class, shows the print dialog to the user. This method returns a Boolean value. If the user selects the Print button, printDialog() will return true; if the user selects the cancel button, the method returns a false value, and the print job will not proceed. printDialog() only interacts with your application by returning the Boolean. None of the parameters set in this dialog will affect any of PrinterJob's objects.

The second dialog is the page dialog. With this dialog, the user can change the page setup. Figure 2 illustrates an example of a page dialog:

Figure 2. Page dialog

Using this dialog, the user can choose all the parameters of the PageFormat object, including the paper size, the paper source, the orientation of the page, and the margins.

The code excerpt below shows how to invoke the page dialog:

   PageFormat documentPageFormat
                                       = new PageFormat ();
                                       documentPageFormat = printJob.pageDialog (documentPageFormat);
                                       book.append (new Document (), documentPageFormat);    

If the user selects the OK button, the pageDialog() method will return a clone of the PageFormat object reflecting the user's setup. If the user selects the Cancel button, then the method returns an unaltered copy of PageFormat.

You have now completed the first step on your path to understanding printing in Java. Let's move on to learn how Java handles text.

Handling text and fonts

Rendering text probably represents the most complex aspect of printing. Long gone are the days of daisy wheel and dot matrix printers, when we only needed to know the number of characters per inch and the width of the page in characters. Full justification of a paragraph was simple, too; we just added spaces between each word until the text aligned with the right margin. To align text vertically, we only sent a CR/LF code to the printer and started printing the next line. Wow, has printing changed! Using today's technology to print two lines of text, one on top of the other, demands much more knowledge, including how to use fonts.


If you want to render eye-appealing text, you must first understand the structure of a font. Figure 3 illustrates how fonts are laid out. Characters align along a line called the baseline, which is the vertical point of reference for positioning a font. The ascend is the distance between the baseline and the top of the tallest character in a string. The space between the baseline and a string's lowest glyph is the descend. The leading represents the vertical distance between two characters, and the font height is the ascend plus the leading plus the descend. Finally, we call a string's length the advance. All of these parameters are font dependent, meaning that text rendered using an Arial font will not occupy the same physical space as the same text rendered using a Lucida font.

Figure 3. Font definition

The process of positioning fonts differs from positioning a standard geometrical figure. A rectangle's top left corner sets its origin; the baseline on the Y axis and the first character on the X axis mark a font's origin. See Figure 4 to see the font origin's location.

Figure 4. Font origin

Two styles of fonts are available:

  • Serif fonts feature short lines stemming from and at an angle to the upper and lower ends of a letter. Times New Roman is an example of a serif font.
  • Sans serif fonts do not have any decorative elements. The Arial font is a sans serif font.

Each of these font styles can be either monospaced or proportional. A monospaced font is a font in which all characters have the same width. Old printers, such as daisy wheel or dot matrix printers, used monospaced fonts, as do many text-based interfaces. Each character in a proportional font spans a different width. We were introduced to proportional fonts when laser printers came out. The first commercial computers to use proportional fonts on both screen and printer were Apple's Lisa and Macintosh -- for those that can remember that far back.

Accessing fonts

Since Java was designed to be written once and run anywhere (WORA), Java creators had to produce a default font family that all Java-supported platforms could access. Even more importantly, Java needed to provide a selection of default fonts, whether or not the peer operating system includes them. Java offers eight default fonts: Serif, Sans Serif, Dialog, Dialog Input, Lucida Sans, Lucida Sans Typewriter, Lucida Bright, and Monospace.

To stay on the WORA bandwagon, Java uses logical font names. These names automatically map to an operating system's fonts. The mapping occurs in the JDK directory's jre/lib/ file. In this file, you would discover how a particular operating system font maps to Java's Serif font, for example. A logical font name is a name assigned by Java to an operating system font. If you look at the file, the logical name is the name that appears on the left side of the equal sign. As an example, Serif is a logical font name that maps to Times New Roman on my computer.

Aside from the logical font name, you can also access fonts by their font face name or font name. The font name is the name given by the font creator, such as Arial, Courier, Times Roman, or Lucida. An example of a font family is Arial; Arial Bold, Arial Italic and Arial Regular are members of the Arial font family.

If your application needs access to more fonts, set the java.awt.fonts parameters of the Java command -- as in java -Djava.awt.fonts=[fonts directory] ... -- to point to a font directory of Type 1 (Postscript) fonts or True Type fonts. This offers a convenient approach for broadening your application's fonts without adding them to the operating system. If your application will run on multiple platforms and you want to make a specific font available to your application, pack the font in your application's jar file.

Related font classes

The Java Graphics API supplies many classes that alleviate the task of manipulating fonts. The definitions of some of the more useful classes follow in the table below:

Classes included in the Java Graphics API

Note: Links to javadocs for these classes are available in


FontClassThe Font class represents an instance of a font face. Use this class to create a new font based on the available fonts on the target system.
FontMetricsClass (Abstract)This class contains information about a font and the rendering device. FontMetrics features many utilities, one of which obtains a string's width. Note that this class is abstract; to acquire an instance, you must call Graphics.getFontMetrics(). Use FontMetrics to learn a font's basic metric information, such as its descend, ascend, leading, or advance.
FontRenderContextClassThis class provides information about a font related to the rendering device and the rules required to render such a font. Rules define the quality of the rendering.

Please refer to the JDK documentation for a complete explanation of each of these classes. To learn how to use these classes, refer to the code examples in this article.

It's not easy to render paragraphs of text by manipulating raw fonts. Writing an algorithm to fully justify text that has a proportional font is a laborious task. And adding support for international characters only adds to the complexity. That's why we will use the TextLayout and the LineBreakMeasurer classes to render text.

TextLayout to the rescue

1 2 Page 1
Page 1 of 2