Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 11

Printing in Java, Part 1 - Acquaint yourself with the Java printing model

By Jean-pierre Dubé, JavaWorld.com, 10/20/00

Welcome to the first article in a five-part series on printing in Java. In this series, you will learn
the strengths and weaknesses of the Java printing API. My goal is to help you build a
framework that will work on top of the API to ease the burden of creating printed output. This
framework will allow you to create pages with running headers/footers, and insert paragraphs,
images, and tables.

This month, I will explain the terminology used in printing and introduce the Java printing
model and API. As you will see throughout this series, printing using the API is not easy;
rendering complex pages using a higher-level API might be helpful. That is why our goal will be
to build a framework that will provide all the fundamental functionality required to effortlessly
render pages. But first, let's learn the basics.

Printing in Java: Read the whole series!

 Part 1: Acquaint yourself with the Java printing model


 Part 2: Print your first page and render complex documents
 Part 3: Jean-Pierre Dubé introduces the print framework that works on top of the Java
Print API
 Part 4: Code the print framework
 Part 5: Discover the print framework's support classes

Definition of a page
Before we dive into the technicalities of the printing API, let's start by defining some
terminology that I will use throughout this series. Although this terminology might seem trivial,
it will help clear up some confusion about margins.

As you probably know, Gutenberg invented the first printing press. At that time, he had to
create a terminology to describe the layout of a page. Here is Gutenberg's definition of a page:
Figure 1. Layout of a portrait page

Figure 2. Layout of a landscape page


In Figures 1 and 2, we can see that the page is divided into several areas. The printer margins
make up the page's periphery. They are printer-dependent and define the minimum area that
the printer needs to feed the page. Printer margins are not user-definable. We seldom use or
know the sizes of printer margins, but some printer manufacturers will publish them in their
user manuals. In Java, you do not need to know these measurements; the API returns the
dimensions of the printable area.

Just inside the printer margins are the margins that define the contour of the page. Notice that
the left and right margins extend the length of the page minus the top and bottom printer
margins. The gutter on the left side of the page provides an additional margin that is used
primarily for binding the pages in a book. On a page printed in duplex mode -- that is, with
printing on both sides -- the gutter can also be found on the page's right side. To obtain the
total usable left or right margin, you add the gutter to either the left or right margin. The
printing API itself does not support the gutter, but our print framework will enable you to
define one. As strange as it may seem, the print API also fails to support margins. The only
way to set margins is to set the location and size of the printable area.

Finally, the area in the middle of the page is called the printable area. At first glance, the page
layout might look similar to the BorderLayout that we are accustomed to. However, in the
BorderLayout, both top and bottom components extend the width of the display area,
whereas in the physical page layout, the top and bottom margins are contained between the
left and right margins.
Units of measurement
When working with the Graphics2D class, it is important to understand the difference between
device space and user space. In the device space, you work in pixels using the resolution of
the device. A square of 100 pixels by 100 pixels drawn on a device that has a resolution of
1,024 pixels by 768 pixels will not be the same size as it is when rendered on a device that has
a resolution of 1,600 pixels by 1,400 pixels. The reason is simple: because the second device
features more pixels per inch, the square will appear smaller.
User space, on the other hand, allows us to think in terms of measurement units, regardless of
the device's resolution. When you create a Graphics2D object for a given device (printer or
screen), a default transform is generated to map the user space to the device space. In user
space, the default is set to 72 coordinates per inch. Instead of thinking in terms of pixels, you
think in terms of units. A 1-by-1-inch square is 72 units by 72 units. A letter-size page (8.5 by
11 inches) is 612 by 792 points. When using the print API, you must set your mind to work
with units because all the classes work in the user space.
Java printing system
The Java printing system has evolved considerably in its last two releases. Starting with
version 1.2, the printing system allows you to use the Java 2D API -- one of the most
advanced graphical APIs built as part of a programming language -- to render a page. This 2D
API allows whatever is drawn on the screen to be rendered on paper.

Although more advanced now, the printing API still only supports the printer currently selected
by the user at any given time. Java does not support printer discovery -- obtaining a list of
available printers and their features on a given computer. Available printers can either be local
or networked. When using the API, no way exists for obtaining a printer list programmatically;
only if the print dialog is displayed can the user select a printer. This is a feature that Sun,
which is adhering to the Internet Printing Protocol, will address in the next version of Java
(1.4).

The printing model changed completely in Java 1.2. In previous versions of Java, the rendering
process was not optimized at all. In Java 1.1 for example, printing a simple page required a
great deal of memory and was very slow. Java 1.2 streamlined and optimized the rendering
process. This redesigned API is based on a callback model, in which the printing subsystem,
not your program, controls when a page is rendered. This model is more object-oriented in
nature than the one used in JDK 1.1, in which the application was in charge of the printing
process.

To simplify the concept, let's say that your program has a contract with the printing subsystem
to supply a given page at a given time. The printing subsystem may request that your
application render a page more than once, or render pages out of sequence. This model
provides several advantages. First, by sending strips of the page instead of the whole page to
the printer, it allows the application to print complex documents that would require more
printer memory than is available. The application does not have to know how to print each
strip; it only needs to know how to render a given page. The API will take care of the rest. In
this case, the printing subsystem might request that a page be rendered several times
depending on the number of strips required to completely print the page. Second, if the paper
tray on a particular printer outputs the pages in reverse order, then your application might be
asked to print the document in reverse order, so it will appear in the right order in the output
tray.

Rendering models
There are two printing models in Java: Printable jobs and Pageable jobs.

Printables
Printable jobs are the simpler of the two printing models. This model only uses one
PagePainter for the entire document. Pages are rendered in sequence, starting with page
zero. When the last page prints, your PagePainter must return the NO_SUCH_PAGE value. The
print subsystem will always request that the application render the pages in sequence. As an
example, if your application is asked to render pages five through seven, the print subsystem
will ask for all pages up to the seventh page, but will only print pages five, six, and seven. If
your application displays a print dialog box, the total number of pages to be printed will not be
displayed since it's impossible to know in advance the number of pages in the document using
this model.

Pageables
Pageable jobs offer more flexibility than Printable jobs, as each page in a Pageable job can
feature a different layout. Pageable jobs are most often used with Books, a collection of pages
that can have different formats. I will explain the Book class in a moment.
A Pageable job has the following characteristics:

 Each page can have its own painter. For example, you could have a painter
implemented to print the cover page, another painter to print the table of contents, and a third
to print the entire document.
 You can set a different page format for each page in the book. In a Pageable job, you
can mix portrait and landscape pages.
 The print subsystem might ask your application to print pages out of sequence, and
some pages may be skipped if necessary. Again, you don't have to worry about this as long as
you can supply any page in your document on demand.
 The Pageable job doesn't need to know how many pages are in the document.

Books
Also new since version 1.2 is the Book class. This class allows you to create multiple-page
documents. Each page can have its own format and its own painter, giving you the flexibility to
create sophisticated documents. Since the Book class implements the Pageable interface, you
could implement your own Book class when the provided Book class lacks the features that you
require.
Name Type Description
Paper This class defines the page's physical
Class
characteristics.
PageFormat defines the page's size and
PageFormat Class orientation. It also defines which Paper to
use when rendering a page.
This class manages the print job. Its
PrinterJob responsibilities include creating a print
Class
job, displaying a print dialog box when
necessary, and printing the document.
Book represents a document. A Book
object acts as a collection of pages. Pages
Book Class included in the Book can have identical or
differing formats and can use different
painters.
A Pageable implementation represents a
set of pages to be printed. The Pageable
Pageable object returns the total number of pages
Interface
in the set as well as the PageFormat and
Printable for a specified page. The Book
class implements this interface.
A page painter must implement the
Printable Interface Printable interface. There is only one
method in this interface, print().
A Book class represents
a The Graphics object implements this collection of
pages. interface. PrinterGraphics provides the When first
PrinterGra
created, phics Interface getPrinterJob() method to obtain the the Book object
is printer job that instantiated the print empty. To add
pages, process. you simply use
one of the two
append() methods (see my explanation of this class in the API section for more details). This
method's parameters are the PageFormat object, which defines the physical characteristics of
the page, and a PagePainter object, which implements the Printable interface. If you don't
know the number of pages in your document, simply pass the UNKNOWN_NUMBER_OF_PAGES
value to the append() method. The printer system will automatically find the number of pages
by calling all the page painters in the book until it receives a NO_SUCH_PAGE value.
API definition
Theory and practice will meet in this section. In the previous sections, we learned about page
structure, units of measurement, and rendering models. In this section, we will look at the
Java printing API.

All of the classes required to print are located in the java.awt.print package, which is
composed of three interfaces and four classes. The following tables define the classes and
interfaces of the print package.

Pageable interface
The Pageable interface includes three methods:

Method name Description


int getNumberOfPages() Returns the number of pages in the document.
Returns the page's PageFormat as specified by
PageFormat getPageFormat(int pageIndex)
pageIndex.
Returns the Printable instance responsible
Printable getPrintable(int pageIndex)
for rendering the page specified by pageIndex.
Printable interface
The Printable interface features one method and two values:

Name Type Description


Requests that the graphics handle
int print(Graphics graphics, PageFormat using the given page format render
pageFormat, int pageIndex) Method
the specified page.

This is a constant. Return this value


NO_SUCH_PAGE Value to indicate that there are no more
pages to print.
The print() method returns
PAGE_EXISTS. It indicates that the
PAGE_EXISTS Value page passed as a parameter to
print() has been rendered and
does exists.

Every page painter must implement the Printable interface. Since there is only one method
to implement, creating page painters may seem easy. However, remember that your code
must be able to render any page in or out of sequence.
There are three parameters to print(), including Graphics, which is the same class used to
draw on the screen. Since the Graphics class implements the PrinterGraphic interface, you
can obtain the PrinterJob that instantiated this print job. If your page layout is complex and
requires some advanced drawing features, you can cast the Graphics parameter to a
Graphics2D object. You will then have access to the full Java 2D API.
Before you start using the Graphics object, note that the coordinates are not translated to the
top left corner of the printable area. Refer to Figure 3 to find the location of the default origin.
Figure 3. Printable area origins

(0, 0) appears at the top left corner of the printer margins. To print a 1-by-1-inch rectangle, 1
inch from both top and left margins, you would use the following code:

1: public int print (Graphics graphics, PageFormat pageFormat, int pageIndex) {


2: Graphics2D graphics2D = (Graphics2D) graphics;
3: Rectangle2D.Double rectangle = new Rectangle2D.Double ();
4: rectangle.setRect (pageFormat.getImageableX () + 72,
5: pageFormat.getImageableY () + 72,
6: 72,
7: 72);
8: graphics2D.draw (rectangle);
9: return (PAGE_EXISTS);
}

From the previous example, we see that we must manually translate the origin of the rectangle
so that it prints at the top of the printable area as in Figure 1. To simplify the code, we could
translate the coordinates once and use (0, 0) as the origin of the printable area. By modifying
the previous example, we get:

1: public int print (Graphics graphics, PageFormat pageFormat, int pageIndex) {


2: Graphics2D graphics2D = (Graphics2D) graphics;
3: graphics2D.translate (pageFormat.getImageableX (), pageFormat.getImageableY
());
4: Rectangle2D.Double rectangle = new Rectangle2D.Double ();
5: rectangle.setRect (72, 72, 72, 72);
6: graphics2D.draw (rectangle);
7: return (PAGE_EXISTS);
8: }

Using the translate() method in line 3, we can translate the coordinates and set our origin
(0, 0) at the top of the printable area. From this point on, our code will be simplified.

PrinterGraphics interface
The PrinterGraphics interface consists of one method:
Method name Description
Returns the PrinterJob for this
PrinterJob getPrinterJob() rendering request and is implemented by
the Graphics class

Paper class
Eight methods make up the Paper class:
Method name Description
This method returns the page's physical height in points (1 inch
double getHeight() = 72 points). For example, if you are printing on a letter-size
page, the return value will be 792 points, or 11 inches.
This method returns the page's imageable height. The imageable
double
getImageableHeight() height is the height of the print area that you may draw on. See
Figure 1 for a graphical view of the imageable area.
This method returns a page's imageable width (the width of the
double
getImageableWidth() print area that you may draw on). See Figure 1 for a graphical
view of the imageable area.
This method returns the x origin of the imageable area. Since
double getImageableX() there is no support for margins, the return value represents the
left margin.

double getImageableY() This method returns the y origin of the imageable area. The
value returned from this method is equivalent to the top margin.
This method returns the page's physical width in points. If you
double getWidth() print on a letter-size paper, the width is 8.5 inches, or 612
points.
void
This method sets the imageable area and specifies the margins
setImageableArea(double
x, double y, double on the page. Actually, the API provides no method to set the
width, double height) margins explicitly; you have to calculate them yourself.

This method sets the physical page size. To define an 8.5-by-11-


void setSize(double
width, double height) inch sheet, you would supply 612 and 792 points. Note that the
default size is LETTER.

Before we move on to the next section, remember that the Paper class defines the page's
physical characteristics. The PageFormat class represents all the page's characteristics, such as
page orientation, size, and the paper type. This class is always passed as a parameter to the
Printable interface's print() method. Use Paper to obtain the imageable area location, size,
and page orientation along with a transformation matrix.
PageFormat class
The PageFormat consists of 12 methods:
Method name Description
This method returns the page's physical height in points (1 inch = 72
double getHeight() points). If your page measures 8.5 by 11 inches, then the return
value will be 792 points, or 11 inches.
This method returns the page's imageable height, which is the height
double getImageableHeight() of the print area on which you may draw. See Figure 1 for a
graphical view of the imageable area.
This method returns the page's imageable width -- the width of the
double getImageableWidth() print area on which you may draw. Figure 1 illustrates a graphical
view of the imageable area.
double getImageableX() This method returns the x origin of the imageable area.
double getImageableY() This method returns the imageable area's y origin.

double getWidth() This method returns the page's physical width in points. If you print
on letter-sized paper, the width is 8.5 inches, or 612 points.

double getHeight() This method returns the page's physical height in points. For
example, letter-sized paper is 11 inches in height, or 792 points.
This method returns a transformation matrix that translates user
double[] getMatrix() space into the requested page orientation. The return value is in the
format required by the AffineTransform constructor.
This method returns the orientation of the page as either PORTRAIT
int getOrientation()
or LANDSCAPE.
void setOrientation(int This method sets the orientation of the page, using the constants
orientation) PORTRAIT and LANDSCAPE.
This method returns the Paper object associated with the page
Paper getPaper() format. Refer to the previous section for a description of the Paper
class.
This method sets the Paper object that will be used by the
void setPaper(Paper paper) PageFormat class. PageFormat must have access to the physical
page characteristics to complete this task.

This concludes the description of the page classes. The next class that we will study is the
PrinterJob.

PrinterJob class
The PrinterJob class controls the printing process. It can both instantiate and control a print
job. Below you will find a definition of the class:

Method name Description


This method cancels the current print job. You can validate the
abstract void cancel()
cancellation with the isCancel() method.
abstract boolean isCancelled() This method returns true if the job is cancelled.
PageFormat defaultPage() This method returns the default page format for the PrinterJob.
abstract PageFormat This method clones the PageFormat passed in parameters and
defaultPage(PageFormat page) modifies the clone to create the default PageFormat.

abstract int getCopies() This method returns the number of copies that the print job will
print.
abstract void setCopies(int This method sets the number of copies that the job will print. Note
copies) that if you show a print dialog box, users can alter the number of
copies (see the pageDialog method).
abstract String getJobName() This method returns the job name.
static PrinterJob getPrinterJob() This method creates and returns a new PrinterJob.
abstract String getUserName() This method returns the user name associated with the print job.
This method displays a dialog that allows the user to modify the
PageFormat. The PageFormat, passed in parameters, sets the fields
of the dialog. If the user cancels the dialog, then the original
abstract PageFormat
PageFormat will be returned. But if the user accepts the parameters,
pageDialog(PageFormat page)
then a new PageFormat will be created and returned. Since it will
not show the same parameters on all operating systems, you must
be careful when using the pageDialog.
This method queries the document to obtain the total number of
abstract void setPageable(Pageable pages. The Pageable will also return the PageFormat and the
document) Printable object for each page. See the definition of the Pageable
interface for more information.
This method sets the Painter object that will render the pages to
abstract void
setPrintable(Printable painter) be printed. A Painter object is an object that implements the
Printable class and its print() method.
This method completes the same tasks as abstract void
abstract void setPrintable(Printable painter), except that you supply the
setPrintable(Printable painter, PageFormat that the Painter will use. As indicated in the definition
PageFormat format) of the Printable interface, the print() method passes a
PageFormat object as the first parameter.
This method prints the document. It actually calls the print()
abstract void print()
method of the Painter previously assigned to this print job.
abstract void setJobName(String
jobName) This method sets the name of the print job.

This method displays a print dialog box that allows the user to
abstract boolean printDialog() change the print parameters. Note that this interaction's result will
not be returned to your program. Instead, it will be passed to the
peer operating system.
This method will validate the PageFormat passed in parameters. If
abstract PageFormat
validatePage(PageFormat page) the printer cannot use the PageFormat that you supplied, then a
new one that conforms to the printer will be returned.

Book class
Seven methods make up the Book class:
Method name Description
void append(Printable painter, This method appends a page to the Book. The painter and
PageFormat page) the PageFormat for that page are passed in parameters.
This method completes the same tasks as void
void append(Printable painter,
PageFormat page, int numPages) append(Printable painter, PageFormat page), except
that you specify the number of pages.
This method returns the number of pages currently in the
int getNumberOfPages()
Book.
PageFormat getPageFormat(int This method returns the PageFormat object for a given
pageIndex) page.
Printable getPrintable(int pageIndex) This method returns the painter for a given page.
void setPage(int pageIndex, Printable This method sets the painter and the PageFormat for a
painter, PageFormat page) given page already in the book.
The printing recipe
The recipe for printing is very simple. First, create a PrinterJob object:

PrinterJob printJob = PrinterJob.getPrinterJob ();

Next, using the setPrintable() method of the PrinterJob, assign the Painter object to the
PrinterJob. Note that a Painter object is one that implements the Printable interface.

printJob.setPrintable (Painter);

Or you could set the PageFormat along with the Painter :

printJob.setPrintable (Painter, pageFormat);

Finally, the Painter object must implement the print() method:

public int print (Graphics g, PageFormat pageFormat, int page)

Here the first parameter is the graphics handle that you will use to render the page, the
pageFormat is the format that will be used for the current page, and the last parameter is the
page number that must be rendered.
That's all there is to it -- for simple printing, that is.

Introduction to the framework


The print framework that we will build in this series will be completely independent of the Java
printing API. It will allow for greater flexibility in producing different outputs. Its structure will
allow you to create documents, pages, and print objects. You will be able to add print objects
to a page while adding pages to a document. By using this structure, you will be able to easily
implement export features to PDF or HTML files, or print directly to the printer using the print
API. But the main goal of the framework is to simplify the creation of printed documents. When
you print using the print API, you only end up with a graphic canvas to draw on. It fails to
address the concepts of paragraphs, images, drawings, graphics, tables, or running headers
and footers. Because you must compute the (x, y) origin, the width and height of the printable
area, setting margins is a chore. Our print framework will address all of these weaknesses.

Conclusion
We covered a lot of ground in this first part. We looked at measurement units, the structure of
page, the two rendering models (Pageable and Printable), and Books, and we concluded
with a detailed explanation of the printing API. Next month, we'll focus primarily on code, as
we will be putting everything into practice. We will also look at the issues that arise when
printing on multiple platforms. Looking ahead to Part 3, I will explain in detail the design and
implementation of the framework.

You might also like