Printing in Java2

You might also like

Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 9

Printing in Java, Part 2 - Print your first page and render complex documents

By Jean-pierre Dubé, JavaWorld.com, 12/01/00

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 {
printJob.print();
}
catch (Exception PrintException) {
PrintException.printStackTrace();
}
}

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.

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/font.properties 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 font.properties 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:
Name Type Description
The Font class represents an instance
Font of a font face. Use this class to create
Class
a new font based on the available
fonts on the target system.
This class contains information about
a font and the rendering device.
FontMetrics features many utilities,
one of which obtains a string's width.
Class Note that this class is abstract; to
FontMetrics
(Abstract) 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.
This class provides information about
a font related to the rendering device
FontRenderContext Class and the rules required to render such
a font. Rules define the quality of the
rendering.
Classes included in the Java Graphics API
Note: Links to javadocs for these classes are available in
Resources

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


TextLayout offers a great deal of functionality in rendering high quality text. This class can
render bidirectional text such as Japanese text, where figures align right to left instead of the
North American style, which flows left to right. The TextLayout class offers some additional
functionalities that we will not use in the course of this series. Features such as text input,
caret positioning, and hit testing are not necessary for printing documents, but it's good to
know that these features exist.
TextLayout lays out paragraphs, but it doesn't work alone. To arrange text within a specified
width, it needs the help of the LineBreakMeasurer class. LineBreakMeasurer will wrap a
string of text to fit a predefined width. Since it's a multilingual class, it knows exactly where to
break a line of text according to each language's rules. The LineBreakMeasurer class also
does not work alone. It needs information from the FontRenderContext class, which, as its
main function, returns accurate font metrics. To measure text effectively, FontRenderContext
must know the rendering hints for the targeted device and the font type being used.
Before I explain the first example of a TextLayout application, I need to introduce a new class,
AttributedString. This class is extremely helpful when you want to embed attribute
information within a string. For example, let's say that we have the following sentence:
This is a Bold attribute.

If you printed this string without the AttributedString class, you would use the
Graphics.drawString() method, and the code would look something like this:

Font normalFont = new Font ("serif", Font.PLAIN, 12);


Font boldFont = new Font ("serif", Font.BOLD, 12);
g2.setFont (normalFont);
g2.drawString ("This is a ");
g2.setFont (boldFont);
g2.drawString ("bold ");
g2.setFont (normalFont);
g2.drawString ("attribute", 72, 72);

Look how much code we needed just to print a simple line of text. Imagine if we had to render
an entire paragraph! We can simplify this example with an AttributedString object.
AttributedString is a string class that supports absolute attributes, which require that you
specify the start and the end position of attributes.
Here is our previous example using AttributedString:

AttributedString attributedString = new AttributedString ("This is


a Bold attribute");
attributedString.addAttribute (TextAttribute.WEIGHT,
TextAttribute.WEIGHT_BOLD, 11, 14);
g2.drawString (attributeString.getIterator (), 72, 72);

In the above example, the word Bold is in boldface type. To apply a bold attribute to the word
Bold, use the addAttribute() method. The first parameter represents the attribute key,
which identifies the family of the attribute you want to set. For example, a
TextAttribute.WEIGHT's attribute key indicates that the set attribute will be of type weight.
The second parameter is the attribute itself. If we set the first parameter to
TextAttribute.WEIGHT, then the available attributes are WEIGHT_DEMIBOLD,
WEIGHT_DEMILIGHT, WEIGHT_EXTRA_LIGHT, WEIGHT_EXTRABOLD, WEIGHT_HEAVY, WEIGHT_LIGHT,
WEIGHT_MEDIUM, WEIGHT_REGULAR, WEIGHT_SEMIBOLD, and WEIGHT_ULTRABOLD.
The third parameter marks the position at which the program should begin applying the
attribute, and the fourth and last parameter indicates where it should stop. You can also apply
an attribute to an entire string by using the same method without supplying the start and end
parameters. Figure 5 illustrates the structure of the text example in an AttributedString.

Figure 5. Structure of an AttributedString

Try the TextLayout


We have now acquired an understanding of all the classes that we need to work with the
TextLayout class. Example 4 from this article's source code (available in Resources)
implements TextLayout to render a paragraph; Listing 4 provides an excerpt. The width of the
paragraph has been set to 7.5 inches. The TextLayout code is located between lines 162 and
235.
Listing 4

To summarize Listing 4: We created an AttributedString. The addAttribute() method sets


the font. We then create a LineBreakMeasurer with two parameters passed to it, a character
iterator from the AttributedString, and a FontRenderContext to supply the necessary font
metrics. In return, the LineBreakMeasurer will provide a TextLayout object for each wrapped
line. The loop completes some basic math to position each line of text. Keep in mind that fonts
are not positioned from their top left corner, like rectangles. Instead, the y=0 is located at the
baseline of the font. Also, if you want to properly position one line of text on top of another,
you must add the descend and the leading together. Refer back to Figure 1 for a graphical
representation of the structure of a font.
The next example will implement full justification. We need to add a few lines of code to the
previous example. Here is an excerpt of Example 5:

Listing 5

Listing 5's most interesting code section stretches from line 219 to line 259. Although the
TextLayout class features a method that fully justifies a line of text, the LineBreakMeasurer
includes no functionality for detecting the paragraph's last line of text. Since a fully justified
paragraph justifies all lines but the last, we need a way to detect the paragraph's last line and
print it without justification.
In order to detect the last line, we must first wrap all lines and store them in a Vector. Then a
for loop will scan each line to check if it's the last line of the paragraph. If it's not, the
getJustifiedLayout() method justifies it. If it is indeed the last line, it is rendered as is.

Printing images
Printing an image is as straightforward as loading it in an Image object and invoking the
Graphics2D.drawImage() method. The following example illustrates how to print the NASA
Space Station image; see lines 165 and 198 for the actual code that prints the image.
Listing 6

The process of printing an image is divided into three easy steps.

1. Create a URL that will point to the image you want to print.
2. Load the image using a MediaTracker class. With this class, you can load GIF, JPEG, and PNG
files. If you want to load other image types, use the Advance Imaging API. Check out JavaWorld's Java
Tip 43 for more information on loading bitmaps (direct link available in Resources).
3. Draw the image using the drawImage() method of the Graphics2D class. drawImage() needs
six parameters. The first is the Image object to print; the second and third parameters are coordinates
for the image's top left position, and the fourth and fifth parameters specify the image's width and height.
The last parameter is for the ImageObserver. For the purpose of this example, the Document extends
the Component class. All Component classes implement the ImageObserver interface.

By following these three easy steps, you can easily render images on paper. If you need to
support more formats, you can always download the Advanced Imaging API from the JavaSoft
Website.

Printing on multiple platforms


Although Java has been qualified as a WORA language, it must rely on some services provided
by the underlying operating system. In order to validate the issues of printing on multiple
platforms, I set up a Linux machine with SuSE 6.4 and Sun Java 1.3. To make the comparison
fair between Windows 2000 and Linux, I connected the same HP 5L printer to both computers.
My main concern was not the output quality, but how Java handles the differences between
Windows, which provides standard print and page setup dialogs, and Linux, which doesn't
support standard dialogs.

The first problem I encountered was configuring the printer. SuSE Linux uses the APS print
filter, and I could not configure my HP 5L to print properly at 600 by 600 dpi. My printer only
worked when set as an HP LaserJet 4 with a resolution of 300 dpi instead of the 600 dpi it
could produce if used with the proper driver.

I ran my code on Linux without any changes made from the Windows version, and, despite the
resolution difference, it printed with excellent quality and superb performance. The display of
the print and page dialogs surprised me. They appeared completely different from their
Windows counterparts. Figures 6 and 7 show these dialogs:

Figure 6. Page dialog in Linux

Figure 7. Print dialog in Linux

The drastic differences in dialogs will deprive your application of some functionality. For
example, under Linux, you cannot use the print dialog to select a page range, while you can
under its Windows counterpart. In addition, Linux's page dialog has no support to set the
margins. If you're writing portable applications, you will have to deal with these major
disparities and test your applications on all targeted platforms. As I don't yet have access to a
Mac running OS X, I cannot offer an example with this platform. I will attempt to access this
new OS soon and share my results in a future article.  

Conclusion
Part 2 introduced you to the pitfall of the Java Print API: it only provides a graphic canvas to
draw on. As the API lacks such concepts as paragraphs, images, running headers/footers, and
tables, you must make primitive calls to it to add functionality. I also touched on the issue of
printing on multiple platforms and described the differences between printing under Linux and
printing under Windows. In Part 3, I will explain the design of the print framework that we will
build in the remaining three articles. Until then, happy printing!

You might also like