Contents | Previous | Next Programmer's Guide to the JavaTM 2D API

Chapter    7

Printing

The Java Printing API enables applications to:

Not all of these features are supported in the Java™ 2 SDK Printing API and implementation. The API will be extended to support all of these features in future releases. For example, additional printer controls will be added by augmenting the set of named properties of a print job that the application can control.

7.1 Interfaces and Classes

Interface
Description
Printable
The Printable interface is implemented by each page painter, the application class(es) called by the printing system to render a page. The system calls the page painter’s print method to request that a page be rendered.
Pageable
The Pageable interface is implemented by a document that is to be printed by the printing system. Through the Pageable methods, the system can determine the number of pages in the document, the format to use for each page, and the page painter to use to render each page.
PrinterGraphics
The Graphics2D objects that a page painter uses to render a page implement the PrinterGraphics interface. This enables an application to get the PrinterJob object that is controlling the printing.
Class
Description
Book
Implements: Pageable
Represents a document in which pages can have different page formats and page painters. This class uses the Pageable interface to interact with a PrinterJob.
PageFormat
Describes the size and orientation of a page to be printed, as well as the Paper used to print it. For example, portrait and landscape paper orientations are represented by PageFormat.
Paper
Describes the physical characteristics of a piece of paper.
PrinterJob
The principal class that controls printing. The application calls PrinterJob methods to set up a job, display a print dialog to the user (optional), and to print the pages in the job.

7.2 Printing Concepts

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.

7.2.1 Supporting Printing

An application has to perform two tasks to support printing:

7.2.1.1 Job Control

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.

7.2.1.2 Imaging

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.

7.2.2 Page Painters

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:

public int print(Graphics g, PageFormat pf, int pageIndex)  

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.

7.2.3 Printable Jobs and Pageable Jobs

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:

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:

7.2.4 Typical Life-Cycle of a PrinterJob

An application steers the PrinterJob object through a sequence of steps to complete a printing job. The simplest sequence used by an application is:

  1. Get a new PrinterJob object by calling PrinterJob.getPrinterJob.
  2. Determine what 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.
  3. Specify the characteristics of the job to be printed to the PrinterJob. For a Printable job, call setPrintable; for a Pageable job, call setPageable. Note that a Book object is ideal for passing to setPageable.
  4. Specify additional print job properties, such as the number of copies to print or the name of the job to print on the banner page.
  5. Call 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.
  6. Call 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:

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.

7.2.5 Dialogs

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 Java™ 2 SDK software 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.

7.2.5.1 Page setup dialog

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.

7.2.5.2 Print dialog

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.

7.3 Printing with Printables

To provide basic printing support:

  1. Implement the Printable interface to provide a page painter that can render each page to be printed.
  2. Create a PrinterJob.
  3. Call setPrintable to tell the PrinterJob how to print your document.
  4. Call 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;    
  }  
} 

7.3.1 Using Graphics2D for Rendering

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;    
  }  
} 

7.3.2 Printing a File

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;} 
  }  
} 

7.4 Printing with Pageables and Books

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.

7.4.1 Using a Pageable Job

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;    
  } 
} 

7.4.2 Using Multiple Page Painters

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 Programmer's Guide to the JavaTM 2D API
JavaTM 2 SDK, Standard Edition, 1.4 version