5.2. Printing in Java 1.2Java 1.2 introduces a more complete printing API. As in Java 1.1, printing is done by calling methods of a special Graphics object that represents the printer device. The printer coordinate system and base resolution of 72 points per inch are the same in both Java 1.1 and Java 1.2. Beyond these similarities, however, the Java 1.2 API flips the Java 1.1 API upside down. Instead of asking a PrintJob object for the Graphics object to draw to, Java 1.2 uses a callback model. You tell the Java 1.2 printing API the object you'd like to print, and it calls the print() method of that object, passing in the appropriate Graphics object to draw to. In Java 1.1, your printing code is in charge of the print job, while in Java 1.2, the print job is in charge of your printing code. The Java 1.2 printing API is contained in the java.awt.print package. Key classes and interfaces in this package are Printable, which represents a printable object, Pageable, which represents a multipage printable document, and PrinterJob, which coordinates the print job and serves as an intermediary between the Java API and the native printing system. Do not confuse java.awt.print.PrinterJob with the java.awt.PrintJob class used by the Java 1.1 printing API! Another important class is PageFormat, which represents the size of the paper being printed on, its margins, and the printing orientation (i.e., portrait mode or landscape mode). The basic Java 1.2 printing algorithm includes the following steps:
5.2.1. Printing Single-Page ObjectsWhen the object, figure, or document you want to print fits on a single printed page, you typically represent it using the Printable interface. This interface defines a single method, print(), that the PrinterJob calls to print the page. The print() method has three arguments. The first is the Graphics object that represents the printer. print() should do all of its drawing using this object. This Graphics object may be cast to a Graphics2D object, enabling all the features of Java 2D, including the use of floating-point coordinates to position graphics elements with more precision than is possible with integer coordinates. The second argument to print() is a PageFormat object. Your print() method should call the getImageableX(), getImageableY(), getImageableWidth(), and getImageableHeight() methods of PageFormat to determine the size and position of the area that it should draw in. Note that these methods are poorly named. The values they return represent the page and margin sizes requested by the user, not the size of the paper actually available in the printer or the imageable area of the printer (i.e., the region of the page that a specific type of printer can actually print to). The third argument is a page number. Although the Printable interface is most useful for single-page documents, it can be used for multipage documents. The PrinterJob has no way to determine how many pages a Printable object requires. Indeed, a Printable object may be implemented in such a way that it does not know how many pages it requires either (e.g., a PrintableStream object that prints a stream of text as it arrives). Because the page count is not known in advance, the PrinterJob calls the print() method repeatedly, incrementing the page number after printing each page. One important responsibility of the print() method is to notify the PrinterJob when all pages are printed. Your method does this by returning the constant Printable.NO_SUCH_PAGE when the PrinterJob asks it to print a page that is past the end of the document. It is also important to implement the print() method so that it can be called more than once for each page. As of this writing, Sun's Java 1.2 printing implementation calls the print() method at least twice for each page (we'll see why at the end of this chapter). Example 5-1 shows a PrintableComponent class that can be used to print the contents of a Swing component, applet, or custom AWT component. This class is a wrapper around a Component and implements the Printable interface. Note that it defines two print() methods. One is the three-argument Printable method I already described. The other print() method takes no arguments and implements the general Java 1.2 printing algorithm. It creates a PrinterJob, displays some dialogs to the user, and initiates the printing process. To print a component, create a PrintableComponent for that component, then call its print() method with no arguments. Example 5-1. PrintableComponent.java
There are a few important points to note about this PrintableComponent example. First, it is not designed to work with native AWT components, since those components do not do their own drawing. Second, it does not work well for components that use double-buffering because double-buffering locks the component drawing into the relatively low resolution of an off-screen image, rather than taking advantage of the high resolution available on the printer. Finally, PrintableComponent prints only the visible portion of a component, not the complete contents of the component. For example, the Swing JEditorPane class can display long HTML documents. If you use PrintableComponent to print a JEditorPane, however, it prints only the currently visible text, not the complete HTML document. The ability to print complete documents is a feature that is sorely missing in the current implementation of Swing. 5.2.2. Printing Multipage DocumentsAs we just discussed, the Printable interface can be used to print multipage documents. However, the PrinterJob has no way of determining in advance how many pages are required. This means that the user cannot request that only a subset of pages be printed, for example. When you know the complete contents of the document to be printed and can break it into pages before printing begins, it is better to use the Pageable interface than the Printable interface. Pageable defines a getNumberOfPages() method that returns the number of pages to be printed. It also defines two methods that take a page number and return PageFormat and Printable objects for that page. To print a Pageable object, the PrinterJob asks for a PageFormat and a Printable object for each page to be printed and then uses the print() method of each Printable object to print that page. Example 5-2 shows a class that implements the Pageable and Printable interfaces in order to print a string, file, or stream of text. This is a rudimentary example of text printing. It prints only text files, using a single font, and does not even expand tabs or wrap long lines. The Java 1.2 printing API allows the use of Java 2D graphics through the Graphics2D class. This example does not use the Java 2D version of the drawString() method, however. Although that method allows text to be positioned more precisely using floating-point coordinates, there is a bug in the current implementation that prevents this method from printing correctly. Example 5-2. PageableText.java
5.2.3. Efficiency Issues in the Java 1.2 Printing APIAlthough the Java 1.2 printing API offers important design improvements over the Java 1.1 API, there are serious efficiency problems with Sun's implementation of the 1.2 API in versions of Java up to at least Java 1.2.2. All printers are good at printing text, but not all are equally good at drawing arbitrary graphics. Thus, when a page contains anything but text or very simple graphics, Java 1.2 converts the entire page to a very large image and prints it in graphics mode. As I mentioned earlier, the current implementation of PrinterJob calls the print() method of a Printable object at least twice. The first call uses a dummy Graphics object whose sole purpose is to determine what kind of graphics the page contains. If the page contains only text, as is the case in Example 5-2, the PrinterJob can print the page efficiently in text mode. However, if the page contains any other type of graphics, the PrinterJob uses a large, high-resolution image to capture the graphics on the page and then transmits this image to the printer for printing in graphics mode. Because such a high-resolution image is memory intensive, the PrinterJob typically breaks the page up into several smaller bands and calls the print() method several times (using a different clipping region each time). In this way, the PrinterJob is able to spool a large image to the printer without using a large amount of memory (a classic time versus space trade-off). Unfortunately, the implementation is not well optimized, and printing performance is unacceptable on some systems. Printing even a simple graphic, such as one produced with the PrintableComponent class shown in Example 5-1, can take several minutes and can produce a printer spool file of more than 50 megabytes. Printing with the Java 1.1 API works better in Java 1.1 than it does in current implementations of Java 1.2. The Java 1.1 API works in Java 1.2, but it suffers the same efficiency problems as the Java 1.2 API. Furthermore, the Java 1.1 API does not perform the first pass to determine what type of graphics a page contains, so even a Java 1.1 program that prints only text is inefficient when run under Java 1.2. ![]() Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|
|