<< 用jimi Java api操作图片进行图片格式转换、重绘图片和图片缩放 | 首页 | HP-UX常用系统命令 >>

SDK 1.2 Printing API Tutorial

Introduction

The Java TM 2 Standard Development Kit, version 1.2 introduced a new printing application programming interface (API). This application interface, referred to as the SDK 1.2 Printing API or Printing API in this document, is independent of the Java 2DTM API, although it was introduced along with the powerful Java 2D graphics library and designed to image the complete set of Java 2D graphics.

This document introduces the Java 2 SDK, version 1.2 Printing API, discusses possible future capabilities of the API, and provides examples of its use.

Requirements and Restrictions

Print All of Java 2D

The breadth of Java2D graphics that can be printed through the SDK 1.2 Printing API exceeds the capabilities of most graphics subsystems, such as Windows GDI and Macintosh Quickdraw. Java 2D also exceeds the power of existing printer control languages, such as PCL 5 and PostScript. Nonetheless, one of the requirements for the new Printing API was that it print the full set of Java 2D grqaphics, even though the host and printer capabilities are overmatched by Java2D. This requirement meant that the Printing API, in some situations, would need to rasterize Java 2D graphics on the host computer.

No Spool Format Available

To rasterize print jobs on a host computer, a print subsystem typically spools graphics primitives to disk. As each band of the raster needs to be sent to the printer, the spool file is replayed in order to render the band. Neither the AWT nor the Java 2D graphics APIs provide a spool format for their primitives. This lack of an existing spool format combined with the tight development schedule of SDK 1.2 meant that a non-spooling solution be implemented to support host rasterized print jobs. The solution implemented in the SDK 1.2 Printing API has the print subsystem call back into the application to rasterize a band

.

What the Printing API Is Not

A complete printing system consists of several layers, extending from the printing application down to the printer, as illustrated in Figure 1. The application interacts with the printing system for two reasons: to target a printer and to generate printed pages.

Figure 1: A Print Subsystem
The act of targeting a printer is termed printer discovery. In its complete form, printer discovery allows an application to obtain a list of available printers and their capabilities and to choose one of these printers for subsequent operations.
Printer Imaging consists of formatting pages and drawing the contents of each page. The conversion of printing operations to a format supported by the targeted printer, in a complete printing system, is the job of a printer driver. The printer driver layer should support plug-in printer drivers.
The SDK 1.2 Printing API primarily supplies the Printer Imaging portion of the print subsystem. Through the Printing API, applications can format pages and draw their contents. Printer discovery is not supported by the SDK 1.2 Printing API, as shown in Figure 2. An application can obtain information about the current printer and print to it by using the Printing API. The printing dialog supplied by the Printing API also allows a user to change the current printer, but the application can not do this programmatically.
Figure 2: The SDK 1.2 Printing API Does Not Support Printer Discovery
The current implementation of the SDK 1.2 Printing API does not support a printer driver layer (see Figure 2). Each SDK 1.2 port re-implements the imaging to the printer translation layer.

Implementation Notes

<To come>

SDK 1.2

The primary goal of the printing support in SDK 1.2 was to be able to render all Java 2D graphics. To meet this goal within the SDK schedule, the initial implementation focused upon the creation of a raster print path and the creation of the new printing API itself. In this path Java 2D is used to render all the graphics on the host computer and then the raster is sent to the printer. Both the Windows and Solaris reference implementations include white space skipping in order to limit the amount of data sent to the printer. Even with this small optimization, the amount of generated data can be huge. At the end of the SDK 1.2 development cycle an optimized shape-printing path was added to avoid the rasterization path for certain pages in a print job. In the shape-print path, graphics on the page are converted into shapes and then filled. This generates much less data than the raster path, but a large amount of data can still be generated for complex shapes, such as a page of text. Overall, however, the shape path is faster than the raster path. If a given page only draws with solid, opaque colors, such as those created with java.awt.Color, and does not draw images then the shape-printing path will be used to render the page.

The PrinterJob Class

With the absence of printer discovery in the SDK 1.2 Printing API, there is no Printer class to represent instances of available printers; there is only the current printer and the current printer job. These concepts are both represented by the PrinterJob class.
An application written for the JavaTM platform obtains an instance of a PrinterJob by invoking the static PrinterJob methodgetPrinterJob(). This method returns an instance of the platform-dependent PrinterJob subclass. For example in SDK 1.2, getPrinterJob() returns an instance of WPrinterJob and on Solaris a PSPrinterJob instance is returned. The actual subclass represented by the returned instance is not meaningful to the printing application because the application only invokes public PrinterJob methods. The PrinterJob is obtained as follows:
PrinterJob printerJob = PrinterJob.getPrinterJob();

The Book Class

To print a document, the application written for the Java platform should build a representation of the document. Instead of explicitly building this representation, the application can use the setPrintable() method that PrinterJob supports, but the better method to use is setPageable() because it is a purer and more powerful printing path.
The Pageable interface allows the print subsystem to query the application for the number of pages in the document to be printed and to obtain, for each page, the PageFormat, the painter, and the Printable. The Pageable interface enables the print subsystems to print pages in any order and to draw each page multiple times to implement banded raster printing, separations, and other special features.
The SDK 1.2 Printing API supplies a concrete implementation of the Pageable interface in the Book class. This Book class is suitable for most applications, but an application can directly implement the Pageable interface to better integrate printing with its internal document structure.
Once a new Book instance is created, the application appends to the Book a pair of objects to represent each page of the document. The pair of objects is made up of an instance of PageFormat and an instance of an object that implements the Printable interface. The PageFormat instance describes the page dimensions and orientation. The Printable interface is called to paint the contents of the page.
The creation of a one-page Book looks as follows where PrintBlank is a class that implements the Printable interface:
Book book = new Book();
book.append(new PrintBlank(), new PageFormat());
After the instance of Book is created, it can be added to the PrinterJob:
printerJob.setPageable(book);

The Print Dialog

In the Printing API, the application does not need to display a print dialog to the user. Most user-oriented applications, as opposed to server applications, will want to display a print dialog. The contents of the print dialog is platform-dependent, but it usually provides the user with page range controls, controls over the number of copies, and perhaps the ability to switch printers. The PrinterJob object's printDialog() method displays the dialog. If the user cancels the dialog then the method returns false and the application should abort the printing process. If the printDialog() returns true, the user has confirmed that printing should occur, as in Listing 1.
Listing 1: printDialog() confirms that printing should occur
boolean doPrint = printerJob.printDialog();
if (doPrint) {
// The user confirmed that printing should proceed.
}

Printing

Because the printing loop is controlled by the printing subsystem, once a PrinterJob has been instanced and a Pageable implementation has been added to it, the actual printing process is simple from the application's point of view. To start printing, the application invokes the PrinterJob object's print method:
printerJob.print();
The printing subsystem then invokes the application's page painters, the Printables, as needed. Each Printable can be invoked multiple times in any order.

A Simple Example: PrintBlank

The PrintBlank class, shown in Listing 2, puts the Printing API components all together to provide a simple example of using the SDK 1.2 printing API. This application presents the user with a print dialog. If the user hits the print button, one blank page is printed.
Listing 2: Putting Together the Printing API Components: PrintBlank
import java.awt.*;
import java.awt.print.*;
/**
 * This example shows how to use the PrinterJob
 * and Book classes.
 */
public class PrintBlank implements Printable {
/**
* Print a single, blank page.
*/
static public void main(String args[]) {
/* Get the representation of the current printer and 
* the current print job.
*/
PrinterJob printerJob = PrinterJob.getPrinterJob();
/* Build a book containing pairs of page painters (Printables)
 * and PageFormats. This example has a single page.
 */
Book book = new Book();
book.append(new PrintBlank(), new PageFormat());
/* Set the object to be printed (the Book) into the PrinterJob.
 * Doing this before bringing up the print dialog allows the
 * print dialog to correctly display the page range to be printed
 * and to dissallow any print settings not appropriate for the
 * pages to be printed.
 */
printerJob.setPageable(book);

/* Show the print dialog to the user. This is an optional step
 * and need not be done if the application wants to perform
 * 'quiet' printing. If the user cancels the print dialog then false
 * is returned. If true is returned we go ahead and print.
 */
boolean doPrint = printerJob.printDialog();
if (doPrint) {

try {
printerJob.print();
} catch (PrinterException exception) {
System.err.println("Printing error: " + exception);
}
}
}
/**
 * Print a blank page.
 */
public int print(Graphics g, PageFormat format, int pageIndex) {
/* Do the page drawing here. This example does not do any
 * drawing and therefore blank pages are generated:
 * "This Page Intentionally Left Blank"
 */
return Printable.PAGE_EXISTS;
}
}

The Printable Interface

When building a Book the application must supply one or more Printable instances. These Printable instances are responsible for drawing the contents of each page. A Printable's print method is invoked by the printing subsystem as needed to render some or all of a page. Listing 3 features PrintText, a sample application that uses its print method to draw a page of text.
Listing 3: Using the print method: PrintText
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.print.*;
import java.text.*;
/**
 * The PrintText application expands on the
 * PrintExample application in that it images
 * text on to the single page printed.
 */
public class PrintText implements Printable {
/**
* The text to be printed.
*/
private static final String mText = 
"Four score and seven years ago our fathers brought forth on this " 
+ "continent a new nation, conceived in liberty and dedicated to the "
+ "proposition that all men are created equal. Now we are engaged in "
+ "a great civil war, testing whether that nation or any nation so "
+ "conceived and so dedicated can long endure. We are met on a great "
+ "battlefield of that war. We have come to dedicate a portion of "
+ "that field as a final resting-place for those who here gave their "
+ "lives that that nation might live. It is altogether fitting and "
+ "proper that we should do this. But in a larger sense, we cannot "
+ "dedicate, we cannot consecrate, we cannot hallow this ground."  
+ "The brave men, living and dead who struggled here have consecrated "
+ "it far above our poor power to add or detract. The world will "
+ "little note nor long remember what we say here, but it can never "
+ "forget what they did here. It is for us the living rather to be "
+ "dedicated here to the unfinished work which they who fought here "
+ "have thus far so nobly advanced. It is rather for us to be here "
+ "dedicated to the great task remaining before us--that from these "
+ "honored dead we take increased devotion to that cause for which "
+ "they gave the last full measure of devotion--that we here highly "
+ "resolve that these dead shall not have died in vain, that this "
+ "nation under God shall have a new birth of freedom, and that "
+ "government of the people, by the people, for the people shall "
+ "not perish from the earth.";

/**
 * Our text in a form for which we can obtain a
 * AttributedCharacterIterator.
 */
private static final AttributedString mStyledText = new AttributedString(mText);
/**
 * Print a single page containing some sample text.
 */
static public void main(String args[]) {
/* Get the representation of the current printer and 
 * the current print job.
*/
PrinterJob printerJob = PrinterJob.getPrinterJob();
/* Build a book containing pairs of page painters (Printables)
 * and PageFormats. This example has a single page containing
 * text.
 */
Book book = new Book();
book.append(new PrintText(), new PageFormat());
/* Set the object to be printed (the Book) into the PrinterJob.
 * Doing this before bringing up the print dialog allows the
 * print dialog to correctly display the page range to be printed
 * and to dissallow any print settings not appropriate for the
 * pages to be printed.
 */
printerJob.setPageable(book);

/* Show the print dialog to the user. This is an optional step
 * and need not be done if the application wants to perform
 * 'quiet' printing. If the user cancels the print dialog then false
 * is returned. If true is returned we go ahead and print.
 */
boolean doPrint = printerJob.printDialog();
if (doPrint) {

try {
printerJob.print();
} catch (PrinterException exception) {
System.err.println("Printing error: " + exception);
}
}
}
/**
 * Print a page of text.
 */
public int print(Graphics g, PageFormat format, int pageIndex) {
/* We'll assume that Jav2D is available.
 */
Graphics2D g2d = (Graphics2D) g;
/* Move the origin from the corner of the Paper to the corner
 * of the imageable area.
 */
g2d.translate(format.getImageableX(), format.getImageableY());
/* Set the text color.
 */
g2d.setPaint(Color.black);
/* Use a LineBreakMeasurer instance to break our text into
 * lines that fit the imageable area of the page.
 */
Point2D.Float pen = new Point2D.Float();
AttributedCharacterIterator charIterator = mStyledText.getIterator();
LineBreakMeasurer measurer = new LineBreakMeasurer(charIterator, g2d.getFontRenderContext());
float wrappingWidth = (float) format.getImageableWidth();

while (measurer.getPosition() < charIterator.getEndIndex()) {
TextLayout layout = measurer.nextLayout(wrappingWidth);
pen.y += layout.getAscent();
float dx = layout.isLeftToRight()? 0 : (wrappingWidth - layout.getAdvance());
layout.draw(g2d, pen.x + dx, pen.y);
pen.y += layout.getDescent() + layout.getLeading();
}
return Printable.PAGE_EXISTS;
}
The print method for a given page may be invoked multiple times so that the print subsystem can gather metrics about the page or rasterize bands of the page. On each invocation, the clip of the Graphics instance parameter is set to the area of the page that should be imaged. To increase performance, an application should only draw graphics that intersect the clip. The example above does not perform this optimization.
The Printable.print method is invoked with three parameters. The first parameter is an instance of a Graphics subclass. In a Java 2D system, this parameter will be an instance of a Graphics2D subclass. If an application uses Java 2D, then it should try to cast this first print parameter to a Graphics2D instance. If the cast succeeds, both Graphics2D and Graphics methods may be invoked. If the cast fails, only Graphics methods may be used to draw the page.
The Graphics parameter passed to the print method will also implement the PrinterGraphics interface. While usually not needed, this interface allows the print method to obtain the PrinterJob that controls the print job. The PrinterGraphics interface is discussed later in this document, and an example of its use is given.
The second print parameter is the PageFormat instance for the page to be drawn. The page painter can use this instance to obtain the paper size, imageable area, and orientation. Applications can supply a PageFormat subclass providing other information to the print method.
Lastly, the print() method is passed the index of the page to be drawn. This index starts at 0 for the first page in the Book and each page is numbered sequentially from the first. The page index passed to the print() method is based upon a page's position in a Book, not the page's position in the job. For example, in a five-page Book, the user may, using the print dialog, request that only page four be printed. In this case, the print() method will be passed a zero-based page index of three. Note that the page index is three even though the page being printed is the first and only page in the job.

The Page Format Dialog

In addition to the print dialog, a PrinterJob provides a page setup dialog. The page setup dialog allows the user to choose a paper size and orientation. In the SDK 1.2 release, the page setup dialog is not displayed by the Solaris reference release. The method that displays the page setup dialog is pageDialog().
The pageDialog()method takes a PageFormat instance as its parameter. The PageFormat is used to initialize the page setup dialog presented to the user. If the user confirms the dialog then a new PageFormat instance cloned from the PageFormat parameter is returned from the method. If the user cancels the page format dialog then the PageFormat instance parameter is returned unaltered. Listing 4 modifies the PrintText main() method (refer to Listing 3) so that the application displays a page format dialog:
Listing 4: Initializing the Page Setup Dialog Using the PrintText main() Method
/**
* Print a single page containing some sample text.
*/
static public void main(String args[]) {
/* Get the representation of the current printer and 
 * the current print job.
 */
PrinterJob printerJob = PrinterJob.getPrinterJob();
/* Let the user choose a paper size and orientation.
*/
PageFormat format = new PageFormat();
format = printerJob.pageDialog(format);
/* Build a book containing pairs of page painters (Printables)
* and PageFormats. This example has a single page containing
* text.
*/
Book book = new Book();
book.append(new PageSetupText(), format);
/* Set the object to be printed (the Book) into the PrinterJob.
* Doing this before bringing up the print dialog allows the
* print dialog to correctly display the page range to be printed
* and to dissallow any print settings not appropriate for the
* pages to be printed.
*/
printerJob.setPageable(book);
/* Show the print dialog to the user. This is an optional step
* and need not be done if the application wants to perform
* 'quiet' printing. If the user cancels the print dialog then false
* is returned. If true is returned we go ahead and print.
*/
boolean doPrint = printerJob.printDialog();
if (doPrint) {

try {
printerJob.print();
} catch (PrinterException exception) {
System.err.println("Printing error: " + exception);
}
}
}

The PageFormat Class

A PageFormat instance describes the size of the paper and the imageable area of the page into which the application will draw. The PageFormat maps the printer's paper size and hardware margins through a series of transformations representing the drawing orientation and any other features. The application is presented with the transformed paper dimensions and with the transformed imageable area.
To get the width of the paper in the drawing orientation, the application calls the getWidth() method of PageFormat. The getHeight() method returns the transformed height. For example, let us assume that the underlying paper size is 8.5 inches wide by 11 inches tall (see Figure 3). If the PageFormat is portrait orientation, then getWidth()returns 612 points (8.5 inches times 72 points per inch). Similarly getHeight()returns 792 points. If, however, the PageFormat is landscape orientation, as shown in Figure 4, getWidth()returns 792 points and getHeight()returns 612 points. 

Figure 3: Portrait PageFormat Dimensions and Coordinates


Figure 4: Landscape PageFormat Dimensions and Coordinates
The application obtains the imageable area of the PageFormat using the four methods: getImageableX() , getImageableY(), getImageableWidth(), and getImageableHeght(). These values, like the paper dimensions, are provided in 1/72 of an inch. The top-left corner of the paper is the origin for the coordinate system. The imageable area coordinates have been transformed like the paper dimensions to take into account the orientation and other features of the PageFormat.
Applications can subclass PageFormat in order to alter the paper and imageable area dimensions seen by the application. The Printing API does not explicitly allow a PageFormat to perform any drawing, an oversight, but this is remedied by a convention in Printable's print() method. Specifically, the application's Printable can check to see if the PageFormat instance it is passed implements Printable. If the PageFormat does implement Printable then the PageFormat's print() method can be invoked. FooterFormat, shown in Listing 5, demonstrates this convention, using it to have the PageFormat print page footers.
Listing 5: Invoking PageFormat's print() method implementing Printable: FooterFormat
import java.awt.*;
import java.awt.font.*;
import java.awt.print.*;
import java.text.*;
import java.util.*;
public class FooterFormat extends PageFormat implements Printable {
/**
 * The font we use for the footer.
 */
private static final Font mFooterFont = new Font("Serif", Font.ITALIC, 10);
/**
 * The amount of space at the bottom of the imageable area that we
 * reserve for the footer.
 */
private static final float mFooterHeight = (float) (0.25 * 72);
/**
 * The format for the date string shown in the footer.
 */
private static final DateFormat mDateFormat = new SimpleDateFormat();
/**
 * A formatted string describing when this instance was
 * created.
 */
private String mDateStr = mDateFormat.format(new Date()).toString();
/**
 * Tell the caller that the imageable area of the paper is shorter
 * than it actually is. We use the extra room at the bottom of the
 * page for our footer text.
 */
public double getImageableHeight() {

double imageableHeight = super.getImageableHeight() - mFooterHeight;
if (imageableHeight < 0) imageableHeight = 0;
return imageableHeight;
}
/**
 * Draws the footer text which has the following format:
 * <date>
 */
public int print(Graphics g, PageFormat format, int pageIndex) {
/* Make a copy of the passed in Graphics instance so
 * that we do not upset the caller's current Graphics
 * settings such as the current color and font.
 */
Graphics2D g2d = (Graphics2D) g.create();
g2d.setPaint(Color.black);
g2d.setFont(mFooterFont);
LineMetrics metrics = mFooterFont.getLineMetrics(mDateStr, g2d.getFontRenderContext());
/* We will draw the footer at the bottom of the imageable
 * area. We subtract off the font's descent so that the bottoms
 * of descenders remain visable.
 */
float y = (float) (super.getImageableY() + super.getImageableHeight()- metrics.getDescent() - metrics.getLeading());
// Cast to an int because of printing bug in drawString(String, float, float)!
g2d.drawString(mDateStr, (int) super.getImageableX(), (int)y);
g2d.dispose();
return Printable.PAGE_EXISTS;

}
}
Listing 6 features TextWithFooter, an application that uses the FooterFormat class:
Listing 6: Using the FooterFormat Class: TextWithFooter
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.print.*;
import java.text.*;
/**
 * The TextWithFooter application uses the 
 * page setup and print dialogs to print
 * a hunk of text. A FooterFormat class
 * is used to print a footer on the page.
 */
public class TextWithFooter implements Printable {

/**
 * The text to be printed.
 */
private static final String mText = 
"Four score and seven years ago our fathers brought forth on this " 
+ "continent a new nation, conceived in liberty and dedicated to the "
+ "proposition that all men are created equal. Now we are engaged in "
+ "a great civil war, testing whether that nation or any nation so "
+ "conceived and so dedicated can long endure. We are met on a great "
+ "battlefield of that war. We have come to dedicate a portion of "
+ "that field as a final resting-place for those who here gave their "
+ "lives that that nation might live. It is altogether fitting and "
+ "proper that we should do this. But in a larger sense, we cannot "
+ "dedicate, we cannot consecrate, we cannot hallow this ground."  
+ "The brave men, living and dead who struggled here have consecrated "
+ "it far above our poor power to add or detract. The world will "
+ "little note nor long remember what we say here, but it can never "
+ "forget what they did here. It is for us the living rather to be "
+ "dedicated here to the unfinished work which they who fought here "
+ "have thus far so nobly advanced. It is rather for us to be here "
+ "dedicated to the great task remaining before us--that from these "
+ "honored dead we take increased devotion to that cause for which "
+ "they gave the last full measure of devotion--that we here highly "
+ "resolve that these dead shall not have died in vain, that this "
+ "nation under God shall have a new birth of freedom, and that "
+ "government of the people, by the people, for the people shall "
+ "not perish from the earth.";
/**
 * Our text in a form for which we can obtain a
 * AttributedCharacterIterator.
 /
private static final AttributedString mStyledText = new AttributedString(mText);
/**
 * Print a single page containing some sample text.
 */
static public void main(String args[]) {

/* Get the representation of the current printer and 
 * the current print job.
 */
PrinterJob printerJob = PrinterJob.getPrinterJob();
/* Use a PageFormat that also prints a footer.
 */
PageFormat format = new FooterFormat();
/* Let the user choose a paper size and orientation.
 */
format = printerJob.pageDialog(format);
/* Build a book containing pairs of page painters (Printables)
 * and PageFormats. This example has a single page containing
 * text.
 */
Book book = new Book();
book.append(new TextWithFooter(), format);
/* Set the object to be printed (the Book) into the PrinterJob.
 * Doing this before bringing up the print dialog allows the
 * print dialog to correctly display the page range to be printed
 * and to dissallow any print settings not appropriate for the
 * pages to be printed.
 */
printerJob.setPageable(book);
/* Show the print dialog to the user. This is an optional step
 * and need not be done if the application wants to perform
 * 'quiet' printing. If the user cancels the print dialog then false
 * is returned. If true is returned we go ahead and print.
 */
boolean doPrint = printerJob.printDialog();
if (doPrint) {

try {
printerJob.print();
} catch (PrinterException exception) {
System.err.println("Printing error: " + exception);
}
}
}
/**
 * Print a page of text.
 */
public int print(Graphics g, PageFormat format, int pageIndex) throws PrinterException {
/* We'll assume that Jav2D is available. Create a copy
 * of it so that we can pass the original Graphics
 * instance to the PageFormat instance.
 */
Graphics2D g2d = (Graphics2D) g.create();
/* Move the origin from the corner of the Paper to the corner
 * of the imageable area.
 */
g2d.translate(format.getImageableX(), format.getImageableY());
/* Set the text color.
 */
g2d.setPaint(Color.black);
/* Use a LineBreakMeasurer instance to break our text into
 * lines that fit the imageable area of the page.
 */
Point2D.Float pen = new Point2D.Float();
AttributedCharacterIterator charIterator = mStyledText.getIterator();
LineBreakMeasurer measurer = new LineBreakMeasurer(charIterator, g2d.getFontRenderContext());
float wrappingWidth = (float) format.getImageableWidth();
while (measurer.getPosition() < charIterator.getEndIndex()) {

TextLayout layout = measurer.nextLayout(wrappingWidth);
pen.y += layout.getAscent();
float dx = layout.isLeftToRight() ? 0 : (wrappingWidth - layout.getAdvance());
layout.draw(g2d, pen.x + dx, pen.y);
pen.y += layout.getDescent() + layout.getLeading();
}
g2d.dispose();
g2d = null;
/* Calling the PageFormat is not part of the printing API,
* but it is a useful convention. In this example PageFormat
* does not implement Printable and so it is not invoked here.
* In later examples, PageFormat will implement Printable.
*/
try {
Printable formatPainter = (Printable) format;
formatPainter.print(g, format, pageIndex);
/* Nothing to do here. The PageFormat has nothing to print.
 */
} catch (ClassCastException exception) {
}
return Printable.PAGE_EXISTS;
}

The PrinterGraphics Interface

The PrinterGraphics interface was discussed briefly in the Printable interface discussion. When a Printable's print method is invoked, the first parameter is a Graphics instance that implements the PrinterGraphics interface. The print method can use the PrinterGraphics interface to get a reference to the current PrinterJob.
In Listing 7, the FooterFormat class from the previous section is extended so that it prints the document name in the footer, along with the date and page number. The document name is obtained from the PrinterJob.
Listing 7: Extending the FooterFormat Class
<Example Under Development>

The Pageable Arial

The Book class, provided with the SDK 1.2 Printing API, is useful for applications that have page-oriented contents. Such applications include word processors, report generators, and page layout programs. Another class of programs is canvas-oriented rather than page-oriented. These canvas-oriented applications draw on a single large canvas which, at print time, needs to be split across several pages. As an example of an application class that implements the Pageable interface, Listing 8 presents a Vista class which can print a canvas over multiple pages. This class is very simple and we will subclass it to make it useful in more specific environments, such as Swing.
Listing 8: Printing Over Multiple Pages: Vista
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.print.Pageable; 
import java.awt.print.PageFormat
import java.awt.print.Printable;
import java.awt.print.PrinterException;
/**
 * A simple Pageable class that can
 * split a large drawing canvas over multiple
 * pages.
 *
 * The pages in a canvas are laid out on
 * pages going left to right and then top
 * to bottom.
 */
public class Vista implements Pageable {

private int mNumPagesX;
private int mNumPagesY;
private int mNumPages;
private Printable mPainter;
private PageFormat mFormat;
/**
 * Create a java.awt.Pageable that will print
 *  a canvas over as many pages as are needed.
 * A Vista can be passed to PrinterJob.setPageable.
 *
 * @param width The width, in 1/72nds of an inch, 
 * of the vist's canvas.
 *
 * @param height The height, in 1/72nds of an inch, 
 * of the vista's canvas.
 *
 * @param painter The object that will drawn the contents
 * of the canvas.
 *
 * @param format The description of the pages on to which
 * the canvas will be drawn.
 */
public Vista(float width, float height, Printable painter, PageFormat format) {

setPrintable(painter);
setPageFormat(format);
setSize(width, height);
}
/**
 * Create a vista over a canvas whose width and height
 * are zero and whose Printable and PageFormat are null.
 */

protected Vista() {
}
/**
 * Set the object responsible for drawing the canvas.
 */

protected void setPrintable(Printable painter) {

mPainter = painter;
}
/**
 * Set the page format for the pages over which the
 * canvas will be drawn.
 */

protected void setPageFormat(PageFormat pageFormat) {

mFormat = pageFormat;
}
/**
 * Set the size of the canvas to be drawn.
 * 
 * @param width The width, in 1/72nds of an inch, of
 * the vist's canvas.
 *
 * @param height The height, in 1/72nds of an inch, of
 * the vista's canvas.
 */

protected void setSize(float width, float height) {

mNumPagesX = (int) ((width + mFormat.getImageableWidth() - 1)/ mFormat.getImageableWidth());
mNumPagesY = (int) ((height + mFormat.getImageableHeight() - 1)/ mFormat.getImageableHeight());
mNumPages = mNumPagesX * mNumPagesY;
}
/**
 * Returns the number of pages over which the canvas
 * will be drawn.
 */
public int getNumberOfPages() {
return mNumPages;
}
protected PageFormat getPageFormat() {
return mFormat;
}
/** 
 * Returns the PageFormat of the page specified by
 * pageIndex. For a Vista the PageFormat
 * is the same for all pages.
 *
 * @param pageIndex the zero based index of the page whose
 * PageFormat is being requested
 * @return the PageFormat describing the size and
 * orientation.
 * @exception IndexOutOfBoundsException
 * the Pageable  does not contain the requested
 * page.
 */
public PageFormat getPageFormat(int pageIndex) throws IndexOutOfBoundsException {

if (pageIndex >= mNumPages) {
throw new IndexOutOfBoundsException();
}
return getPageFormat();
}
/**
 * Returns the <code>Printable</code> instance responsible for
 * rendering the page specified by <code>pageIndex</code>.
 * In a Vista, all of the pages are drawn with the same
 * Printable. This method however creates
 * a Printable which calls the canvas's
 * Printable. This new Printable
 * is responsible for translating the coordinate system
 * so that the desired part of the canvas hits the page.
 *
 * The Vista's pages cover the canvas by going left to
 * right and then top to bottom. In order to change this
 * behavior, override this method.
 *
 * @param pageIndex the zero based index of the page whose
 * Printable is being requested
 * @return the Printable that renders the page.
 * @exception IndexOutOfBoundsException
 * the Pageable does not contain the requested
 * page.
 */
public Printable getPrintable(int pageIndex) throws IndexOutOfBoundsException {

if (pageIndex >= mNumPages) {
throw new IndexOutOfBoundsException();
}
double originX = (pageIndex % mNumPagesX) * mFormat.getImageableWidth();
double originY = (pageIndex / mNumPagesX) * mFormat.getImageableHeight();
Point2D.Double origin = new Point2D.Double(originX, originY);
return new TranslatedPrintable(mPainter, origin);
}
/**
 * This inner class's sole responsibility is to translate
 * the coordinate system before invoking a canvas's
 * painter. The coordinate system is translated in order
 * to get the desired portion of a canvas to line up with
 * the top of a page.
 */
public static final class TranslatedPrintable implements Printable {

/**
 * The object that will draw the canvas.
 */
private Printable mPainter;
/**
 * The upper-left corner of the part of the canvas
 * that will be displayed on this page. This corner
 * is lined up with the upper-left of the imageable
 * area of the page.
 */
private Point2D mOrigin;
/**
 * Create a new Printable that will translate
 * the drawing done by painter on to the
 * imageable area of a page.
 *
 * @param painter The object responsible for drawing
 * the canvas
 *
 * @param origin The point in the canvas that will be
 * mapped to the upper-left corner of
 * the page's imageable area.
 */
public TranslatedPrintable(Printable painter, Point2D origin) {

mPainter = painter;
mOrigin = origin;
}
/**
 * Prints the page at the specified index into the specified 
 * {@link Graphics} context in the specified
 * format. A PrinterJob calls the 
 * Printableinterface to request that a page be
 * rendered into the context specified by 
 * graphics. The format of the page to be drawn is
 * specified by pageFormat. The zero based index
 * of the requested page is specified by pageIndex. 
 * If the requested page does not exist then this method returns
 * NO_SUCH_PAGE; otherwise PAGE_EXISTS is returned.
 * The Graphics class or subclass implements the
 * {@link PrinterGraphics} interface to provide additional
 * information. If the Printable object
 * aborts the print job then it throws a {@link PrinterException}.
 * @param graphics the context into which the page is drawn 
 * @param pageFormat the size and orientation of the page being drawn
 * @param pageIndex the zero based index of the page to be drawn
 * @return PAGE_EXISTS if the page is rendered successfully
 * or NO_SUCH_PAGE if pageIndex specifies a
 * non-existent page.
 * @exception java.awt.print.PrinterException
 * thrown when the print job is terminated.
 */
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {

Graphics2D g2 = (Graphics2D) graphics;
g2.translate(-mOrigin.getX(), -mOrigin.getY());
mPainter.print(g2, pageFormat, 1);
return PAGE_EXISTS;
}
}
}
The Vista class is not very useful by itself. Listing 9 creates a subclass of Vista, JComponentVista that makes it easy to print Swing JComponents over multiple pages:
Listing 9: Printing JComponents over Multiple Pages: JComponentVista
import java.awt.*;
import java.awt.print.*;
import javax.swing.*;
public class JComponentVista extends Vista implements Printable {
private static final boolean SYMMETRIC_SCALING = true;
private static final boolean ASYMMETRIC_SCALING = false;
private double mScaleX;
private double mScaleY;
/**
 * The Swing component to print.
 */
private JComponent mComponent;
/**
 * Create a Pageable that can print a
 * Swing JComponent over multiple pages.
 *
 * @param c The swing JComponent to be printed.
 *
 * @param format The size of the pages over which
 * the componenent will be printed.
 */
public JComponentVista(JComponent c, PageFormat format) {

setPageFormat(format);
setPrintable(this);
setComponent(c);
/* Tell the Vista we subclassed the size of the canvas.
 */
Rectangle componentBounds = c.getBounds(null);
setSize(componentBounds.width, componentBounds.height);
setScale(1, 1);

}
protected void setComponent(JComponent c) {
mComponent = c;
}
protected void setScale(double scaleX, double scaleY) {
mScaleX = scaleX;
mScaleY = scaleY;
}
public void scaleToFitX() {
PageFormat format = getPageFormat();
Rectangle componentBounds = mComponent.getBounds(null);
double scaleX = format.getImageableWidth() /componentBounds.width;
double scaleY = scaleX;
if (scaleX < 1) {
setSize( (float) format.getImageableWidth(),
(float) (componentBounds.height * scaleY));
setScale(scaleX, scaleY);
}
}
public void scaleToFitY() {
PageFormat format = getPageFormat();
Rectangle componentBounds = mComponent.getBounds(null);
double scaleY = format.getImageableHeight() /componentBounds.height;
double scaleX = scaleY;
if (scaleY < 1) {
setSize( (float) (componentBounds.width * scaleX),(float) format.getImageableHeight());
setScale(scaleX, scaleY);
}
}
public void scaleToFit(boolean useSymmetricScaling) {
PageFormat format = getPageFormat();
Rectangle componentBounds = mComponent.getBounds(null);
double scaleX = format.getImageableWidth() /componentBounds.width;
double scaleY = format.getImageableHeight() /componentBounds.height;
System.out.println("Scale: " + scaleX + " " + scaleY);
if (scaleX < 1 || scaleY < 1) {
if (useSymmetricScaling) {
if (scaleX < scaleY) {
scaleY = scaleX;
} else {
scaleX = scaleY;
}
}
setSize( (float) (componentBounds.width * scaleX), (float) (componentBounds.height * scaleY) );
setScale(scaleX, scaleY);
)
}
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
Graphics2D g2 = (Graphics2D) graphics;
g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
Rectangle componentBounds = mComponent.getBounds(null);
g2.translate(-componentBounds.x, -componentBounds.y);
g2.scale(mScaleX, mScaleY);
boolean wasBuffered = mComponent.isDoubleBuffered();
mComponent.paint(g2);
mComponent.setDoubleBuffered(wasBuffered);
return PAGE_EXISTS;

}
)
Next, JBrowser.java (Listing 10) uses JComponentVista to print an HTML page over several physical pages. JComponentVista's scaling methods are also demonstrated in the example.
Listing 10: Printing HTML Pages: JBrowser
import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;
import java.awt.Window;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

/**
* Uses JFrame to create a simple browser with printing.
* User passes String URL (e.g., http://www.rbi.com)
* to set opening location. User then may follow
* hyperlinks or type new preferred location into
* provided JTextField.
*
* Scaling options are demonstrated in this
* example. Scaling options may be set from a
* submenu in the File menu or by specified KeyStroke.
* A second menu track nn websites, which user can
* reselect as destination using mouse.
*/
 
 

public class JBrowser extends JFrame {

private static final int kNumSites = 20; //number of sites listed in JMenu "Last 20"
private static final int kDefaultX = 640;
private static final int kDefaultY = 480;
private static final int prefScale = 0;

private static final String kScale2Label = "2X Scale";
private static final String kScaleFitLabel = "Scale to Fit";
private static final String kScaleHalfLabel = "1/2 Scale";
private static final String kScaleOffLabel = "Scaling Off";
private static final String kScaleXLabel = "Scale by Width";
private static final String kScaleYLabel = "Scale by Length";

private JEditorPane mainPane;
private String path;
private JButton goButton = new JButton("Go");
private JComponentVista vista;
private JMenu fileMenu = new JMenu("File", true);
private JMenu prefMenu = new JMenu("Print Preferences", true);
private JMenu siteMenu = new JMenu("Last 20", true);
private JRadioButtonMenuItem scale2RadioBut = new JRadioButtonMenuItem(kScale2Label);
private JRadioButtonMenuItem scaleFitRadioBut = new JRadioButtonMenuItem(kScaleFitLabel);
private JRadioButtonMenuItem scaleHalfRadioBut = new JRadioButtonMenuItem(kScaleHalfLabel);
private JRadioButtonMenuItem scaleOffRadioBut = new JRadioButtonMenuItem(kScaleOffLabel, true);
private JRadioButtonMenuItem scaleXRadioBut = new JRadioButtonMenuItem(kScaleXLabel);
private JRadioButtonMenuItem scaleYRadioBut = new JRadioButtonMenuItem(kScaleYLabel);
private JTextField pathField = new JTextField(30);
private Vector siteMIVector = new Vector();

public static void main(String[] args) {

new JBrowser(args[0]);
}

public JBrowser(String url) {

super("JBrowser HTML Printing Demo");
path = url;
addSite(path);

try {

mainPane = new JEditorPane(path);
} catch (IOException ex) {
ex.printStackTrace(System.err);
System.exit(1);
}

JMenuBar menuBar = new JMenuBar();
JPanel navPanel = new JPanel();
JMenuItem printMI = new JMenuItem("Print");
JMenuItem exitMI = new JMenuItem("Exit");

printMI.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, Event.CTRL_MASK));
exitMI.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, Event.CTRL_MASK));

scale2RadioBut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, Event.CTRL_MASK));
scaleFitRadioBut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.CTRL_MASK));
scaleHalfRadioBut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.CTRL_MASK));
scaleOffRadioBut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, Event.CTRL_MASK));
scaleXRadioBut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, Event.CTRL_MASK));
scaleYRadioBut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, Event.CTRL_MASK));

printMI.addActionListener(new printMIListener());
exitMI.addActionListener(new exitMIListener());

scaleXRadioBut.addActionListener(new scaleXListener());
scaleYRadioBut.addActionListener(new scaleYListener());
scaleFitRadioBut.addActionListener(new scaleFitListener());
scaleHalfRadioBut.addActionListener(new scaleHalfListener());

scale2RadioBut.addActionListener(new scale2Listener());

pathField.addActionListener(new pathFieldListener());
pathField.setText(path);
goButton.addActionListener(new pathFieldListener());

ButtonGroup scaleSetGroup = new ButtonGroup();
scaleSetGroup.add(scale2RadioBut);
scaleSetGroup.add(scaleFitRadioBut);
scaleSetGroup.add(scaleHalfRadioBut);
scaleSetGroup.add(scaleOffRadioBut);
scaleSetGroup.add(scaleXRadioBut);
scaleSetGroup.add(scaleYRadioBut);

prefMenu.add(scaleXRadioBut);
prefMenu.add(scaleYRadioBut);
prefMenu.add(scaleFitRadioBut);
prefMenu.add(scaleHalfRadioBut);
prefMenu.add(scale2RadioBut);
prefMenu.addSeparator();
prefMenu.add(scaleOffRadioBut);

fileMenu.add(prefMenu);
fileMenu.add(printMI);
fileMenu.addSeparator();
fileMenu.add(exitMI);

menuBar.add(fileMenu);
menuBar.add(siteMenu);
menuBar.add(pathField);
menuBar.add(goButton);
 

mainPane.setEditable(false);
mainPane.addHyperlinkListener(new linkListener());

vista = new JComponentVista(mainPane, new PageFormat());

addWindowListener(new BasicWindowMonitor());
setContentPane(new JScrollPane(mainPane));
setVisible(true);

setJMenuBar(menuBar);
setSize(kDefaultX, kDefaultY);
setVisible(true);

}

/*
* addSite method takes the String url and adds it to the
* Vector siteMIVector and the JMenu siteMenu.
*/
public void addSite(String url) {

boolean beenThere = false;

/*
* Cycle through the contents of the siteMenu, comparing
* their labels to the string to determine if there is
* redundancy.
*/
for(int i=0; i<siteMenu.getItemCount() && !beenThere; i++) {

JMenuItem site = siteMenu.getItem(i);
/*
* The String url, is compared to the labels of the
* JMenuItems in already stored in siteMIVector. If
* the string matches an existing label, the older
* redundant element, at i, is removed. The new JMenuItem
* site is inserted in the Vector at 0. The updateMenu
* method is called to update the "Last nn" menu accordingly
* and the "beenThere" boolean trigger is set TRUE.
*/
if (site.getText().equals(url)) {
siteMIVector.removeElementAt(i);
siteMIVector.insertElementAt(site, 0);
updateMenu(siteMenu);
beenThere = true;
}
}
/*
* If the new JMenuItem site has a unique string, then the
* addSite method handles it as follows.
*/
if (!beenThere) {
/*
* If the "Last nn" menu has reached kNumSites capacity,
* the oldest JMenuItem is removed from the vector, enabling
* storage for the new menu item and maintaining the specified
* capacity of the "Last nn" menu.
*/
if (siteMenu.getItemCount() >= kNumSites){
siteMIVector.removeElementAt(siteMIVector.size()-1);
}

/*
* A new JMenuItem is created and a siteMenuListener added.
* It is added to the vector then the menu is updated.
*/
JMenuItem site = new JMenuItem(url);
site.addActionListener(new siteMenuListener(url));
siteMIVector.insertElementAt(site, 0);
System.out.println("\n Connected to "+ url);
updateMenu(siteMenu);

}
}

public void updateMenu(JMenu menu) {

menu.removeAll();
for(int i=0; i<siteMIVector.size(); i++) {
JMenuItem mi = (JMenuItem)siteMIVector.elementAt(i);
menu.add(mi);
}
}

/*
*
* The ActionListener methods
*
*/
public class exitMIListener implements ActionListener {

public void actionPerformed(ActionEvent evt) {
System.out.println("\n Killing JBrowser...");
System.out.println(" ...AHHHHHHHHHhhhhhhh...ya got me...ugh");
System.exit(0);
}
}

public class linkListener implements HyperlinkListener {

public void hyperlinkUpdate(HyperlinkEvent ev) {
try {
if (ev.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
mainPane.setPage(ev.getURL());
path = ev.getURL().toString();
pathField.setText(path);
addSite(path);
}
} catch (IOException ex) {
ex.printStackTrace(System.err);
}
}
}

public class pathFieldListener implements ActionListener {

public void actionPerformed(ActionEvent evt) {
System.out.println("\n Switching from "+path+" to "+pathField.getText()+".");
path = pathField.getText();
try {
mainPane.setPage(path);
} catch (IOException ex){
ex.printStackTrace(System.err);
}

if (!path.equals("")) {

addSite(path);
}
}
}

public class printMIListener implements ActionListener {

public void actionPerformed(ActionEvent evt) {
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setPageable(vista);
try {
if (pj.printDialog()) {
pj.print();
}
} catch (PrinterException e) {
System.out.println(e);
}
}
}

public class scale2Listener implements ActionListener {

public void actionPerformed(ActionEvent evt) {
vista = new JComponentVista(mainPane, new PageFormat());
vista.setScale(2.0, 2.0);
}
}

public class scaleFitListener implements ActionListener {

public void actionPerformed(ActionEvent evt) {
vista = new JComponentVista(mainPane, new PageFormat());
vista.scaleToFit(false)
}
}

public class scaleHalfListener implements ActionListener {

public void actionPerformed(ActionEvent evt) {
vista = new JComponentVista(mainPane, new PageFormat());
vista.setScale(0.5, 0.5);
}
}

public class scaleOffListener implements ActionListener {

public void actionPerformed(ActionEvent evt) {
vista = new JComponentVista(mainPane, new PageFormat());
}
}

public class scaleXListener implements ActionListener {

public void actionPerformed(ActionEvent evt) {
vista = new JComponentVista(mainPane, new PageFormat());
vista.scaleToFitX();
}
}

public class scaleYListener implements ActionListener {

public void actionPerformed(ActionEvent evt) {
vista = new JComponentVista(mainPane, new PageFormat());
vista.scaleToFitY();
}
}

public class siteMenuListener implements ActionListener {

private String site;

public siteMenuListener(String url) {

site = url;
}

public void actionPerformed(ActionEvent evt) {

System.out.println("\n Switching from "+path+" to "+site+".");
path = site;
try {
mainPane.setPage(path);
} catch (IOException ex){
ex.printStackTrace(System.err);
}

if (!path.equals("")) {

addSite(path);
}

pathField.setText(path);

}
}
}
The Pageable interface can also be used to provide special effects on top of an existing class that implements Pageable
标签 :



发表评论 发送引用通报