Part IV - Special Topics
1. Java 2 Printing API overview
2. Printing images
3. Print preview
4. Printing styled text
5. Printing tables
In the following six chapters we cover several different topics which relate directly to the use of Swing. Chapter 22 discusses the powerful new Java 2 printing API. We construct examples showing how to print an image on multiple pages, construct a print preview component, print styled text, and print JTable data (in both portrait and landscape modes). Chapter 23 introduces a few Java2D features. Examples include a generic 2D chart class, a 2D label class, and the beginnings of a Pac-man game. Chapter 24 introduces Accessibility and shows how easy it is to integrate this functionality into existing apps. Chapter 25 covers the basics of the JavaHelp API, and includes examples showing how we can customize the Swing-based help viewer to our liking. Chapter 26 introduces CORBA and contains an example of a client-server, Swing-based app based on our StocksTable example from chapter 18. Chapter 27 consists of two examples contributed by experienced Swing developers: constructing custom multi-line labels and tooltips and an internet browser application. Unfortunately, due to space limitations, chapters 24-27 were not included in this edition. However, they remain freely available to all readers on the book's web site.
Chapter 22. Printing
In this chapter:
- Java 2 Printing API overview
- Printing images
- Print preview
- Printing styled text
- Printing tables
22.1 Java 2 Printing API overview
With Java 2 comes a considerably advanced printing API. Java veterans may recall that JDK 1.0 didn't provide printing capabilities at all. JDK 1.1 provided access to native print jobs, but multi-page printing was a real problem for that API.
Now Java developers are able to perform multi-page printing using page count selection and other typical specifications in the native Print dialog, as well as page format selection in the native platform-specific Page Setup dialog. The printing-related API is concentrated in the java.awt.print package, and we'll start this chapter with an overview of these classes and interfaces.
This is the main class which controls printing in Java 2. It is used to store print job properties, to initiate printing when necessary, and to control the display of Print dialogs. A typical printing process is shown in the following code:
PrinterJob prnJob = PrinterJob.getPrinterJob();
This code retrieves an instance of PrinterJob with the static getPrinterJob() method, passes a Printable instance to it (used to render a specific page on demand--see below), invokes a platform-dependent Print dialog by calling PrinterJob's printDialog() method, and, if this method returns true (indicating the "ok" to print), starts the actual printing process by calling the print() method on that PrinterJob.
The Print dialog will look familiar, as it is the typical dialog used by most other applications on the user's system. For example, figure 22.1 shows a Windows NT Print dialog:
Figure 22.1 Windows NT Print dialog: about to print a pageable job .
Though the PrinterJob is the most important constituent of the printing process, it can do nothing without a Printable instance that specifies how to actually perform the necessary rendering for each page.
22.1.2 The Printable interface
abstract interface java.awt.print.Printable
This interface defines only one method: print(), which takes three parameters:
Graphics graphics: the graphical context into which the page will be drawn.
PageFormat pageFormat: an object containing information about the size and orientation of the page being drawn (see below).
int pageIndex: the zero based index of the page to be drawn.
The print() method will be called to print a portion of the PrinterJob corresponding to a given pageIndex. An implementation of this method should perform rendering of a specified page, using a given graphical context and a given PageFormat. The return value from this method should be PAGE_EXISTS if the page is rendered successfully, or NO_SUCH_PAGE if the given page index is too large and does not exist. (These are static ints defined in Printable.)
A class that implements Printable is said to be a page painter. When a PrinterJob uses only one page painter to print each page it is referred to as a printable job. The notion of a document as being separated into a certain number of pages is not predefined in a printable job. In order to print a specific page, a printable job will actually render all pages leading up to that page first, and then it will print the specified page. This is because it does not maintain information about how much space each page will occupy when rendered with the given page painter. For example, if we specify, in our Print dialog, that we want to print pages 3 and 5 only, then pages 0 through 4 (because pages are 0-indexed) will be rendered with the print() method, but only 2 and 4 will actually be printed.
In reality, it is often the case that print() will be called for each page more than once. From a draft of an overview of the Java Printing API: "This *callback* printing model is necessary to support printing on a wide range of printers and systems...This model also enables printing to a bitmap printer from a computer that doesn't have enough memory or disk space to buffer a full-page bitmap. In this situation, a page is printed as a series of small bitmaps or *bands*. For example, if only enough memory to buffer one tenth of a page is available, the page is divided into ten bands. The printing system asks the application to render each page ten times, once to fill each band. The application does not need to be aware of the number or size of the bands; it simply must be able to render each page when requested." -- http://java.sun.com/printing/jdk1.2/index.html
Though this explains some of the performance problems that we will see in the coming examples, it seems that the model described above is not exactly what we are dealing with in Java 2 FCS. In fact, after some investigation, it turns out that the division into bands is not based on available memory. Rather, a hard-coded 512k buffer is used. By increasing the size of this buffer, it is feasible to increase performance significantly. However, this would involve modification of peer-level classes; something that we are certainly not encouraged to do. We hope to see this limitation accounted for in future releases.
22.1.3 The Pageable interface
abstract interface java.awt.print.Pageable
It is possible to support multiple page painters in a single PrinterJob. As we know, each page printer can correspond to a different scheme of printing because each Printable implements its own print() method. Implemenatations of the Pageable interface are designed to manage groups of page painters, and a print job that uses multiple page painters is referred to as a pageable job. Each page in a pageable job can use a different page printer and PageFormat (see below) to perform its rendering.
Unlike printable jobs, pageable jobs do maintain the predefined notion of a document as a set of separate pages. For this reason pages of a pageable job can be printed in any order without the necessity of rendering all pages leading up to a specific page (as is the case with printable jobs). Also, because a Pageable instance carries with it an explicit page count, this can be communicated to the native printing system when a PrinterJob is established. So when printing a pageable job the native Print dialog will know the correct range of pages to display, unlike a printable job. (Note that this does not mean pageable jobs are not subject to the inherent limitations described above; we will see the same repetitive calling of print() that we do in printable jobs.)
When constructing a pageable PrinterJob, instead of calling PrinterJob's setPrintable() method (see section 22.1.1 above), we call its setPageable() method. Figure 22.1 shows a Windows NT Print dialog about to print a pageable job. Notice that the range of pages is not 1 to 9999.
We won't be working with pageable jobs in this chapter because all the documents we will be printing only require one Printable implementation, even if documents can span multiple pages. In most real-world applications, each page of a document is printed with identical orientation, margins, and other sizing characterstics. However, if greater flexibility is desired, Pageable implementations such as Book (see below) can be useful.
22.1.4 The PrinterGraphics interface
abstract interface java.awt.print.PrinterGraphics
This interface defines only one method: getPrinterJob(), which retrieves the PrinterJob instance controlling the current printing process. It is implemented by Graphics objects that are passed to Printable objects to render a page. (We will not need to use this interface at all, as it is used deep inside PrinterJob instances to define Graphics objects passed to each Printable's paint() method during printing.)
This class encapsulates a Paper object and adds to it an orientation property (landscape or portrait). We can force a Printable to use a specific PageFormat by passing one to PrinterJob's overloaded setPrintable() method. For instance, the following would force a printable job to use a specific PageFormat with a landscape orientation:
PrinterJob prnJob = PrinterJob.getPrinterJob();
PageFormat pf = job.defaultPage();
PageFormat defines three orientations:
LANDSCAPE: The origin is at the bottom left-hand corner of the paper with x axis pointing up and y axis pointing to the right.
PORTRAIT (most common): The origin is at the top left-hand corner of the paper with x axis pointing to the right and y axis pointing down.
REVERSE_LANDSCAPE: The origin is at the top right-hand corner of the paper with x axis pointing down and y axis pointing to the left.
We can optionally display a page setup dialog in which the user can specify page characteristics such as orientation, paper size, margin size, etc. This dialog will return a new PageFormat to use in printing. The page setup dialog is meant to be presented before the Print dialog and can be displayed using PrinterJob's pageDialog() method. The following code brings up a page setup dialog, and uses the resulting PageFormat for printing a printable job:
PrinterJob prnJob = PrinterJob.getPrinterJob();
PageFormat pf = job.pageDialog(job.defaultPage());
Note that we need to pass the pageDialog() method a PageFormat instance, as it uses it to clone and modify as the user specifies. If the changes are accepted the cloned and modifed version is returned. If they are not, the original version passed in is returned. Figure 22.2 shows a Windows NT page setup dialog:
Figure 22.2 Windows NT page setup dialog .
This class holds the size and margins of the paper used for printing. Methods getImageableX() and getImageableY() retrieve the coordinates of the top-left corner of the printable area in 1/72nds of an inch (which is approximately equal to one screen pixel--referred to as a "point" in typography). Methods getImageableWidth() and getImageableHeight() retrieve the width and height of the printable area (also in 1/72nds of an inch). We can also change the size of the useable region of the paper using its setImageableArea() method.
We can access the Paper object associated with a PageFormat using PageFormat's getPaper() and setPaper() methods.
This class represents a collection of Printable instances with corresponding PageFormats to represent a complex document whose pages may have different formats. The Book class implements the Pageable interface, and Printables are added to a Book using one of its append() methods. This class also defines several methods allowing for the manipulation and replacement of specific pages. (A page in terms of a Book is a Printable-PageFormat pair. Each page does correspond to an actual printed page.) See the API docs and the Java Tutorial for more information about this class.
This exception may be thrown to indicate an error during a printing procedure. It has two concrete sub-classes: PrinterAbortException and PrinterIOException. The former indicates that a print job was terminated by the application or user while printing, and the latter indicates that there was a problem outputting to the printer.