Printing in Java, Part 3

Jean-Pierre Dubé introduces the print framework that works on top of the Java Print API

1 2 Page 2
Page 2 of 2

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.

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 this article.

Learn more about this topic

1 2 Page 2
Page 2 of 2