Printing

   

The ability to print graphics to a printing device was introduced in Java 1.1. However, it's only through Java 2D that printing has become sophisticated. You can use Java 2D's printing API to perform the following tasks :

  • Print Java 2D and AWT graphics (including text and images)

  • Control document composition (such as reverse order printing)

  • Invoke printer-specific functions (such as stapling)

Note

Not all the aforementioned tasks are completely implemented in Version 1.3 of Java 2.


The printing API is located in the java.awt.print package. This package contains three interfaces and four classes, which are described in Table 18.3.

Table 18.3. Printing Classes and Interfaces
Interface/Class Description
Book This class provides a representation of a document in which pages might have different page formats and page painters .
Pageable This interface is implemented by classes whose objects represent a set of pages to be printed.
PageFormat T his class describes the size and orientation of a page to be printed.
Paper This class describes the physical characteristics of a piece of paper.
Printable This interface is implemented by classes whose objects represent page painters.
PrinterGraphics This interface is implemented by Graphics subclass objects that are passed to page painters for rendering pages.
PrinterJob This class is the main class that controls printing.

The Printing Framework

The printing API works in conjunction with a printing framework that uses a callback printing model. According to this model, it is the printing framework (and not the application) that controls when pages are printed. Essentially, an application provides information about a document to be printed, and the printing framework informs the application to render each page when needed. (The callback printing model is similar to the callback painting model that was discussed in Chapter 13, "Beginning AWT." )

The printing framework can request a certain page to be rendered multiple times, or request that pages be rendered out of order. It's the application's responsibility to render the correct page, no matter which page is requested .

The callback-printing model offers a higher degree of flexibility than is found in the traditional application-driven model. As an example, the printing framework will ask an application to render pages in reverse order, so that a printer capable of stacking pages in reverse order receives the final stack of pages in nonreverse order.

Another feature of the callback-printing model is that it allows applications to render content to bitmap printers that don't have enough memory to hold an entire page. In this situation, a page is printed as a series of bands (small bitmaps). For example, if a printer only has one-fifth the memory required to hold an entire page, this page is divided into five bands and the application is requested to render this page five times, with each rendering producing an appropriate band that is sent to the printer.

Job Control and Rendering

When it comes time to print, an application initiates and manages a printer job on behalf of a user 's request. (This request can be specified by selecting an item from a menu, or through some other means). To initiate printing, the application must first obtain a subclass object from the abstract PrinterJob class as shown in the following code fragment:

 PrinterJob job = PrinterJob.getPrinterJob (); 

Each printer job requires a page painter (an object that implements the Printable interface) to take care of rendering content. This page painter can be easily created by implementing Printable 's solitary print method. The following code fragment shows print 's signature:

 int print(Graphics graphics, PageFormat pageFormat, int pageIndex) 

The graphics argument is a reference to an object created from a subclass of Graphics. (This object's class implements the PrinterGraphics interface. As a result, you can cast graphics from Graphics ”or Graphics2D ”to PrinterGraphics, and then call its getPrinterJob method to return the printer job.) The pageFormat argument is a reference to a page formatter (an object that describes the size and orientation of a page being rendered). Finally, pageIndex provides a zero-based index that identifies the page to be rendered. An index of 0 corresponds to the first page, 1 corresponds to the second page, and so on.

The print method returns Printable.PAGE_EXISTS to indicate that a requested page was rendered, or Printable.NO_SUCH_PAGE to indicate that pageIndex was too large, and printing should end.

A page formatter determines whether a page has portrait or landscape orientation. (Portrait is the default.) If portrait orientation is used, a page's x-axis will be aligned with the page's width and the y-axis will be aligned with the page's height. When landscape orientation is used, the x-axis aligns with the page's height and the y-axis aligns with the page's width. PageFormat 's getOrientation method returns the current orientation and its setOrientation (int o) method sets this orientation to o. ( PageFormat contains PORTRAIT, LANDSCAPE, and REVERSE_LANDSCAPE constants that should be used when specifying the orientation.)

A page formatter contains a Paper object that describes a paper's physical characteristics. PageFormat 's setPaper method sets the current paper and its getPaper method retrieves this paper.

Paper 's setSize method can be called to set a paper's horizontal and vertical size (in units of 1/72 inches). At any point, its getWidth and getHeight methods can be called to retrieve this size.

Because some printers cannot render content on an entire piece of paper, each Paper object specifies an imageable area (the region of a paper on which it's safe to render). This imageable area can be specified by calling Paper 's setImageableArea method. At any point, its getImageableX, getImageableY, getImageableWidth, and getImageableHeight methods can be called to return the imageable area's current origin (located in the upper-left corner of the paper) and dimensions (where x values increase to the right and y values increase to the bottom).

Note

PageFormat contains getImageableX, getImageableY, getImageableWidth, and getImageableHeight methods. Although similar to Paper 's same-named methods, these methods take orientation into account.


A user can alter page format information by requesting that the application display a page dialog box. (This request can be made from an application's menu item.) To display this dialog box, the application would call PrinterJob 's pageDialog method. (The page dialog box is initialized by using the PageFormat argument passed to pageDialog. ) If the user clicks the OK button, the PageFormat instance is cloned, altered to reflect the user's selections, and then returned. If the user cancels the dialog box, pageDialog returns the original unaltered PageFormat object. The following code fragment shows how to create a PageFormat object, and then provide a user with an opportunity to modify this format, via a page dialog box:

 PageFormat pf = new PageFormat (); pf = job.pageDialog (pf); 

What comes next depends on the kind of printer job being initiated: a printable or a pageable. In a nutshell , printables are simple printer jobs, whereas pageables are more complex printer jobs.

For a printable, your code would pass an object, whose class implements the Printable interface, to PrinterJob 's setPrintable method. This provides the printing framework with the location of the print method, which it calls when necessary. The following code fragment shows how to accomplish this objective. (It assumes that the current object provides the print method.)

 job.setPrintable (this); 

This version of the setPrintable method assumes that a default page formatter is being used. However, you can specify your own page formatter and pass it to a second version of setPrintable, as shown in the following code fragment:

 job.setPrintable (this, pf); 

For a pageable, your code would create a Book object. It would then call one of Book 's two overloaded append methods to append a series of instructions for how one or more pages are rendered. Each append method call specifies the location of the print method (via an object whose class implements Printable ), the page format to be used, and ( optionally ) how many pages must be printed using the information passed to the append method. After this is done, your code would call PrinterJob 's setPageable method and pass the Book object as this method's solitary argument, so that the printing framework knows the locations of all print methods that render the various pages contributing to the pageable. The following code fragment shows how to take care of this task:

 Book bk = new Book (); bk.append (this, pf, 2); job.setPageable (bk); 

The book, referenced by bk, is instructed to print two pages using the page formatter specified by pf. Furthermore, the print method is located in the current object (that is, the object referenced by this ).

At this point, your code could specify additional printer job properties, such as the number of copies to print or the name of the job to be displayed on a banner page. To demonstrate , the following code fragment shows how you would specify two copies to be printed, along with a name for this printer job:

 job.setCopies (2); job.setJobName ("Resume"); 

The number of copies and job name can be accessed from inside a print method by casting its Graphics argument to a PrinterGraphics argument, calling PrinterGraphics ' getPrinterJob method, and finally calling getCopies and getJobName ”as demonstrated by the following code fragment:

 PrinterGraphics p = (PrinterGraphics) g; System.out.println (p.getPrinterJob ().getCopies ()); System.out.println (p.getPrinterJob ().getJobName ()); 

The next task depends on whether the application is designed to be interactive. If interactive, a print dialog box must be displayed. A user can then choose how printing will proceed.

To display this print dialog box, an application calls PrinterJob 's printDialog method. A user's choices are constrained by the number and format of pages in the printable or pageable. The number of copies, previously specified by setCopies, is also displayed. If the user clicks OK, printDialog returns a Boolean true value and printing can continue. However, if the user cancels this dialog box, by clicking the Cancel button, false is returned and the printer job should be abandoned .

Assuming that the user chose to print, PrinterJob 's print method is called to perform the actual printing. This method throws a PrinterException object if an error, occurring during printing, results in the job being aborted.

The printer 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. As a result, the state of the PrinterJob subclass object might not reflect the state of the actual job being printed. Furthermore, because the state of a PrinterJob subclass object changes during its life, it's illegal to invoke certain methods at certain times. For example, if setPageable is called after you've called print, the PrinterJob subclass object throws an IllegalStateException object.

Playing with Printables

Printables are the simplest printer jobs. Only a single page painter is required. Furthermore, 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 a page. Pages are requested in order, beginning at page index 0. However, a page painter might be requested to render a page several times before advancing to the next page. (Remember banding?) After the last page has been printed, the page painter's print method will return Printable 's NO_SUCH_PAGE constant. The following points summarize printable job features:

  • All pages use the same page painter and page formatter. If a print dialog box is presented, it will not display the number of pages in the document, because that information isn't available to the printing framework. (Under Windows 98, the print dialog box typically shows 10,000 pages ”from 0 to 9,999.)

  • The printing framework will always ask the page painter to print each page in indexed order, starting with page index 0. No pages are skipped . For example, if a user wants pages 7 and 8 to be printed, the page painter will be called with page indexes 0 (for page 1) through 7 (for page 8).

  • The page painter informs the printing system when the end of the document has been reached.

  • All page painters are called in the same thread.

  • Some printing systems might not be able to achieve the ideal output. For example, the stack of pages emerging from the printer might be in the wrong order, or the pages might not be collated if multiple copies are requested.

Listing 18.27 presents source code to a PrintDemo1 application that creates and demonstrates a printable.

Listing 18.27 The PrintDemo1 Application Source Code
 // PrintDemo1.java import java.awt.*; import java.awt.geom.*; import java.awt.print.*; class PrintDemo1 implements Printable {    public static void main (String [] args)    {       // Get a printer job.       PrinterJob job = PrinterJob.getPrinterJob ();       // Specify the printable to be an instance of PrintDemo1.       job.setPrintable (new PrintDemo1 ());       // Specify number of copies and job name.       job.setCopies (2);       job.setJobName ("Printable");       // Put up the dialog box.       if (job.printDialog ())       {           // Print the job if the user didn't cancel printing.           try           {              job.print ();           }           catch (PrinterException e)           {              System.out.println (e);           }       }       System.exit (0);    }    public int print (Graphics g, PageFormat pf, int pageIndex)               throws PrinterException    {       // pageIndex 0 corresponds to page number 1.       if (pageIndex >= 1)           return Printable.NO_SUCH_PAGE;       PrinterGraphics p = (PrinterGraphics) g;       System.out.println (p.getPrinterJob ().getCopies ());       System.out.println (p.getPrinterJob ().getJobName ());       Graphics2D g2 = (Graphics2D) g;       double w = pf.getImageableWidth ();       double h = pf.getImageableHeight ();       int xo = (int) pf.getImageableX ();       int yo = (int) pf.getImageableY ();       Rectangle2D r = new Rectangle2D.Double (xo, yo, w, h);       g2.setColor (Color.red);       g2.draw (r);       for (int x = 0; x + 32 < w; x += 36)            for (int y = 0; y + 32 < h; y += 36)       {            g2.setColor (new Color (rnd (256), rnd (256), rnd (256)));            Shape s = new Ellipse2D.Double (xo + x + 4, yo + y + 4,                                            32, 32);            g2.fill (s);             }       return Printable.PAGE_EXISTS;      }    int rnd (int limit)    {       return ((int) (Math.random () * limit));    }  } 

PrintDemo1 is not that remarkable . When run, its main method creates a printer job and gives a user a chance to print this job, by way of a print dialog box. If the user chooses OK, the print method is called to print randomly colored circles in a rectangular outline of the imageable area.

The print method is called once for each page that the user selects from the print dialog box. The pageIndex argument identifies the current page being printed. (The first page is assigned page index 0.) When the print method "decides" that there are no more pages to print, Printable.NO_SUCH_PAGE is returned. This causes printing to terminate. However, for each successfully printed page, Printable.PAGE_EXISTS is returned. (To keep printing pages, you must return PAGE_EXISTS. )

Playing with Pageables

Pageable jobs are more flexible (and complex) than their printable cousins. For example, each page in a pageable job can have a different layout and implementation. The Book class is used to manage a pageable job. Through this job, the printing framework is able to determine how many pages need to be printed, along with the page painter and page formatter to be used for each page. Applications (such as word processors) that need to print documents with a planned structure and format should use pageable jobs. The following points summarize pageable job features:

  • Different pages can use different page painters and page formatters.

  • The printing system can ask page painters to print pages in an arbitrary order and some pages might be skipped. For example, if a user asks to print pages 2 and 3 of a document, the page painter will be called with indices 1 (for page 2) and 2 (for page 3). Page index 0 (for page 1) will be skipped.

  • 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 (for page 3) and 1 (for page 2).

Listing 18.28 presents source code to a PrintDemo2 application that creates and demonstrates a pageable.

Listing 18.28 The PrintDemo2 Application Source Code
 // PrintDemo2.java import java.awt.*; import java.awt.geom.*; import java.awt.print.*; class PrintDemo2 {    public static void main (String [] args)    {       // Get a PrinterJob       PrinterJob job = PrinterJob.getPrinterJob ();       // Create a landscape page format.       PageFormat pf = job.defaultPage ();       pf.setOrientation (PageFormat.LANDSCAPE);       // Set up a book.       Book bk = new Book ();           bk.append (new paintCover (), pf);       bk.append (new paintContent (), job.defaultPage (), 1);       // Pass the book to the PrinterJob.       job.setPageable (bk);       // Specify the job name.       job.setJobName ("My book");       // Put up the dialog box.       if (job.printDialog ())       {           // Print the job if the user didn't cancel printing           try           {              job.print ();           }           catch (PrinterException e)           {              System.out.println (e);           }        }      System.exit (0);    } } class paintContent implements Printable {    public int print (Graphics g, PageFormat pf, int pageIndex)               throws PrinterException    {       System.out.println ("Page index = " + pageIndex);       // pageIndex 1 corresponds to page number 2.       if (pageIndex > 2)           return Printable.NO_SUCH_PAGE;       Graphics2D g2 = (Graphics2D) g;       double w = pf.getImageableWidth ();       double h = pf.getImageableHeight ();       int xo = (int) pf.getImageableX ();       int yo = (int) pf.getImageableY ();       Rectangle2D r = new Rectangle2D.Double (xo, yo, w, h);       g2.setColor (Color.red);       g2.draw (r);       for (int x = 0; x + 32 < w; x += 36)            for (int y = 0; y + 32 < h; y += 36)       {            g2.setColor (new Color (rnd (256), rnd (256), rnd (256)));            Shape s = new Ellipse2D.Double (xo + x + 4, yo + y + 4,                                            32, 32);            g2.fill (s);             }       return Printable.PAGE_EXISTS;      }    int rnd (int limit)    {       return ((int) (Math.random () * limit));    }  } class paintCover implements Printable {    Font fnt = new Font ("TimesRoman", Font.ITALIC, 72);          public int print (Graphics g, PageFormat pf, int pageIndex)               throws PrinterException    {       Graphics2D g2 = (Graphics2D) g;       double w = pf.getImageableWidth ();       double h = pf.getImageableHeight ();       int xo = (int) pf.getImageableX ();       int yo = (int) pf.getImageableY ();       Rectangle2D r = new Rectangle2D.Double (xo, yo, w, h);       g2.setColor (Color.red);       g2.draw (r);       PrinterGraphics p = (PrinterGraphics) g2;       String s = p.getPrinterJob ().getJobName ();       int x = s.length () / 10;       g2.setFont (fnt);       int y = g2.getFont ().getSize () * 5 / 2;       g2.translate (xo + x, yo + y);       AffineTransform old = g2.getTransform  ();       g2.shear (-0.95, 0.0);       g2.scale (1.0, 3.0);       g2.setPaint (Color.lightGray);       g2.drawString (s, 0, 0);       g2.setTransform (old);       g2.setPaint (Color.black);       g2.drawString (s, 0, 0);       return Printable.PAGE_EXISTS;      }  } 

PrintDemo2 creates a pageable with two different page painters. The first page painter renders a cover. It gets the cover's name from the job name. The second page painter paints the content of the book. It uses the same page painter as used by PrintDemo1.

Quick and Dirty Printing

Suppose you need to send some text to a printer and don't want to bother with the printing framework. There is a very simple (and nonportable) way in which you can accomplish this task. The trick is to create a PrintWriter object that's attached to a FileWriter object. In turn , the FileWriter object is attached to a printer device. Listing 18.29 contains source code to a PrintDemo3 application that illustrates sending text to a Windows printer device, code named LPT1:.

Listing 18.29 The PrintDemo3 Application Source Code
 // PrintDemo3.java import java.io.*; class PrintDemo3 {    static String s = "This paragraph is being sent to the printer.  " +                      "I'll bet you didn't think there was such an " +                      "easy way to accomplish this task.  The trick " +                      "is to create a FileWriter object that's " +                      "connected to a printer device, and then a " +                      "PrintWriter object that's connected to the " +                      "FileWriter.  Obviously, this isn't very " +                      "portable.";    public static void main (String [] args)    {       try       {           FileWriter fw = new FileWriter ("LPT1:");           PrintWriter pw = new PrintWriter (fw);           int i, len = s.length ();           for (i = 0; len > 80; i += 80)           {                pw.print (s.substring (i, i + 80));                pw.print ("\ r\ n");                len -= 80;           }           if (len > 0)           {               pw.print (s.substring (i));               pw.print ("\ r\ n");           }           pw.close ();       }       catch (IOException e)       {          System.out.println (e);       }    } } 

PrintDemo3 outputs the contents of a String object, referenced by s, to the printer attached to LPT1:. It uses a loop to output this string in 80 character lines. After each line is printed, a carriage return character '\ r' followed by a new-line character '\ n' are output. If not output, some printers (such as HP Deskjet printers) will truncate the line, and you'll only see the first 80 characters of the string.

Caution

Although PrintDemo3 's technique will work on many printers, there is no guarantee that it will work on your printer. For example, PrintDemo3 works on an HP Deskjet 970Cse printer, but doesn't work on a Lexmark 3200. Because it's nonportable, you should only use this technique for debugging and experimentation. In other words, don't use it in a production-quality program.


   


Special Edition Using Java 2 Standard Edition
Special Edition Using Java 2, Standard Edition (Special Edition Using...)
ISBN: 0789724685
EAN: 2147483647
Year: 1999
Pages: 353

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net