|
Articles Index
2 Platform Introduces Print Capability to the Swing Forum
By Michael Shoffner; Reprinted from
Java World
June 1999
The Java 2 printing APIs give applications developers the ability to add print
capabilities to Java applications and signed applets. The Java 2 printing
system consists of a small number of interfaces and classes that encapsulate
print-related entities such as printer jobs, page formats, printable pages,
and collections of printable pages.
This month, we'll add Java 2 print capabilities to our old pal, the Swing
Forum. But first we'll investigate the basics of the Java 2 printing system,
including its operating principles and high-level features.
Java 2 printing basics
The Java 2 printing system offers applications the ability to print AWT text
and graphics to any standard printer, whether networked or local. The printing
system is a "callback" system, which means the system, not the printing
application, drives the print process. In practice, this means the system
determines the order in which pages are printed and makes callbacks into
programmer-defined objects that know how to print pages. This architecture
is similar to the 1.1 delegation event model and is highly analogous to the
way component painting works.
The printing system consists of two entity types: job-control entities, which
control printer jobs, and document-related entities, which represent documents
to be printed as well as their constituent pages. The printing API is contained
in the package java.awt.print.
Note: The Java 2 printing system should not be confused with
the 1.1 printing system, although it is similar in some respects.
Job-control entities Classes
PrinterJob, PageFormat, and Paper
are the job-control entities responsible for printer-job management. The
PrinterJob class encapsulates a printer job.
PrinterJob operations include retrieving the default page
format, popping up print- and page-format dialogs, and printing the job.
The PageFormat class represents a set of page-format settings.
This class provides offset- and imageable-area information for use in rendering
pages.
The Paper class represents the physical properties of a piece of
paper in the printer. This class is used by the system and is not necessary
for applications development.
In addition to the job-control entities described above, Java 2 printing makes
use of document-related entities that provide both high level and low level
document printing services, which we'll look at next.
Document-related entities
Document-related entities can be classified as either high level or
low level. Pageable and Book are considered
high level. In contrast, Printable "page painters" are low level.
High-level document-related entities represent whole documents, such as a
resume, a book, or any other collection of printable pages.
A document is a collection of pages in which each page or group of
pages may have different formats. For example, a business letter might consist
of a "first page" that displays letterhead and standard header information and
the beginning of the letter's text. This page may be followed by a series of
additional pages containing the rest of the letter's text.
The Pageable interface provides applications developers with
high-level document-related services. The Java 2 API supplies the
Book class, which is an implementation of Pageable,
so that developers don't have to write an implementation from scratch.
Book allows the developer to create an ordered list of
document sections. Each document section consists of an instance of
PageFormat associated with a page painter (see below)
and an int that specifies the number of pages in the section.
Books are used by appending groups of pages to them. Here's
the correct syntax:
book.append (Printable painter,
PageFormat pageFormat,
int numpages);
High-level document-related entities make use of low-level entities to do
the actual work of printing pages.
A low-level page painter is an object that knows how to render a
single kind of page. It implements the Printable interface,
which has one method:
public int print (Graphics graphics,
PageFormat pageFormat,
int pageIndex) throws PrinterException
When the system calls this method on the page painter object, the page painter's
implementation will render the page specified by pageIndex, using
the page format object pageFormat. Supplied graphics context
rendering and component painting are done in exactly the same way.
The application doing the printing may use multiple page painters, each of which
knows how to render a different kind of page. For example, most documents have a
title page, table of contents, and pages of content. Each of these page types
could have an associated page painter, which would be collected in a
Pageable implementation, such as an instance of Book.
A single page painter can render multiple pages of the same type. In this case,
the painter is responsible for rendering each page as it is requested by the
system. The system may request pages in order or out of order, and it may request
the same page more than once.
Printable jobs versus pageable jobs
A given printer job, represented by an instance of PrinterJob,
can be either printable or pageable.
Printable jobs are simple jobs that consist of a single page painter.
As noted above, a single page painter can paint multiple pages, in which case
the system keeps calling the painter's print () method until the
method returns Printable.NO_SUCH_PAGE.
Pageable jobs represent documents. Pageable jobs can consist of
multiple page formats, each associated with a Printable page
painter. Pageable jobs also have the advantage of allowing the system to find
out in advance the number of pages in the document, so that the user can choose
which pages to print by means of the print dialog.
The application specifies the type of printer job it wants by using one of the
following two calls on the PrinterJob object (job is
used as an example):
-
job.setPrintable (Printable painter);
-
job.setPageable (Pageable document);
Now that we understand the Java 2 printing system, it's almost time to apply
our knowledgebut first, a quick peek at the Swing Forum.
Swing Forum basics
If you'll recall, the Swing Forum is a simple client/server discussion-forum
system in which the client is implemented using Swing. The Swing Forum uses
a JTree to allow users to access discussion threads and messages
within those threads.
Swing Forum articles are encapsulated in the ForumArticle class
and are stored as user objects aggregated by nodes in the tree. To get the
instance of ForumArticle associated with a node, the following
incantation is used (when the article is selected from the tree by the user):
ForumArticle art = (ForumArticle) node.getUserObject();
The client display area used to display messages for reading is a
TextArea class. After the user selects an article from the tree
and the application obtains the correct instance of ForumArticle,
the application sets the display area's text to the article text stored in the
ForumArticle:
readArea.setText (art.getText ());
We're now ready to add message printing functionality to the Swing Forum.
Add printing to the Swing Forum
With the addition of message printing, Swing users can print either a selected
article or all the articles in a message thread, simply by selecting the
appropriate node in the tree and then selecting File-Print Current from the
menu bar.
To add print functionality, we'll define a new menu item and event
handler, a print-management helper method, a page painter for forum
articles, and a Thread subclass to do the printing in a
nonblocking fashion.
The five steps of this process are as follows:
Step 1. Add a new action
printCurrentAction = new AbstractAction (
"Print Current Article") {
public void actionPerformed (ActionEvent e) {
printCurrentArticle ();
}
};
|
Step 2. Add the action to the menu bar
void layOutMenuBar () {
menubar = new JMenuBar ();
JMenu m = new JMenu ("File");
m.add (closeAction);
m.add (printCurrentAction);
...
|
Step 3. Create the printCurrent () method
The SwingForum class uses the printCurrent ()
method to kick off the printing process when the user actuates the process
from the menu:
synchronized void printCurrent () {
// print based on the currently selected node
TreePath path = tree.getSelectionPath ();
if (path == null) {
// nothing selected
return;
}
DefaultMutableTreeNode node;
node = (DefaultMutableTreeNode)
path.getLastPathComponent ();
int depth = path.getPathCount ();
ArrayList list = new ArrayList ();
if (depth == 2) {
// node is a thread
Enumeration children = node.children ();
while (children.hasMoreElements ()) {
DefaultMutableTreeNode child;
child = (DefaultMutableTreeNode)
children.nextElement ();
list.add (child.getUserObject ());
}
} else if (depth == 3) {
// node is an article
list.add (node.getUserObject ());
}
if (list.size () > 0) {
// do the actual printing in another thread
new PrintingThread (list).start ();
}
}
|
The printCurrent () method first determines if a valid
selection (thread or article) has been made. If so, it adds the selected
ForumArticle instance(s) to an ArrayList. If the
list turns out to have items in it, printCurrent () creates and
starts a new PrintingThread object, and then passes in the list.
Note that since the items backing the list are immutable (an article object is
never modified after it has been created), there is no need to clone the list
to put it in another thread. Even in the unlikely event that the backing article
object disappears from the tree (say, because the server removes the article and
the client refreshes from the server while the print is in progress), the article
object will still exist in the list until the printing thread goes out of scope.
Step 4. Create the PrintingThread class
The PrintingThread class is responsible for the application's
print logic:
public class PrintingThread extends Thread {
ArrayList list;
public PrintingThread (ArrayList l) {
list = l;
}
public void run () {
PrinterJob job = PrinterJob.getPrinterJob ();
Book book = new Book ();
// cover page could be appended to book here
ForumArticlePagePainter fapp =
new ForumArticlePagePainter (list);
PageFormat pf = job.pageDialog (job.defaultPage ());
int count = fapp.calculatePageCount (pf);
book.append (fapp, pf, count);
job.setPageable (book);
if (job.printDialog ()) {
try {
job.print ();
}
catch (PrinterException ex) {
ex.printStackTrace ();
}
}
}
}
|
At runtime, the PrintingThread object first obtains a new printer
job by using the static method call PrinterJob.getPrinterJob(). It
then creates a new Book.
PrintingThread next instantiates the ForumArticlePagePainter
fapp, and a page dialog pops up to allow the user to set margins and so
forth.
It then sends a query to fapp to cause it to calculate the number
of pages the job will include, based on the user's previous PageFormat
selection.
Based on the page count, PrintingThread appends a new
sectioncontaining the painter, the current PageFormat, and
the current page countto Book.
Finally, PrintingThread hands the print job to Book
to print and starts the print job with a call to job.print ().
Step 5. Create the ForumArticlePagePainter class
ForumArticlePagePainter is the most interesting class we'll add
to the Swing Forum.
This class is responsible for rendering ForumArticle pages when
the system requests them. Since it must know how to render pages, it should
provide an operation to allow a Pageable such as Book
to precalculate the total number of pages in a job.
The difficulty in designing this class stems from the fact that the
PageFormat passed into print() is not clearly
guaranteed to be the same as the one used to precalculate the page size for
the Book instance. The user conceivably could have changed it
with the print dialog Properties option. To guard against this possibility,
PageFormat instances passed into the precalculate method are kept
separate from those passed into the print() method.
If it weren't for this consideration, we could have designed the class to take
a PageFormat object as well as the ArrayList of
ForumArticle in its constructor. The PageFormat
object would have become part of the object's state and the precalculation
method wouldn't need to be parameterized.
Note: The repagination functionality must be separate from
rendering functionality because we want the ability to calculate the page
count independently from the actual printing. We therefore create a division
in functionality in which the "repaginator" puts lines of text in the
ArrayList pages and the "renderer" just draws.
In the sections below, we'll look first at the code listing, followed by the
explanation.
import java.awt.*;
import java.awt.print.*;
import java.util.*;
public class ForumArticlePagePainter implements Printable {
ArrayList articles, pages;
PageFormat curPageFormat;
Font font = new Font ("TimesRoman", Font.PLAIN, 12);
public ForumArticlePagePainter (ArrayList l) {
articles = l;
}
public int calculatePageCount (PageFormat pf) {
// calculate pagecount based on pf and articles
ArrayList pgs = repaginate (pf);
return pgs.size ();
}
|
The above portion of the class represents the usual setup. It also contains
the interface method calculatePageCount ().
public int print (Graphics g, PageFormat pf, int idx)
throws PrinterException {
// Printable's method implementation
if (curPageFormat != pf) {
curPageFormat = pf;
pages = repaginate (pf);
}
if (idx >= pages.size ()) {
return Printable.NO_SUCH_PAGE;
}
g.setFont (font);
g.setColor (Color.black);
renderPage (g, pf, idx);
return Printable.PAGE_EXISTS;
}
|
In the above listing, the print () method prints the page
to the graphics context that is passed into it. It stores the latest
PageFormat in an instance variable. Subsequent repaginations
occur only if a different PageFormat object comes into the
method. This shouldn't happen, so this optimization allows us to avoid
redundant repaginations.
Of course, if the current PageFormat instance were modified, we
wouldn't be able to detect it. However, this would be nonsensical because it
would mean that the page formatting had changed in midprint! Therefore, we can
safely ignore this possibility.
The ForumArticlePagePainter's renderPage() method
does the actual drawing on the graphics context.
ArrayList repaginate (PageFormat pf) {
// step through articles, creating pages of lines
int maxh = (int) pf.getImageableHeight ();
int lineh = font.getSize ();
ArrayList pgs = new ArrayList ();
Iterator it = articles.iterator ();
while (it.hasNext ()) {
ForumArticle art = (ForumArticle) it.next ();
ArrayList page = new ArrayList ();
int pageh = 0;
// headers
page.add ("Author: " + art.toString ());
page.add (" ");
pageh += (lineh * 2);
// body
StringTokenizer st = new StringTokenizer (
art.getText (), "\n");
while (st.hasMoreTokens ()) {
String line = st.nextToken ();
if (pageh + lineh > maxh) {
// need new page
pgs.add (page);
page = new ArrayList ();
pageh = 0;
}
page.add (line);
pageh += lineh;
}
pgs.add (page);
}
return pgs;
}
|
In the previous section, the repaginate () method calculates the
size of lines of text and puts as many as it can fit into a page, which is
implemented as an ArrayList of String. As new pages are
created and filled up, the method adds them to the pages ArrayList.
void renderPage (Graphics g, PageFormat pf, int idx) {
// render the lines from the pages list
int xo = (int) pf.getImageableX ();
int yo = (int) pf.getImageableY ();
int y = font.getSize ();
ArrayList page = (ArrayList) pages.get (idx);
Iterator it = page.iterator ();
while (it.hasNext ()) {
String line = (String) it.next ();
g.drawString (line, xo, y + yo);
y += font.getSize ();
}
}
}
|
Here, the renderer simply loops through pages, drawing the lines
it reads there.
And that's all there is to the ForumArticlePagePainter class. The
figure below illustrates the Swing Forum printing subsystem we've developed.
The object model for the Swing Forum printing
subsystem
Java 2 printing issues
Java 2 printing is pretty clean from the development perspective, but the
developer community has reported some issues. The most important of these
pertain to overly large spool-file size and its subsequent sluggishness.
Also, printing support is limited to Win32 and Solarisother platforms
may not work properly. There are other bugs as well that may be worth
searching though if you need to do a production implementation. (See
Resources for more details.)
Use it
Using the print-enabled Swing Forum is basically the same as using the
nonprinting version. First you start the server, then you start the client:
Step 1. Start the server
-
Start a command prompt or
xterm
-
Change to the server subdirectory
-
java ForumSocketServer
Step 2. Start the client
-
Start a command prompt or
xterm
-
Change to the client subdirectory
-
java SwingForumLauncher http://localhost:5000/
Step 3. Print something!
There's a message or two supplied with the local server; you can add your own
as well.
And most of all, enjoy!
Conclusion
In this installment, we've seen the basics of how to add Java 2 printing to
a Swing application. We've covered the highlights of the Java 2 API, such as
document-oriented interfaces and classes, and job-oriented classes such as
PrinterJob.
Extensions to what we've developed here are of course possible; in particular,
an excellent extension would be to modify the repaginate() method
in ForumArticlePagePainter to wrap lines that extend beyond a set
length.
Resources
Download
the source code for this article in zip format.
The bundled JDK
1.2 documentation (listed under 2D API) provides the reference
documentation for the Java 2 printing subsystem.
The Java Developer Connection's
online
printing tutorial has deeper and broader information on printing than
this article attempts to provide. Look here, in particular, if you want to
focus on printing nested components for the purpose of printing GUIs.
The JDC
Bug Parade. A search under "1.2 printing" yields information on
bugs in the printing system. Use this as a resource when you're doing
exploratory prototypes to test the printing technology on your target
platforms.
Michael's previous Swing Forum articles
"Making
the Forum Swing, Part 2," Michael Shoffner (November 1998)
"Making
the Forum Swing, Part 1," Michael Shoffner
(September 1998)
Credits
Clip art used in the Swing Forum: http://www.iconographics.com/

Acknowledgements
From JavaWorld's "How-to-Java" series.
Reprinted with permission from the June 1999 edition of JavaWorld magazine,
http://www.javaworld.com.
Copyright Web Publishing Inc., and IDG Communications company. Register for
editorial email alerts at:
http://www.javaworld.com/javaworld/common/jw-subscribe.html.
About the Author
Michael Shoffner is a Java-enabled guy. He has written several books;
his latest, Java
Network Programming, Second Edition has just hit the stands!
Java and all Java-based marks are trademarks or registered trademarks
of Sun Microsystems, Inc. in the U.S. and other countries.
|