You are on page 1of 9

Printing in Java, Part 3 - Jean-Pierre Dubé introduces

the print framework that works on top of the Java Print API
By Jean-pierre Dubé, JavaWorld.com, 01/05/01

In this third part of a five-part series on printing in Java, I will explain the design of the print
framework. This framework will work on top of the API to ease the burden of creating printed
output. With it, you will be able to create pages with running headers/footers, and insert
paragraphs, images, and tables. The coding phase will begin in Part 4 and continue into Part 5.
We have a lot of ground to cover, so let's start.

Goals of the print framework

In Parts 1 and 2, you had the opportunity to examine in detail the Java Print API. With this
knowledge, you are now able to evaluate its strengths and weaknesses and build a framework
on top of it. That framework will compensate for the lack of high-level features in the Java
Print API.

The print framework will:

1. Be easy to use.
2. Provide the high-level functionality required to efficiently render pages.
3. Loosely couple with the Print API. Because the framework has no direct ties to the Java
Print API, it will allow greater support for other output formats, such as PDF or HTML.
4. Provide a structure that clearly defines each component involved in creating documents
(document, page, paragraph, and so on).
5. Use an abstract measurement system. Developers will thus be able work with the
measurement system of their choice or even create their own. The framework will
provide three default units of measurements: inches, centimeters, and points.
6. Provide a print-preview facility. The Java Print API has no support for previewing the
output before you print.
7. Support a standard and portable page-setup dialog. As mentioned in Part 2, even
though Java has been categorized as a WORA (Write Once, Run Anywhere) language,
its functionality varies from platform to platform. The print framework will provide one
page-setup dialog that will be uniform across all platforms.
8. Offer export features. Although the framework will support several output formats
(PDF, HTML, Postscript), their implementation is beyond the scope of this series. I leave
the implementation up to you.
9. Offer text handling, which is of primary importance. The framework will provide all the
functionality needed to effectively render text, including right justification, left
justification, full justification, and support for the AttributedString class.
10. Support graphics primitives such as rectangles, circles, and lines.
11. Support GIF and JPEG image types.
12. Support headers and footers. These can be set at the document or the page level, and
the first page can feature a different header/footer.
13. Support sticky position, which easily sticks a component to a specific location. As an
example, let's assume that you want to center an object in a page. Instead of doing all
the math yourself, you can set the vertical and horizontal sticky value to a CENTER
value; the framework centers the object within its parent container -- in this case, the
page. Since all print objects are containers, the sticky values would be applied within
the boundaries of the parent object.
14. Support sticky dimensions, with which you can set a component's width and/or height
to the parent container's width and/or height.
15. Support tables, though their implementation will be limited.
In addition, all print objects are containers in the print framework.

A detailed look at the framework

To better understand the design of the framework, select UML Diagram 1 below:

UML Diagram 1

The diagram shows a hierarchical structure. Starting from the top of the hierarchy, you will
find a PFDocument object that contains PFPage objects that contain any objects that are
instances of PFPrintObject, where each PFPrintObject can contain other PFPrintObjects. If you
got lost in the above explanation, take a look at Figure 1, which presents a better view of the
print framework's structure. A closer look at the print objects reveals that the design is based
on the composite pattern. I present more on the composite pattern later.

Figure 1. Document structure


Click on thumbnail to view
full-size image (44 KB)

The following sections will explain in detail the print framework's design; I begin with the
universal measurement system.

Universal measurement system

An important feature of the print framework is that it supports conventional measurement


units. Most of us prefer working with inches or centimeters, rather than with points or pixels. I
created an abstract class called PFUnit to define the basic functionality required to convert
from a given measurement system to points.

I also created three classes that implement the PFUnit class: PFInchUnit, PFCmUnit, and
PFPointUnit. With these classes, you can work with your preferred unit of measurement. You
can also create your own measurement system by subclassing PFUnit. When using these
classes, you will use two methods: setUnit(), to set a value in the measurement system, and
getPoints(), to get the value converted into points.

Using classes to represent numbers, instead of the standard double, float, or integer types,
can cause problems when performing calculations. Another drawback to using classes is the
lack of operator overloading in the Java language. To alleviate this problem, I added all the
basic math operations to the PFUnit class. In this way, we preserve the encapsulation of data
while providing basic math operations. As an example, let's add 2 inches to a PFInchUnit of 3
inches. You could complete that task in the non-object-oriented way:

PFInchUnit inch = new PFInchUnit (3.0);


inch.setUnit (new PFInchUnit (2.0).getUnit () + inch.getUnit ());

Or, by using the built-in math methods, you could do it like so:

PFInchUnit inch = new PFInchUnit (3.0);


inch = inch.add (2.0);

I created three other classes to complement the measurement system:

1. PFPoint, which represents an X,Y point


2. PFSize, which represents the size of an object
3. PFRectangle, which represents a rectangle area

All of the above classes use the PFUnit class.

The PFDocument class

The PFDocument class is located at the top of the hierarchy in the print framework library. The
class acts as a page container and provides the necessary functionality to handle the print and
export processes. This class also offers some methods related to document-handling.

With the PFDocument class, you can set headers and footers for a whole document using the
setHeader()/setFooter() methods. You can set a different header/footer for the first page by
using the setFirstPageHeader() and the setFirstPageFooter() methods.

PFDocument also includes a print-preview window. By calling the printPreview() method, a


print-preview window will display. I will discuss the preview window in further detail later.

One of the goals of the print framework is to provide functionality for exporting documents to
formats other than paper. Since the print framework model is totally independent of the
output, it's possible to export to any format. To export your document to such formats as
HTML or PDF, use either the printPDF() or the printHTML() methods. Note that these methods
appear in the design but have not been implemented yet. I leave their implementation to you.

PFDocument offers several other methods to set document properties. For example, with
setDocumentName() you can set a document's name, which will identify your document in the
host operating system's print queue. The print-preview window also uses the document name
to identify your document in the window's title bar. Finally the showPrintDialog() and
showPageDialog() methods will show the appropriate dialog. Note that the page dialog called
by showPageDialog() does not originate in the Java Print API. I decided to implement my own
page dialog after I tried printing on different platforms and discovered that page dialogs vary
from platform to platform.

Now that you know about the functionality implemented by the PFDocument class, let's dive
into the print method's details. Take a look at UML Diagram 2 below.

UML Diagram 2
As shown in this diagram, the messages flow between each component when the PFDocument
print() method is called. The PFDocument will instantiate the print job. It will first create a
Book object and will enter in a loop to add all of the document pages to the Book. After
PFDocument completes the loop, PrinterJob print() is called, and the Java Print API handles the
rest of the printing process.

The following sections will elaborate on each object's role in the print framework. I begin these
explanations with the PFPage class.

PFPage class

Next in the hierarchy, the PFPage class defines a page and acts as a container for objects that
will be rendered on it. Each page in the print framework can have its own format. There is no
limit to the number of objects that you can add to a page.

Each page can either have its own header/footer or use the one defined in the PFDocument
class. How does the print framework decide which header/footer to use? First, the page checks
if its own header/footer is null. If so, it will try to obtain the header/footer from the
PFDocument object. If the header/footer has a non-null value, the page will use the document
header/footer. Note that the document has two methods, getHeader() and getFooter(), that
handle the logistics of supplying the appropriate header/footer. The header/footer value is
based on the value of the first page header/footer and whether or not the page under
consideration is the first one.

The PFPage class also contains two important methods, getPrintableAreaOrigin() and
getPrintableAreaSize(). getPrintableAreaOrigin() provides the location of the printable area's
top left corner, which is determined by the margins, the gutter, and the height of the page
header. getPrintableAreaSize(), on the other hand, returns the printable area's size. Figure 2
illustrates the printable area origin and the printable area size.

Figure 2. Page layout


Click on thumbnail to view
full-size image (13 KB)

To print, PFPage must implement the Printable interface, which Part 1 explains.

You are now ready to see how the framework handles the print objects.

Understand the PFPrintObject


The design of the print object architecture is based on the composite pattern. I decided to use
that pattern after considering the following: Suppose you wish to draw a circle in the middle of
a rectangle with a drawing program. You decide to group these two objects together to form
one object so that you can move, resize, copy, and delete the objects as a whole. But how
does a good object-oriented program handle such a feature?

You could create container object and another object that is a graphics primitive. Your program
would have to differentiate between these two distinct objects and treat them accordingly. I've
tried this approach before, and the results were buggy and hard to maintain.

Instead, I suggest using a composite pattern. It ensures uniform access to all graphics objects.
Because each graphics object also acts as a container, no difference exists between a container
object and a graphics primitive object. The same objects handle all functionality. The
composite structure will produce a hierarchy of objects.

Figure 3 illustrates how the composite pattern works.

Figure 3. Composite structure


Click on thumbnail to view
full-size image (15 KB)

Figure 3 shows that you can create a complex object composition with only one type of object.
You can also group objects together with the composite pattern, and even group together
several groups of objects. By using the pattern, you simplify the framework's design
considerably.

The composite pattern is also easy to expand; you can create your own print objects by
extending the PFPrintObject. Since all the basic functionality is located in one place, expanding
the PFPrintObject gives your extended objects all the container functionality as well as sticky
positioning, sticky fill, and the rendering of all child objects.

To see how this pattern would help solve a real-world problem, consider the following example.
Let's say you want to build a page header as shown in Figure 4.

Figure 4. Page header structure

To build the header, you could draw everything by hand and work out the math necessary to
position each component. Or you could take advantage of the PFPrintObject's container
feature.
By examining the structure of the page header, you see that the master object contains
everything. Two paragraph objects are added to the master object. The first paragraph object
will print the string "Printing in Java". That object will have the following properties set:
verticalSticky = CENTER, horizontalSticky = LEFT. The second paragraph object will contain
the string "Page nn", with the properties verticalSticky = CENTER and the horizontalSticky =
RIGHT. Finally, we add a line object with the following properties: verticalSticky = BOTTOM,
horizontalSticky = CENTER, and horizontalFill = TRUE. Don't forget to set a margin around the
main component to ensure that everything centers within the header area. Refer to Figure 5 to
see where everything will fit.

Figure 5. Page header structure


Click on thumbnail to view
full-size image (9 KB)

The use of the sticky functionality saves us many headaches. The code that we created in that
example is 100 percent reusable and will automatically adapt to any situation. If the user
decides to change the orientation of the page, it will adjust itself automatically; no additional
code is required.

So, what is sticky functionality, anyway? The following section should answer your questions.

Sticky positioning

Many of you may be wondering why I've been using the term sticky. I devised this term while
working as a graphics editor. I had to provide functionality with which a user could glue a
component to another component using a relative position such as top, bottom, center, right,
or left. The component had to stick to the assigned position even if the user moved or resized
the position. I came up with the term sticky component, and it has stuck with me ever since.

A sticky component facilitates the positioning of print components on a page or inside another
component. With these sticky methods, you can place a component within a parent component
at the following positions: on the vertical axis, top, center, bottom and on the horizontal axis
left, center, and right. Figure 6 shows all the possibilities along with their properties.
Figure 6. Sticky positioning
Click on thumbnail to view
full-size image (6 KB)

Two methods allow you to set the sticky position values: setVerticalSticky() and
setHorizontalSticky().

Sticky sizing

Another useful bit of functionality is sticky sizing. With sticky sizing you can set a component's
width and/or height to the size of its parent component. The parent component can either be
the page to which the component was added or another component.

Two methods set sticky sizing: setHorizontalFill (boolean) and setVerticalFill (boolean).

Margins

If pages can have margins, then PFPrintObject can have margins too. Having the ability to set
a margin around a component can be useful and will enhance your printed output. You can set
margins independently using the current measurement system.

How do you implement all of the margins' properties? The computePositionAndSize() method
handles everything. You probably noted upon examining UML Diagram 1 that this method is
private; only the PFPrintPrintObject class has access to it. computePositionAndSize() computes
the sticky positioning, the sticky filling, and the margins. When finished computing, the
method returns the top left corner of the area where you can draw, and the width and height
of the drawing area for the object. Everything that falls outside this area will be clipped.

Let's take a look inside computePositionAndSize().

First, the parent object returns the actual drawing origin and size. The parent object can be
either the page to which the object -- PFPage -- was added, or another object -- PFPrintObject.
Next, set the margins and then calculate the sticky sizing. Finally, determine the sticky
positioning properties. By completing the calculations in this order, you solve all possible
conflicts within each property without relying on any if code. To prevent any ill-behaved object
from drawing outside of the drawing area, Graphics2D is clipped accordingly. The
drawingOrigin and drawingSize objects store the result of that computation. To access these
values, use the following two methods: getDrawingOrigin() and getDrawingSize().

To produce different effects, each component can be rotated. The method rotate (double) in
PFPrintObject adds this functionality. All angles are in degrees.

Paragraph

Efficient text management is of primary importance in this framework, and PFParagraph


manages this task. The object will accept two types of input: a standard String object and an
AttributedString object. AttributedString, explained in Part 2, is currently the only way to add
reformatted text. Some of you may ask, "What about HTML, or RTF, or XML?" I'm leaving their
implementation to you. You could easily add support for the XML format, whereas the other
two formats would require extra work. Even so, the framework features enough functionality
to satisfy your immediate needs.

The PFParagraph supports left, center, right, and full justification. You can set a default text
color, but it will be overridden if AttributedString sets the text color.

Four private methods handle the text justification. I took the implementation code from the
text examples in Part 2.

Drawing primitives

The framework would not be complete without the basic graphics primitives: rectangles,
circles, and lines. All of these extend the PFPrintObject object. Each object uses a
corresponding Graphics2D draw method to render its graphics. Several methods are available
to set different properties such as line width, line color, and fill color.

Images

The PFImage object supports JPEG and GIF files. You could easily add other formats. I kept
PFImage as simple as possible, while providing enough flexibility for advanced image
processing. Instead of trying to provide all the functionality of the Java 2D API inside a single
object, I opted to implement the functionality directly related to the print framework, and left
the image processing to you. I included a method for obtaining a copy of the BufferedImage
object. Once you have access to a BufferedImage, complete all the image processing that you
want. Load an image using the setURL (String) method. The string parameter must be a valid
URL.

Tables

Our framework would also not be complete if it didn't sustain tables. When creating complex
layouts, tables often come to the rescue. A PFPrintObject will represent each cell in the table,
so right from the start you have all that class's functionality. Included in the framework are
methods that apply properties to a complete row, column, or table.

However, the framework's table implementation features a few limitations. First, the table
class does not support column spanning. A cell cannot occupy the space of more than one cell
at a time. Second, the print framework cannot import tabular data. You will need to build each
row one at time by completing some additional coding.

Printing visual components

Sometimes it may be useful to render visual components on paper. The PFVisualObject has
only one method, setComponent(). Pass the visual component that you want to print to that
method. The print method will size and position the visual component to the size and position
of the PFVisualComponent, and then call the visual component paint method with a Graphics2D
object.

You must remember one important factor when printing visual components. As explained in
Part 1, the location at which you create your visual object affects the resolution of the Graphics
object that is assigned to it. When a Graphics object is created, an AffineTransform is
automatically assigned to it. AffineTransform maps the user space to the device space. For
example, when you create your components inside a Frame, it sets the AffineTransform to map
the device space -- in this case, a screen that has a lower resolution. Thus, your component
will print at the same resolution as the screen. Always make sure that you create your
components outside a graphic context if you want the maximum resolution.
Print preview

The print preview implemented here will only show a single page at a time. In order to
illustrate a page on screen, you must resize it to fit the view window. Preserving the aspect
ratio of the page is important. For example, the aspect ratio for a page that measures 8.5 by
11 inches is 0.77 (8.5 divided by 11). The size of the view window for such a page must
therefore have a width that is 0.77 of the height. Once you size the view window accordingly,
you can resize the page to fit within this view window.

Since the print framework renders a Graphics2D object, we can use that same object to draw
on the screen. You must scale the drawing to fit the page in the preview window. To achieve
that task, apply an AffineTransform to Graphics2D.

Using the preview window, the user can navigate to the first page, last page, next page, and
previous page, and can zoom in and out. In addition, the window includes a button to print the
document.

The preview window is structured as two objects, the first one being PFPrintPreviewToolbar.
That object creates the toolbar and will send the user actions to PFPrintPreview, the second
object; it renders a given page on screen. To see how these two objects collaborate, select
UML Diagram 3 below.

UML Diagram 3

Print job dialog

To make sure that users of all platforms have access to the same page setup functionality, I
devised a standard dialog for setting such printing parameters as the margins and paper
orientation. Because adding the number of copies and page range selection would double the
parameters found in print dialog, I omitted that functionality. UML Diagram 1 provides a good
overview of the PFPageSetup class.

Conclusion

This concludes our discussion on the design of the print framework. Now that you understand
the goals of the framework, we will move to the coding phase. In Part 4, you will start coding
all the basic objects, such as the PFDocument, PFPage, and the PFPrintObject. Until then,
happy printing.

You might also like