Printing in Java, Part 2

Print your first page and render complex documents

1 2 Page 2
Page 2 of 2

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!

Jean-Pierre Dubé is an independent Java consultant. He founded Infocom in 1988. Since then, Infocom has developed custom applications in fields including manufacturing, document management, and large-scale electrical power line management. Jean-Pierre has extensive programming experience in C, Visual Basic, and Java; the latter is now the primary language for all new projects. He dedicates this series to his mother, who passed away while he was writing this article.

Learn more about this topic

1 2 Page 2
Page 2 of 2