Printing in Java, Part 4

Code the print framework

1 2 Page 2
Page 2 of 2
Figure 2. Header/footer locations

The next step is the print loop. The loop will ask every PFPrintObject stored in the pageObjectCollection to render themselves.

Let's review what you have learned so far: The PFPage defines a page and acts as a container of PFPrintObject objects. When the print() method invokes, it renders each object in the page. The PFPage class does not store the page parameters directly; it uses a PFPageFormat class for storage, which I will discuss next.

Set page parameters with the PFPageFormat class

Use the PFPageFormat class to set and store the page format data. PFPageFormat computes the page area origin with the getPageAreaOrigin() method, and the page area size using the getPageAreaSize() method. Both methods are located between lines 212 and 239 in Listing 5.

Listing 5: PFPageFormat

This class also contains the getPageFormat() method (lines 52-95 in Listing 5). Use getPageFormat() to obtain a java.awt.PageFormat object. Before returning a PageFormat object, getPageFormat() must first evaluate the page orientation and set the page's margins and size accordingly. If the page is to render in a landscape orientation, switch the top and left margins along with the width and height of the page.

Recall our discussion of the PFDocument's print() method. At line 277 in Listing 5, the getPageFormat() is used as an interface between the print framework and the Java Print API. Since you used a different class to store the page format, you should translate it to a java.awt.print.PageFormat object in order to set the page format in the Java Print API.

I will now discuss the most important class of the print framework: the PFPrintObject. You will need to implement this class for every print object you create.

The PFPrintObject class

The PFPrintObject is the base class for every print object that will appear on a page. It's currently the print framework's most complex class. PFPage will only accept objects that extend the PFPrintObject class. Its design is based on the composite pattern explained in Part 3. The complete listing of the PFPrintObject is in Listing 6.

Listing 6: PFPrint Object

Implementation of the composite pattern

A composite object provides features that allow PFPrintObject to contain other PFPrintObjects. As a result, each child object can store other PFPrintObjects, creating a tree structure. I store child objects in a Vector class. (See line 34 in Listing 6.)

Two methods let you add and remove child objects. add (PFPrintObject) (line 347 in Listing 6) adds a child object to the printObjectCollection, and sets the parent object to this object using the setParent() method. It helps to know an object's parent when rendering trees of the PFPrintObject. The other method, remove(), allows you to remove a child object from the collection. It will only accept the source object as the reference.

Rendering

PFPrintObject's rendering process is a bit more complex than PFDocument's or PFPage's, because each object must be rendered in relation to its parent object. The parent object can be either the PFPage object onto which the print object was added or another PFPrintObject. (See Part 3 for more information.)

Since the PFPrintObject is abstract, I will explain how a subclass object can render itself.

  1. Call the computeSizeAndPosition() method (lines 115-212 in Listing 6), which will compute the object's size and position in relation to its parent object. When complete, the drawOrigin and drawSize objects will set to the subclass object's origin and size.

    The first step in calculating size and position is to obtain the parent object's size and origin. To check whether the parent is a PFPage object or another PFPrintObject, call the isChild() method. After the parent object has been found, two objects are set: parentLocation and parentSize. You will find those objects useful when you calculate fills and sticky positions.

    The second step is to validate the positioning mode, in which the mode can be either absolute or relative. If the positioning mode is set to relative, then all the positioning will execute relative to the parent origin. Otherwise, all positioning will execute relative to the page.

    Third, set the margins. (See lines 151-155 in Listing 6.)

    The resolution of fills (lines 158-168 in Listing 6) is the fourth step. Fills allow the child object to fill its parent horizontally and vertically.

    The fifth and final step in calculating size and position is calculating the vertical and horizontal sticky positions.

  2. Render your object using drawOrigin and drawSize, which the computeSizeAndPosition() method calculates.
  3. Call the printChild() method, which renders any child object that your parent object might contain. Child objects render on top of the parent object.

This code example demonstrates how to implement the PFFrame's print() method:

113|   /**
114|    * Method: print <p>
115|    *
116|    * @param parG a value of type Graphics2D
117|    */
118|   public void print (Graphics2D parG) {
119|
120|
121|      Color saveForeground = parG.getColor ();
122|
123|
124|      //--- Print the child objects
125|      computePositionAndSize ();
126|
127|
128|      rectangle = new Rectangle2D.Double (getDrawingOrigin ().getX ().getPoints (),
129|                                          getDrawingOrigin ().getY ().getPoints (),
130|                                          getDrawingSize ().getWidth ().getPoints (),
131|                                          getDrawingSize ().getHeight ().getPoints ());
132|
133|
134|      //--- Draw the rectangle
135|      if (fillColor != null) {
136|         parG.setColor (fillColor);
137|         parG.fill (rectangle);
138|      }
139|
140|      //--- Set the tickness
141|      Stroke lineStroke = new BasicStroke ((float) tickness);
142|      parG.setStroke (lineStroke);
143|      parG.setColor (lineColor);
144|      parG.draw (rectangle);
145|
146|
147|      parG.setColor (saveForeground);
148|
149|      printChilds (parG);
150|   }

At line 125 in the code above, the computeSizeAndPosition() method invokes. The rectangle is rendered between lines 128 and 147. You should pay special attention to the parameters used for the rectangle object. The drawOrigin and drawSize values are used instead of the object's own position. You must always use the drawOrigin() and drawSize() methods to obtain the rendering position of a print object. That will assure that the object renders at the right position. Finally, the printChilds() method at line 149 renders any child objects that the parent object may contain.

Sticky positioning

The print framework offers two methods for setting an object's sticky position. setVerticalSticky() sets the sticky parameter for the vertical axis. setHorizontalSticky() sets the sticky value for the horizontal axis. The following table lists the sticky attributes at your disposal:

AttributeAxisDescription
STICKY_NONEBothDisable the sticky feature
STICKY_LEFTHorizontalStick to the left side of the parent object
STICKY_CENTERBothStick to the center of the parent object
STICKY_RIGHTHorizontalStick to the right side of the parent object
STICKY_TOPVerticalStick to the top of the parent object
STICKY_BOTTOMVerticalStick to the bottom of the parent object
Table 2. Sticky constants

The two sticky methods can be found between lines 215 and 259 in Listing 6.

Margins

You can set margins for a PFPage object and PFPrintObject. By combining object composition and margins, you can easily achieve some special effects.

Examine Figure 3 to see how to create a double-border rectangle using the print framework.

Figure 3. Rectangle structure

If you use the functionality that the print framework provides, you can achieve the result shown in Figure 3 with minimal effort:

  1. Define two rectangles -- the inner and the outer -- and set the line stickiness accordingly.
  2. Set the margins of the inner rectangle to 0.125 inches.
  3. Set both fill attributes, and set the vertical and horizontal sticky to STICKY_CENTER.
  4. Add the inner rectangle to the outer rectangle. Even if you change the rectangle's size, it will always keep its appearance.

The recipe for using the print framework

I have presented the entire structure of the print framework. You learned about the PFDocument, PFPage, and PFPrintObject objects. In addition, you discovered how the measurement system was designed and how to extend it to create your own system. Now that you have every ingredient, take a look at the recipe for using the print framework.

The process can be divided into four easy steps:

  1. Create a PFDocument object
  2. Create a PFPage object and add it to the PFDocument class
  3. Create all the print objects that will appear in your page and add them to the PFPage object
  4. Invoke the document's print() method to print it

Repeat steps 2 and 3 until all the pages in your document have been created. The following code excerpt shows how to implement the recipe:

PFDocument document = new PFDocument ();
PFPage page = new PFPage ();
PFFrame rectangle = new PFFrame ();
document.addPage (page);
rectangle.setHeight (new PFInchUnit (5.0));
rectangle.setHorizontalSticky (PFPrintObject.STICKY_LEFT);
rectangle.setHorizontalFill (true);
rectangle.setFillColor (Color.black);
rectangle.setLineColor (Color.red);
page.add (rectangle);
document.print ();

Next month

I will conclude this series next month with a discussion on PFFrame, PFLine, PFCircle, PFParagraph, PFImage, PFPrintPreview, and PFPageSetup. I will also release the source code for the print framework. Until then, happy printing!

Jean-Pierre Dubé is an independent Java consultant. He founded Infocom in 1988. Since then, Infocom has developed custom applications in fields including manufacturing, document management, and large-scale electrical power line management. Jean-Pierre has extensive programming experience in C, Visual Basic, and Java; the latter is now the primary language for all new projects. He dedicates this series to his mother, who passed away while he was writing it.

Learn more about this topic

1 2 Page 2
Page 2 of 2