Contents | Previous | Next |
The Java Printing API enables applications to:
The Java Printing API is based on a callback printing model in which the printing system, not the application, controls when pages are printed. The application provides information about the document to be printed and the printing system asks the application to render each page as it needs them.
The printing system might request that a particular page be rendered more than once or request that pages be rendered out of order. The application must be able to generate the proper page image, no matter which page the printing system requests. In this respect, the printing system is similar to the window toolkit, which can request components to repaint at any time, in any order.
The callback printing model is more flexible than traditional application-driven printing models and supports printing on a wider range of systems and printers. For example, if a printer stacks output pages in reverse order, the printing system can ask the application to generate pages in reverse order so that the final stack is in proper reading order.
This model also enables applications to print to a bitmap printer from computers that don’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.
An application has to perform two tasks to support printing:
The user often initiates printing by clicking a
button or selecting a menu item in an application. When a print
operation is triggered by the user, the application creates a
PrinterJob
object and uses it to manage
the printing process.
The application is responsible for setting up the print job, displaying print dialogs to the user, and starting the printing process.
When a document is printed, the application has to
render each page when the printing system requests it. To support
this mechanism, the application provides a page painter that implements the Printable
interface. When the printing system needs
a page rendered, it calls the page painter’s print
method.
When a page painter’s print
method is called, it is passed a Graphics
context to use to render the page image. It
is also passed a PageFormat
object that
specifies the geometric layout of the page, and an integer
page index that identifies the ordinal
position of the page in the print job.
The printing system supports both Graphics
and Graphics2D
rendering, To print Java 2D Shapes
, Text
, and
Images
, you cast the Graphics
object passed into the print
method to a Graphics2D
.
To print documents in which the pages use
different page painters and have different formats, you use a
pageable job. To create a pageable job,
you can use the Book
class or your own
implementation of the Pageable
interface. To implement simple printing operations, you do not need
to use a pageable print job; Printable
can be used as long as all of the pages share the same page format
and painter.
The principal job of a page painter is to render a
page using the graphics context that is provided by the printing
system. A page painter implements the Printable
.print
method:
The graphics context passed to the print
method is either an instance of Graphics
or Graphics2D
,
depending on the packages loaded in your Java Virtual Machine. To
use Graphics2D
features, you can cast
the Graphics
object to a Graphics2D
. The Graphics
instance passed to print
also implements
the PrinterGraphics
interface.
The PageFormat
passed
to a Printable
describes the geometry of
the page being printed. The coordinate system of the graphics
context passed to print
is fixed to the
page: the origin of the coordinate system is at the upper left
corner of the paper, X increases to the
right, Y increases downward, and the
units are 1/72 inch. If the page is in portrait orientation, the
x-axis aligns with the paper’s “width,” while the
y-axis aligns with the paper’s “height.”
(Normally, but not always, a paper’s height exceeds its
width.) If the page is in landscape orientation, the roles are
reversed: the x-axis aligns with the paper’s
“height” and the y-axis with its
“width.”
Because many printers cannot print on the entire
paper surface, the PageFormat
specifies
the imageable area of the page: this is
the portion of the page in which it’s safe to render. The
specification of the imageable area does not alter the coordinate
system; it is provided so that the contents of the page can be
rendered so that they don’t extend into the area where the
printer can’t print.
The graphics context passed to print
has a clip region that describes the portion
of the imageable area that should be drawn. It is always safe to
draw the entire page into the context; the printing system will
handle the necessary clipping. However, to eliminate the overhead
of drawing portions of the page that won’t be printed, you
can use the clipping region to limit the areas that you render. To
get the clipping region from the graphics context, call
Graphics.getClip
. You are strongly
encouraged to use the clip region to reduce the rendering
overhead.
It is sometimes desirable to launch the entire
printing operation “in the background” so that a user
can continue to interact with the application while pages are being
rendered. To do this, call PrinterJob.print
in a separate thread.
If possible, you should avoid graphics operations
that require knowledge of the previous image contents, such as
copyArea
, setXOR
, and compositing. These operations can slow
rendering and the results might be inconsistent.
A Printable
job provides the simplest way to print.
Only one page painter is used; the application provides a single
class that implements the Printable
interface. When it’s time to print, the printing system calls
the page painter’s print
method to
render each page. The pages are requested in order, starting with
page index 0. However, the page painter might be asked to render
each page several times before it advances to the next page. When
the last page has been printed, the page painter’s print
method returns NO_SUCH_PAGE.
In a Printable
job:
PageFormat
. If a print dialog is presented, it will
not display the number of pages in the document because that
information is not available to the printing system.A Pageable
job is more flexible than a Printable
job. Unlike the pages in a Printable
job, pages in a Pageable
job can differ in layout and
implementation. To manage a Pageable
job, you can use the Book
class or
implement your own Pageable
class.
Through the Pageable
, the printing
system can determine the number of pages to print, the page painter
to use for each page, and the PageFormat
to use for each page. Applications that need to print documents
that have a planned structure and format should use Pageable
jobs.
In a Pageable
job:
PageFormats
.Pageable
jobs do not need to know in
advance how many pages are in the document. However, unlike
Printable
jobs, they must be able to
render pages in any order. There might be gaps in the sequencing
and the printing system might request that a page be rendered
multiple times before moving to the next page. For example, a
request to print pages 2 and 3 of a document might result in a
sequence of calls that request pages with indices 2,2,1,1, and
1.An application steers the PrinterJob
object through a sequence of steps to
complete a printing job. The simplest sequence used by an
application is:
PrinterJob
object by
calling PrinterJob.getPrinterJob
.PageFormat
to use for
printing. A default PageFormat
can be
obtained by calling defaultPage
or you
can invoke pageDialog
to present a
dialog box that allows the user to specify a format.PrinterJob
. For a Printable
job, call setPrintable
; for a Pageable
job, call setPageable
. Note that a Book
object is ideal for passing to setPageable
.printDialog
to present a dialog
box to the user. This is optional. The contents and appearance of
this dialog can vary across different platforms and printers. On
most platforms, the user can use this dialog to change the printer
selection. If the user cancels the print job, the printDialog
method returns FALSE
.Printerjob.print
to print the
job. This method in turn calls print
on
the appropriate page painters.A job can be interrupted during printing if:
PrinterException
is
thrown—the exception is caught by the print
method and the job is halted. A page painter
throws a PrinterException
if it detects
a fatal error.PrinterJob.cancel
is
called—the printing loop is terminated and the job is
canceled. The cancel
method can be
called from a separate thread that displays a dialog box and allows
the user to cancel printing by clicking a button in the box.Pages generated before a print job is stopped might or might not be printed.
The print job is usually not finished when the
print
method returns. Work is typically
still being done by a printer driver, print server, or the printer
itself. The state of the PrinterJob
object might not reflect the state of the actual job being
printed.
Because the state of a PrinterJob
changes during its life cycle, it is
illegal to invoke certain methods at certain times. For example,
calling setPageable
after you’ve
called print
makes no sense. When
illegal calls are detected, the PrinterJob
throws a java.lang.IllegalStateException
.
The Java Printing API requires that applications invoke user-interface dialogs explicitly. These dialogs might be provided by the platform software (such as Windows) or by a JDK implementation. For interactive applications, it is customary to use such dialogs. For production printing applications, however, dialogs are not necessary. For example, you wouldn’t want to display a dialog when automatically generating and printing a nightly database report. A print job that requires no user interaction is sometimes called a silent print job.
You can allow the user to alter the page setup
information contained in a PageFormat
by
displaying a page setup dialog. To display the page setup dialog,
you call PrinterJob.pageDialog
. The page
setup dialog is initialized using the parameter passed to
pageDialog
. If the user clicks the OK
button in the dialog, the PageFormat
instance is cloned, altered to reflect the user’s selections,
and then returned. If the user cancels the dialog, pageDialog
returns the original unaltered
PageFormat
.
Typically, an application presents a print dialog
to the user when a print menu item or button is activated. To
display this print dialog, you call the PrinterJob’s printDialog
method. The
user’s choices in the dialog are constrained based on the
number and format of the pages in the Printable
or Pageable
that have been furnished to the PrinterJob
. If the user clicks OK in the print
dialog, printDialog
returns TRUE
. If the user cancels the print dialog,
FALSE
is returned and the print job
should be considered abandoned.
To provide basic printing support:
Printable
interface to
provide a page painter that can render each page to be
printed.PrinterJob
.setPrintable
to tell the
PrinterJob
how to print your
document.print
on the PrinterJob
object to start the job.In the following example, a Printable
job is used to print five pages, each of
which displays a green page number. Job control is managed in the
main
method, which obtains and controls
the PrinterJob
. Rendering is performed
in the page painter’s print
method.
import java.awt.*; import java.awt.print.*; public class SimplePrint implements Printable { private static Font fnt = new Font("Helvetica",Font.PLAIN,24); public static void main(String[] args) { // Get a PrinterJob PrinterJob job = PrinterJob.getPrinterJob(); // Specify the Printable is an instance of SimplePrint job.setPrintable(new SimplePrint()); // Put up the dialog box if (job.printDialog()) { // Print the job if the user didn't cancel printing try { job.print(); } catch (Exception e) { /* handle exception */ } } System.exit(0); } public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException { // pageIndex 0 to 4 corresponds to page numbers 1 to 5. if (pageIndex >= 5) return Printable.NO_SUCH_PAGE; g.setFont(fnt); g.setColor(Color.green); g.drawString("Page " + (pageIndex+1), 100, 100); return Printable.PAGE_EXISTS; } }
You can invoke Graphics2D
functions in you page painter’s
print method by first casting the Graphics
context to a Graphics2D
.
In the following example, the page numbers are
rendered using a red-green gradient. To do this, a GradientPaint
is set in the Graphics2D
context.
import java.awt.*; import java.awt.print.*; public class SimplePrint2D implements Printable { private static Font fnt = new Font("Helvetica",Font.PLAIN,24); private Paint pnt = new GradientPaint(100f, 100f, Color.red, 136f, 100f, Color.green, true); public static void main(String[] args) { // Get a PrinterJob PrinterJob job = PrinterJob.getPrinterJob(); // Specify the Printable is an instance of SimplePrint2D job.setPrintable(new SimplePrint2D()); // Put up the dialog box if (job.printDialog()) { // Print the job if the user didn't cancel printing try { job.print(); } catch (Exception e) { /* handle exception */ } } System.exit(0); } public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException { // pageIndex 0 to 4 corresponds to page numbers 1 to 5. if (pageIndex >= 5) return Printable.NO_SUCH_PAGE; Graphics2D g2 = (Graphics2D) g; // Use the font defined above g2.setFont(fnt); // Use the gradient color defined above g2.setPaint(pnt); g2.drawString("Page " + (pageIndex+1), 100f, 100f); return Printable.PAGE_EXISTS; } }
When a page painter’s print method is invoked several times for the same page, it must generate the same output each time.
There are many ways to ensure that repeated requests to render a page yield the same output. For example, to ensure that the same output is generated each time the printing system requests a particular page of a text file, page painter could either store and reuse file pointers for each page or store the actual page data.
In the following example, a “listing”
of a text file is printed. The name of the file is passed as an
argument to the main
method. The
PrintListingPainter
class stores the
file pointer in effect at the beginning of each new page it is
asked to render. When the same page is rendered again, the file
pointer is reset to the remembered position.
import java.awt.*; import java.awt.print.*; import java.io.*; public class PrintListing { public static void main(String[] args) { // Get a PrinterJob PrinterJob job = PrinterJob.getPrinterJob(); // Ask user for page format (e.g., portrait/landscape) PageFormat pf = job.pageDialog(job.defaultPage()); // Specify the Printable is an instance of // PrintListingPainter; also provide given PageFormat job.setPrintable(new PrintListingPainter(args[0]), pf); // Print 1 copy job.setCopies(1); // Put up the dialog box if (job.printDialog()) { // Print the job if the user didn't cancel printing try { job.print(); } catch (Exception e) { /* handle exception */ } } System.exit(0); } } class PrintListingPainter implements Printable { private RandomAccessFile raf; private String fileName; private Font fnt = new Font("Helvetica", Font.PLAIN, 10); private int rememberedPageIndex = -1; private long rememberedFilePointer = -1; private boolean rememberedEOF = false; public PrintListingPainter(String file) { fileName = file; try { // Open file raf = new RandomAccessFile(file, "r"); } catch (Exception e) { rememberedEOF = true; } } public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException { try { // For catching IOException if (pageIndex != rememberedPageIndex) { // First time we've visited this page rememberedPageIndex = pageIndex; // If encountered EOF on previous page, done if (rememberedEOF) return Printable.NO_SUCH_PAGE; // Save current position in input file rememberedFilePointer = raf.getFilePointer(); } else raf.seek(rememberedFilePointer); g.setColor(Color.black); g.setFont(fnt); int x = (int) pf.getImageableX() + 10; int y = (int) pf.getImageableY() + 12; // Title line g.drawString("File: " + fileName + ", page: " + (pageIndex+1), x, y); // Generate as many lines as will fit in imageable area y += 36; while (y + 12 < pf.getImageableY()+pf.getImageableHeight()) { String line = raf.readLine(); if (line == null) { rememberedEOF = true; break; } g.drawString(line, x, y); y += 12; } return Printable.PAGE_EXISTS; } catch (Exception e) { return Printable.NO_SUCH_PAGE;} } }
Pageable
jobs are
suited for applications that build an explicit representation of a
document, page by page. The Book
class
is a convenient way to use Pageables
,
but you can also build your own Pageable
structures if Book
does not suit your
needs. This section shows you how to use Book
.
Although slightly more involved, Pageable
jobs are preferred over Printable
jobs because the printing system has more
flexibility. A major advantage of Pageables
is that the number of pages in the
document is usually known and can be displayed to the user in the
print dialog box. This helps the user to confirm that the job is
specified correctly or to select a range of pages for printing.
A Book
represents a
collection of pages. The pages in a book do not have to share the
same size, orientation, or page painter. For example, a
Book
might contain two letter size pages
in portrait orientation and a letter size page in landscape
orientation.
When a Book
is first
constructed, it is empty. To add pages to a Book
, you use the append
method. This method takes a PageFormat
object that defines the page’s size, printable area, and
orientation and a page painter that implements the Printable
interface.
Multiple pages in a Book
can share the same page format and painter. The
append
method is overloaded to enable
you to add a series of pages that have the same attributes by
specifying a third parameter, the number of pages.
If you don’t know the total number of pages
in a Book
, you can pass UNKNOWN_NUMBER_OF_PAGES
to the append
method. The printing system will then call
your page painters in order of increasing page index until one of
them returns NO_SUCH_PAGE
.
The setPage
method can
be used to change a page’s page format or painter. The page
to be changed is identified by a page index that indicates the
page’s location in the Book
.
You call setPageable
and pass in the Book
to prepare the
print job. The setPageable
and
setPrintable
methods are mutually
exclusive; that is, you should call one or the other but not both
when preparing the PrinterJob
.
In the following example, a Book
is used to reproduce the first simple printing
example. (Because this case is so simple, there is little benefit
in using a Pageable
job instead of a
Printable
job, but it illustrates the
basics of using a Book
.) Note that you
still have to implement the Printable
interface and perform page rendering in the page painter’s
print
method.
import java.awt.*; import java.awt.print.*; public class SimplePrintBook implements Printable { private static Font fnt = new Font("Helvetica",Font.PLAIN,24); public static void main(String[] args) { // Get a PrinterJob PrinterJob job = PrinterJob.getPrinterJob(); // Set up a book Book bk = new Book(); bk.append(new SimplePrintBook(), job.defaultPage(), 5); // Pass the book to the PrinterJob job.setPageable(bk); // Put up the dialog box if (job.printDialog()) { // Print the job if the user didn't cancel printing try { job.print(); } catch (Exception e) { /* handle exception */ } } System.exit(0); } public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException { g.setFont(fnt); g.setColor(Color.green); g.drawString("Page " + (pageIndex+1), 100, 100); return Printable.PAGE_EXISTS; } }
In the following example, two different page painters are used: one for a cover page and one for content pages. The cover page is printed in landscape mode and the contents pages are printed in portrait mode.
import java.awt.*; import java.awt.print.*; public class PrintBook { public static void main(String[] args) { // Get a PrinterJob PrinterJob job = PrinterJob.getPrinterJob(); // Create a landscape page format PageFormat pfl = job.defaultPage(); pfl.setOrientation(PageFormat.LANDSCAPE); // Set up a book Book bk = new Book(); bk.append(new PaintCover(), pfl); bk.append(new PaintContent(), job.defaultPage(), 2); // Pass the book to the PrinterJob job.setPageable(bk); // Put up the dialog box if (job.printDialog()) { // Print the job if the user didn't cancel printing try { job.print(); } catch (Exception e) { /* handle exception */ } } System.exit(0); } } class PaintCover implements Printable { Font fnt = new Font("Helvetica-Bold", Font.PLAIN, 72); public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException { g.setFont(fnt); g.setColor(Color.black); int yc = (int) (pf.getImageableY() + pf.getImageableHeight()/2); g.drawString("Widgets, Inc.", 72, yc+36); return Printable.PAGE_EXISTS; } } class PaintContent implements Printable { public int print(Graphics g, PageFormat pf, int pageIndex) throws PrinterException { Graphics2D g2 = (Graphics2D) g; int useRed = 0; int xo = (int) pf.getImageableX(); int yo = (int) pf.getImageableY(); // Fill page with circles or squares, alternating red & green for (int x = 0; x+28 < pf.getImageableWidth(); x += 36) for (int y = 0; y+28 < pf.getImageableHeight(); y += 36) { if (useRed == 0) g.setColor(Color.red); else g.setColor(Color.green); useRed = 1 - useRed; if (pageIndex % 2 == 0) g.drawRect(xo+x+4, yo+y+4, 28, 28); else g.drawOval(xo+x+4, yo+y+4, 28, 28); } return Printable.PAGE_EXISTS; } }
Contents | Previous | Next |