Printing in Java, Part 5

Discover the print framework's support classes

Welcome to the fifth and final installment of "Printing in Java." In Part 1 you learned about the different models that the Java API uses to produce printed output, and Part 2 presented code examples of those models. Part 3 introduced you to the print framework. Most recently, in Part 4, you learned about the base classes that form the print framework, as I explained the PFDocument, PFPage, PFPrintObject, and all the measurement classes. In Part 5 I will focus my explanations on what I call the support classes of the print framework. I will start with the PFParagraph class.

Handling text

The PFParagraph class is essential to the print framework. It provides all the basic functions needed to render text. By using PFParagraph, you will be able to render text with the following alignments: left, right, center, and fully justified. In addition, the class enables you to set the text color and the font size. PFParagraph also supports two text input methods. You can use either the standard String class, or if you need a more elaborate way of inputting text, you can use an AttributedString.

PFParagraph also provides all the previously explained functions of the PFPrintObject. Although PFParagraph does not provide direct support for margins, you can use the implementation provided by the PFPrintObject. You will find PFParagraph's code in Listing 1.

Listing 1: PFParagraph As most of PFParagraph is pretty straightforward, I will focus only on its rendering methods. The rendering process starts in the print method located at line 179 in Listing 1. First, the method sets the text color and the text font. Then, depending on the horizontal alignment, the method calls the proper text renderer. When finished with the rendering process, the print method calls the printChilds() method to render any child object that it may contain.

There are four private methods available to render text: renderLeftJustfied(), renderRightJustified(), renderCenterJustified(), and renderFullyJustified(). The renderLeftJustified() method (line 387) uses nearly the same code as that presented in Part 2, Listing 4.

Line 325 shows how renderCenterJustified() differs from renderLeftJustified(). To calculate the object's center, divide the width of the object by two. Then divide the width of the string -- which the layout.getAdvance() method obtains -- by two. Next, subtract the object's center from the string's center to obtain the position where the string will be rendered.

The renderRightJustified() method resembles renderLeftJustified() except that you subtract the string's width from the object's width (i.e., you compute the same math without the divisions).

The renderFullyJustified() method differs from the other private rendering methods. With renderLeftJustfied(), renderRightJustified(), and renderCenterJustified(), all paragraph lines are justified, whereas in a fully justified paragraph, the last line should not be justified. Since the LineBreakMeasurer class cannot determine when it has reached the last line in a paragraph, you must implement that function yourself. Fortunately, the solution to the problem is simple: just scan and store each line in the paragraph in a Vector object (lines 423-449). Then, the method enters into a for loop to render each line stored in the previously created Vector. Use an if statement (line 459) to trap the last line and render it as left justified. At line 460, note the use of the getJustifiedLayout() method; it fully justifies a line of text, a useful feature of the TextLayout class.

To ease the layout of paragraphs on a page, I implemented the following two methods in PFParagraph: getTextHeight(), which obtains the height of a paragraph, and getNextParagraphPosition(), which obtains the next paragraph location.

Use the getNextParagraphPosition() method when you want to lay out several paragraphs one after the other, as shown in Figure 1:

Figure 1. Paragraph layout using the getNextParagraphPosition method

To achieve the result in Figure 1, I used the following code excerpt:

 1|PFParagraph paragraph = new PFParagraph ();
 2|PFParagraph paragraph1 = new PFParagraph ();
 3|
 4|page.add (paragraph);
 5|paragraph.setText (text);
 6|paragraph.setHorizontalAlignment (PFParagraph.FULL_JUSTIFIED);
 7|paragraph.setSize (new PFSize (page.getPrintableAreaSize ().getWidth (), new PFInchUnit (6)));
 8|
 9|page.add (paragraph1);
10|paragraph1.setText (text1);
11|paragraph1.setHorizontalAlignment (PFParagraph.FULL_JUSTIFIED);
12|paragraph1.setSize (new PFSize (page.getPrintableAreaSize ().getWidth (), new PFInchUnit (6)));
13|paragraph1.setPosition (paragraph.getNextParagraphPosition ());

For the sake of clarity, assume that the text was assigned to

paragraph

and

paragraph1

. The trick resides at line 13, where

paragraph

's

getNextParagraphPosition()

method calls

setPosition()

to obtain the next paragraph's position. Suppose you want to print a file using the framework. To do that, create a

PFParagraph

instance for each line and use the

getNextParagraph()

method to lay out each line. To know when it's time to create another page, use the

getTextHeight()

method. Let's review what you have learned so far about rendering text using the print framework. The

PFParagraph

class enables you to render text with the following justifications: left, center, right, and full. The class will accept text as either a

String

or an

AttributedString

object. The

PFParagraph

will also provide metrics to help you lay out text objects. Next, I will introduce the drawing primitives available in the framework.

Drawing primitives

The print framework offers three drawing primitives

  1. The PFFrame to draw rectangles
  2. The PFLine to render lines
  3. The PFCircle to render circles

Those three primitives enable you to enhance your printed output and can form the base for drawing more complicated figures.

Rectangles

In the print framework, the PFFrame class represents a rectangle drawing. Don't confuse it with the PFRectangle, which the measurement system uses. You can render rectangles with several attributes: line thickness, fill color, and line color. Since all the attribute methods are basically setters/getters, I will focus on the print() method located between lines 130 and 170 in Listing 2.

Listing 2: PFFrame

First, you compute the size and position of the PFFrame object by calling the computePositionAndSize() method (line 145). Next, a Rectangles2D.Double object is created (line 148). Use that rectangle object as your rendering object. Notice the use of the getDrawingOrigin() and getDrawingSize() methods, which set the rectangle's position and size. To set the line thickness, use a BasicStroke object (lines 160-161). Finally, the rendering process takes place at line 163. But just before you print the child objects, restore the Graphics2d object to its previous color.

As the PFCircle and PFLine classes work in a similar way as PFFrame, I will not go into their details.

The next rendering class that you will explore is the PFImage class, which will enable you to render GIF and JPEG images.

Images

At first glance, rendering images might look like a tedious process, but thanks to the Java Print API, rendering images has never been easier. The PFImage class implementation is also extremely simple. To render an image, you must set the image URL using the setURL() method. The method will save the URL and load the image in memory. The print() method, located between lines 76 and 98 in Listing 3, uses the same structure as the previously explained print methods. The computePositionAndSize() is called first, and then the Image object is rendered. Finally, the child objects are rendered.

Listing 3: PFImage

Of course, you could extend this method to support more image formats. You could use the JAI (Java Advanced Imaging) library from Sun to extend this class or use JavaWorld's Java Tip 43 (Jeff West, with John D. Mitchell) to add support for 8- and 24-bit bitmap images.

Print Preview window

I started to present the structure of the print-preview window in Part 3. Recall that the print-preview window is divided into two major classes. The first one, the PFPrintPreview, renders the page on screen; the second class, the PFPrintPreviewToolbar, presents the toolbar and calls the appropriate method when the user selects a button. The PFPrintPreviewToolbar is an inner class of the PFPrintPreview. Let's take a look at the PFPrintPreviewToolbar class first, located between lines 369 and 488 of Listing 4.

Listing 4: PFPrintPreview

Aside from creating the toolbar, the only other thing worth mentioning about PFPrintPreviewToolbar is that you must pass a PFPrintPreview object to the constructor (line 403). The toolbar sends user requests to the PFPrintPreview. The ActionPerformed() method completes the routing (lines 462-486).

The PagePanel class performs a page's rendering process (lines 270-366). I will focus my explanations on the paint() method (lines 338-364). The PagePanel class implements a JPanel object. You override the paint() method to render the page. Next, create a scaleFactor variable (line 348) that keeps the page's proportion relative to a printed page. At this time, the print preview supports only letter-sized pages. For an 8.5-by-11-inch page, the scale factor is 0.77. You obtain this value by dividing the page's width by its height.

At this point, a BufferedImage object is created (line 350). That object will be used to render the page, then the image is rendered later on the screen. Next, paint the background white (line 352). Subsequently, you scale the drawing area in accordance with the paper size by using the previously calculated scale factor and applying it to the g2d object.

Finally, render the page (lines 358-359). First, make sure that you don't render a nonexistent page, then call the page.print() method with the Graphics2D object. After those two lines of code, the page will be rendered to your doubleBuffer image object. The last step is to render the image on the screen (line 361-362).

That concludes the discussion on the PFPrintPreview class. You have learned how the classes involved in the print preview system interact with one another to enable users to preview documents on screen. Our discussion will now focus on the PFPageSetupDialog.

Page-setup dialog

Using the simple dialog PFPageSetupDialog, you can select the paper size, margins, and -- in the future, when JDK 1.4 is ready -- the paper source. You use this class in conjunction with the PFPageFormat class. Recall from Part 4 that PFPageFormat acts as a container for page metrics.

To use the PFPageSetupDialog, you need to pass a PFPageFormat object to the constructor. The dialog will then show the PFPageFormat values to the user. The user is free to accept or change the page-format values. Figure 2 displays the page-setup dialog:

Figure 2. Page-setup dialog.

Let's take a look at the implementation:

Listing 5: PFPageSetupDialog

To ease the layout of components in the dialog, use a FormPanel object that implements a FormLayout layout manager (see Resources below for more information on FormLayout). With FormLayout, you can treat labels and input fields as one entity. You then position the label/field pairs by using a row/column value. Believe me, FormLayout is a lot easier to use than the GridBagLayout.

Initialize the dialog box (lines 88-91). The setFields() method is then called (line 94), which assigns the parameters stored in the PFPageFormat to their related fields. Then, the layout of the fields is achieved (lines 95-100). See how easy it is to lay out fields using the FormPanel class! Next, the two buttons on the dialog -- Accept and Cancel -- are initialized.

When the page-setup dialog is displayed, the user can either accept the changes he or she made by selecting the Accept button, or ignore the changes by choosing the Cancel button. Take a look at the ActionPerformed() method at line 182. When the user selects Accept, the getFields() method is called to save the fields' values back to the PFPageFormat object. Then, the dialog box is discarded. If the user chooses Cancel, the dialog is disposed of without saving the values to the PFPageFormat object.

The page-setup dialog provides a platform-independent dialog and fully integrates itself with the print framework.

I will now move on to a subject that has generated many emails, printing visual components. To add this function to the print framework, I created the PFVisualComponent.

Printing AWT/Swing components

Because visual components rely on layout managers, printing those components can be tricky. To circumvent problems, I created a component image and rendered the image instead of the component itself. You can more easily position and resize an image on a canvas.

Take a look at the print method located between lines 58 and 101 in Listing 6; you will see that I created BufferedImage to hold the component's image.

Listing 6: PFVisualComponent

The component is asked to render itself on the double-buffer image. And finally, the image of the component renders on the page.

1 2 Page 1
Page 1 of 2