Printing in Java

In the following sections, we will show you how you can easily print a drawing on a single sheet of paper, how you can manage a multipage printout, and how you can save a printout as a PostScript file.

1. Graphics Printing

In this section, we will tackle what is probably the most common printing situation: printing a 2D graphic. Of course, the graphic can contain text in various fonts or even consist entirely of text.

To generate a printout, you have to take care of these two tasks:

  • Supply an object that implements the Printable interface
  • Start a print job

The Printable interface has a single method:

int print(Graphics g, PageFormat format, int page)

That method is called whenever the print engine needs to have a page for­matted for printing. Your code draws the text and the images to be printed onto the graphics context. The page format tells you the paper size and the print margins. The page number tells you which page to render.

To start a print job, use the PrinterJob class. First, call the static getPrinterJob method to get a print job object. Then set the Printable object that you want to print.

Printable canvas = . . .;

PrinterJob job = PrinterJob.getPrinterJob();

job.setPrintable(canvas);

Before starting the print job, you should call the printDialog method to display a print dialog box (see Figure 11.60). That dialog box gives the user a chance to select the printer to be used (in case multiple printers are available), the page range that should be printed, and various printer settings.

Collect printer settings in an object of a class that implements the PrintRequestAttributeSet interface, such as the HashPrintRequestAttributeSet class.

var attributes = new HashPrintRequestAttributeSet();

Add attribute settings and pass the attributes object to the printDialog method.

The printDialog method returns true if the user clicked OK and false if the user canceled the dialog box. If the user accepted, call the print method of the PrinterJob class to start the printing process. The print method might throw a PrinterException. Here is the outline of the printing code:

if (job.printDialog(attributes))

{

try

{

job.print(attributes);

}

catch (PrinterException exception)

{

}

}

During printing, the print method of the PrinterJob class makes repeated calls to the print method of the Printable object associated with the job.

Since the job does not know how many pages you want to print, it simply keeps calling the print method. As long as the print method returns the value Printable.PAGE_EXISTS, the print job keeps producing pages. When the print method returns Printable.NO_SUCH_PAGE, the print job stops.

Therefore, the print job doesn’t have an accurate page count until after the printout is complete. For that reason, the print dialog box can’t display the correct page range—instead, it displays “Pages 1 to 1.” You will see in the next section how to avoid this blemish by supplying a Book object to the print job.

During the printing process, the print job repeatedly calls the print method of the Printable object. The print job is allowed to make multiple calls for the same page. You should therefore not count pages inside the print method but always rely on the page number parameter. There is a good reason why the print job might call the print method repeatedly for the same page. Some printers, in particular dot-matrix and inkjet printers, use banding. They print one band at a time, advance the paper, and then print the next band. The print job might use banding even for laser printers that print a full page at a time—it gives the print job a way of managing the size of the spool file.

If the print job needs the Printable object to print a band, it sets the clip area of the graphics context to the requested band and calls the print method. Its drawing operations are clipped against the band rectangle, and only those drawing elements that show up in the band are rendered. Your print method need not be aware of that process, with one caveat: It should not interfere with the clip area.

The PageFormat parameter of the print method contains information about the printed page. The methods getWidth and getHeight return the paper size, measured in points. (One point is 1/72 of an inch; an inch equals 25.4 millimeters.) For example, A4 paper is approximately 595 x 842 points, and US Letter paper is 612 x 792 points.

Points are a common measurement in the printing trade in the United States. Much to the chagrin of the rest of the world, the printing package uses point units. There are two purposes for that: paper sizes and paper margins are measured in points, and points are the default unit for all print graphics contexts. You can verify that in the example program at the end of this section. The program prints two lines of text that are 72 units apart. Run the example program and measure the distance between the baselines; they are exactly 1 inch or 25.4 millimeters apart.

The getWidth and getHeight methods of the PageFormat class give you the complete paper size. Not all of the paper area is printable. Users typically select margins, and even if they don’t, printers need to somehow grip the sheets of paper on which they print and therefore have a small unprintable area around the edges.

The methods getlmageabteWidth and getlmageabteHeight tell you the dimensions of the area that you can actually fill. However, the margins need not be symmet­rical, so you must also know the top left corner of the imageable area (see Figure 11.61), which you obtain by the methods getlmageabteX and getlmageabteY.

If you want your users to choose the settings for the page margins or to switch between portrait and landscape orientation without setting other printing attributes, call the pageDialog method of the PrinterJob class:

PageFormat format = job.pageDialog(attributes);

The program in Listings 11.23 and 11.24 shows how to render the same set of shapes on the screen and on the printed page. A subclass of JPanel imple­ments the Printable interface. Both the paintComponent and the print methods call the same method to carry out the actual drawing.

class PrintPanel extends JPanel implements Printable

{

public void paintComponent(Graphics g)

{

super.paintComponent(g); var g2 = (Graphics2D) g; drawPage(g2);

}

public int print(Graphics g, PageFormat pf, int page) throws PrinterException {

if (page >= 1) return Printable.NO_SUCH_PAGE; var g2 = (Graphics2D) g;

g2.translate(pf.getImageableX(), pf.getImageableY());

drawPage(g2);

return Printable.PAGE_EXISTS;

}

public void drawPage(Graphics2D g2)

{

// shared drawing code goes here

}

}

This example displays and prints the image shown in Figure 11.50—namely, the outline of the message “Hello, World” used as a clipping area for a pattern of lines.

Click the Print button to start printing, or click the Page setup button to open the page setup dialog box. Listing 11.23 shows the code.

2. Multiple-Page Printing

In practice, you usually don’t pass a raw Printable object to a print job. Instead, you should obtain an object of a class that implements the Pageable interface. The Java platform supplies one such class, called Book. A book is made up of sections, each of which is a Printable object. To make a book, add Printable objects and their page counts.

var book = new Book();

Printable coverPage = . . .;

Printable bodyPages = . . .;

book.append(coverPage, pageFormat); // append 1 page

book.append(bodyPages, pageFormat, pageCount);

Then, use the setPageabte method to pass the Book object to the print job.

printJob.setPageabte(book);

Now the print job knows exactly how many pages to print, so the print dialog box displays an accurate page range and the user can select the entire range or subranges.

From your perspective as a programmer, the biggest challenge of using the Book class is that you must know how many pages each section will have when you print it. Your Printable class needs a layout algorithm that computes the layout of the material on the printed pages. Before printing starts, invoke that algorithm to compute the page breaks and the page count. You can retain the layout information so you have it handy during the printing process.

You must guard against the possibility that the user has changed the page format. If that happens, you must recompute the layout, even if the information that you want to print has not changed.

Listing 11.26 shows how to produce a multipage printout. This program prints a message in very large characters on a number of pages (see Figure 11.63). You can then trim the margins and tape the pages together to form a banner.

The layoutPages method of the Banner class computes the layout. We first lay out the message string in a 72-point font. We then compute the height of the re­sulting string and compare it with the imageable height of the page. We derive a scale factor from these two measurements. When printing the string, we magnify it by that scale factor.

The getPageCount method of the Banner class first calls the layout method. Then it scales up the width of the string and divides it by the imageable width of each page. The quotient, rounded up to the next integer, is the page count.

It sounds like it might be difficult to print the banner because characters can be broken across multiple pages. However, thanks to the power of the Java 2D API, this turns out not to be a problem at all. When a particular page is requested, we simply use the translate method of the Graphics2D class to shift the top left corner of the string to the left. Then, we set a clip rectangle that equals the current page (see Figure 11.64). Finally, we scale the graphics context with the scale factor that the layout method computed.

This example shows the power of transformations. The drawing code is kept simple, and the transformation does all the work of placing the drawing in the appropriate place. Finally, the clip cuts away the part of the image that falls outside the page. This program shows another compelling use of transformations—to display a print preview.

3. Print Services

So  far, you have seen how to print 2D graphics. However, the printing API introduced in Java SE 1.4 affords far greater flexibility. The API defines a number of data types and lets you find print services that are able to print them. Among the data types are

  • Images in GIF, JPEG, or PNG format
  • Documents in text, HTML, PostScript, or PDF format
  • Raw printer code data
  • Objects of a class that implements Printable, Pageable, or RenderableImage

The data themselves can be stored in a source of bytes or characters such as an input stream, a URL, or an array. A document flavor describes the combina­tion of a data source and a data type. The DocFlavor class defines a number of inner classes for the various data sources. Each of the inner classes defines constants to specify the flavors. For example, the constant

DocFlavor.INPUT_STREAM.GIF

describes a GIF image that is read from an input stream. Table 11.4 lists the combinations.

Suppose you want to print a GIF image located in a file. First, find out whether there is a print service that is capable of handling the task. The static lookupPrintServices method of the PrintServiceLookup class returns an array of PrintService objects that can handle the given document flavor.

DocFlavor flavor = DocFlavor.INPUT_STREAM.GIF;

PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, null);

The second parameter of the lookupPrintServices method is null to indicate that we don’t want to constrain the search by specifying printer attributes. We’ll cover attributes in the next section.

If the lookup yields an array with more than one element, select from the listed print services. You can call the getName method of the PrintService class to get the printer names and let the user choose.

Next, get a document print job from the service:

DocPrintJob job = services[i].createPrintJob();

For printing, you need an object that implements the Doc interface. The Java library supplies a class SimpleDoc for that purpose. The SimpleDoc constructor re­quires the data source object, the document flavor, and an optional attribute set. For example,

var in = new FileInputStream(fileName);

var doc = new SimpleDoc(in, flavor, null);

Finally, you are ready to print:

job.print(doc, null);

As before, the null parameter can be replaced by an attribute set.

Note that this printing process is quite different from that of the preceding section. There is no user interaction through print dialog boxes. For example, you can implement a server-side printing mechanism in which users submit print jobs through a web form.

4. Stream Print Services

A print service sends print data to a printer. A stream print service generates the same print data but instead sends them to a stream, perhaps for delayed printing or because the print data format can be interpreted by other programs. In particular, if the print data format is PostScript, it may be useful to save the print data to a file because many programs can process PostScript files. The Java platform includes a stream print service that can produce PostScript output from images and 2D graphics. You can use that service on all systems, even if there are no local printers.

Enumerating stream print services is a bit more tedious than locating regular print services. You need both the DocFtavor of the object to be printed and the MIME type of the stream output. You then get a StreamPrintServiceFactory array of factories.

DocFtavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;

String mimeType = “application/postscript”;

StreamPrintServiceFactory[] factories

= StreamPrintServiceFactory.lookupStreamPrintServiceFactories(flavor, mimeType);

The StreamPrintServiceFactory class has no methods that would help us distinguish any one factory from another, so we just take factories [0]. We call the getPrintService method with an output stream parameter to get a StreamPrintService object.

var out = new FileOutputStream(fileName);

StreamPrintService service = factories[0].getPrintService(out);

The StreamPrintService class is a subclass of PrintService. To produce a printout, simply follow the steps of the preceding section.

The program in Listing 11.29 demonstrates how to use a stream print service to print Java 2D shapes to a PostScript file. You can replace the sample drawing code with code that generates any Java 2D shapes and have the shapes converted to PostScript. Then you can easily convert the result to PDF or EPS, using an external tool. (Unfortunately, Java does not support printing to PDF directly.)

5. Printing Attributes

The print service API contains a complex set of interfaces and classes to specify various kinds of attributes. There are four important groups of attributes. The first two specify requests to the printer.

  • Print request attributes request particular features for all doc objects in a print job, such as two-sided printing or the paper size.
  • Doc attributes are request attributes that apply only to a single doc object. The other two attributes contain information about the printer and job status.
  • Print service attributes give information about the print service, such as the printer make and model or whether the printer is currently accepting jobs.
  • Print job attributes give information about the status of a particular print job, such as whether the job is already completed.

To describe the various attributes, there is an interface Attribute with subinterfaces:

PrintRequestAttribute

DocAttribute

PrintServiceAttribute

PrintJobAttribute

SupportedValuesAttribute

Individual attribute classes implement one or more of these interfaces. For example, objects of the Copies class describe the number of copies of a printout. That class implements both the PrintRequestAttribute and the PrintJobAttribute inter­faces. Clearly, a print request can contain a request for multiple copies. Conversely, an attribute of the print job might be how many of these copies were actually printed. That number might be lower, perhaps because of printer limitations or because the printer ran out of paper.

The SupportedValuesAttribute interface indicates that an attribute value does not reflect actual request or status data but rather the capability of a service. For example, the CopiesSupported class implements the SupportedValuesAttribute interface. An object of that class might describe that a printer supports 1 through 99 copies of a printout.

In addition to the interfaces and classes for individual attributes, the print service API defines interfaces and classes for attribute sets. A superinterface, AttributeSet, has four subinterfaces:

PrintRequestAttributeSet

DocAttributeSet

PrintServiceAttributeSet

PrintJobAttributeSet

Each of these interfaces has an implementing class, yielding the five classes:

HashAttributeSet

HashPrintRequestAttributeSet

HashDocAttributeSet

HashPrintServiceAttributeSet

HashPrintJobAttributeSet

Figure 11.66 shows a class diagram of the attribute set hierarchy.

For example, you can construct a print request attribute set like this:

var attributes = new HashPrintRequestAttributeSet();

After constructing the set, you are freed from worrying about the Hash prefix.

Why have all these interfaces? They make it possible to check for correct at­tribute usage. For example, a DocAttributeSet accepts only objects that implement the DocAttribute interface. Any attempt to add another attribute results in a runtime error.

An attribute set is a specialized kind of map where the keys are of type Class and the values belong to a class that implements the Attribute interface. For example, if you insert an object

new Copies(10)

into an attribute set, then its key is the Class object Copies.class. That key is called the category of the attribute. The Attribute interface declares a method

Class getCategory()

that returns the category of an attribute. The Copies class defines the method to return the object Copies.ctass, but it isn’t a requirement that the category be the same as the class of the attribute.

When an attribute is added to an attribute set, the category is extracted automatically. Just add the attribute value:

attributes.add(new Copies(10));

If you subsequently add another attribute with the same category, it overwrites the first one.

To retrieve an attribute, you need to use the category as the key, for example:

AttributeSet attributes = job.getAttributes();

var copies = (Copies) attribute.get(Copies.ctass);

Finally, attributes are organized by the values they can have. The Copies at­tribute can have any integer value. The Copies class extends the IntegerSyntax class that takes care of all integer-valued attributes. The getVatue method returns the integer value of the attribute, for example:

int n = copies.getVatue();

The classes

TextSyntax

DateTimeSyntax

URISyntax

encapsulate a string, a date and time value, or a URI.

Finally, many attributes can take a finite number of values. For example, the PrintQuatity attribute has three settings: draft, normal, and high. They are represented by three constants:

PrintQuality.DRAFT

PrintQuatity.NORMAL

PrintQuality.HIGH

Attribute classes with a finite number of values extend the EnumSyntax class, which provides a number of convenience methods to set up these enumera­tions in a typesafe manner. You need not worry about the mechanism when using such an attribute. Simply add the named values to attribute sets:

attributes.add(PrintQuatity.HIGH);

Here is how you check the value of an attribute:

if (attributes.get(PrintQuatity.ctass) == PrintQuality.HIGH)

Table 11.5 lists the printing attributes. The second column lists the superclass of the attribute class (for example, IntegerSyntax for the Copies attribute) or the set of enumeration values for the attributes with a finite set of values. The last four columns indicate whether the attribute class implements the DocAttribute (DA), PrintJobAttribute (PJA), PrintRequestAttribute (PRA), and PrintServiceAttribute (PSA) interfaces.

You have now reached the end of this long chapter covering advanced Swing and AWT features. In the final chapter, we will turn to a different aspect of Java programming: interacting, on the same machine, with “native” code in a different programming language

Source: Horstmann Cay S. (2019), Core Java. Volume II – Advanced Features, Pearson; 11th edition.

Leave a Reply

Your email address will not be published. Required fields are marked *