Cliff, vice president of technology of Digital Focus, can be contacted at [email protected] To submit questions, check out the Java Developer FAQ web site at http://www.digitalfocus.com/faq/.
Sidebar: The Browser Matters
Despite the electronic age and the coming paperless office, people still want to print documents. A long-standing problem with Java, however, is that printing support has not been available. JDK 1.1 makes a first stab at solving this problem.
Existing Printing Capabilities
The current JDK 1.1 printing model is a low-level one on which future JDK releases will build. It doesn't have built-in support for pagination, collating, previewing, image-rendering options for print or page size, orientation, or formatting. These requirements fall into the category of "document printing," as opposed to rendering on a single page a set of selected drawing operations. An application that needs document-printing features must either implement those features itself or use third-party components that do.
The Basic Printing Model
The JDK printing model is a rendering model that treats a print-rendering context the same way it treats a window-display context. In other words, you use the same method calls to generate print output that you use to generate display output. The only difference is that, to print, you draw text and images onto a "PrintGraphics" context whereas, to render to a display component surface, you draw onto the component's "Graphics" context. PrintGraphics extends Graphics, so you can use the same algorithm to do both, thereby making it possible to have a uniform approach for outputting both to the display and printer.
In fact, JDK 1.2 expands on this approach by adding PostScript-like functionality such as composites, paths, font rendering, and more. This model was developed as a collaboration between JavaSoft and Adobe. This makes it possible to carry PostScript and its derivatives into the Java domain.
Security: The Right to Print
As with access to any device or system resource, access to a printer is monitored by the Java security manager. Every time an applet or application tries to print, the method checkPrintJobAccess() is called in the installed security manager. If the applet is running in a browser, the right to print will be denied by default, unless special provisions are taken. JDK 1.2 will introduce an enhanced security model for Java, and the concept of "protection domains." It will be some time before this new API finds its way into browsers, however. At this time, unfortunately, the approach used for security depends on the browser you are using. For example, Microsoft's Internet Explorer requires that the applet be signed; if it is signed, print-job access (and, indeed, any other kind of access) is automatically granted by IE's security manager. This is moderately secure at best, since most applets will need to be signed to do useful things, so users must give every signed applet full reign of their machines. In a situation in which every useful applet must be signed, who will be able to figure out which applet it was if one turns out to be bad? (The system does not keep a log of applet activity.) Still, this model may work for intranet applications, when users have no Internet access.
Netscape Navigator is more watchful. As the programmer, you must not only sign the applet, thereby proving its authenticity, but you must also insert a call to the Netscape Capabilities API, explicitly requesting permission to print. Thus, Navigator provides fine-grained control over what an applet is allowed to do; for example, users may permit it to print, but not access the local drive (someone's system registry), or open sockets. Attempts by the applet to do other things must be preceded by requests to do specifically those kinds of operations, which will require the user to grant permission at least once for each type of operation. (Even this model needs refinement. For example, users should be able to restrict socket connections on a host-specific basis, and file access on a directory-specific basis. This will likely be possible in a future release of Navigator.) Netscape lets you save permission profiles for an applet, so you do not have to go through this procedure every time; but you can rest easy knowing that the applet will not do things it is not permitted to do (or has no business doing). This brings to mind the famous "Dilbert" cartoon, in which Dilbert installs a new software product, and at the end of installation the program prompts for permission to log onto the vendor's computer to remotely register the program. Dilbert, after a moment of hesitation, clicks Yes, and from that point on, events proceed out of Dilbert's control, finally ending with the program saying that it is deleting files from Dilbert's system to make room for new programs that have been automatically ordered using Dilbert's credit card.
The bottom line is, if you must grant a program permission for it to do its job, do you want that license to be all encompassing? Usually the answer is no, and qualified permission is more appropriate.
The ClipboardPrinter Demo
ClipboardPrinter, the demo printing class presented here, is invoked by a demo applet called ClipboardPrinterApplet. The ClipboardPrinter class provides the ability to retrieve the system clipboard as text, and print that text in a multipage format. It also lets users preview text page-by-page. ClipboardPrinter is designed as a JavaBean, so it can be dropped into a builder tool and used to provide a rudimentary clipboard printing service to any applet.
ClipboardPrinter has a few additional features. For one, it lets users select the font size used to print or preview the clipboard contents. It also provides Copy and Paste buttons, which merely generate events that can be listened for by the application that instantiates ClipboardPrinter. My demo applet has a text area, and I use the Copy event to copy text from that text area into the system clipboard. I use the Paste event to paste text from the clipboard into the text area. Finally, ClipboardPrinter has Print and Preview buttons, which initiate the multipage printing and preview features, respectively.
Accessing the System Clipboard
The first challenge in this program is accessing the system clipboard. JDK 1.1 provides the java.awt.datatransfer package for this purpose. The ClipboardPrinter methods that encapsulate access to the clipboard are getClipboardData() and setClipboardData(). Prior to calling these, however, my getClipboardData() method first requests permission to access the system clipboard by calling the Netscape static method in Example 1(a).
The PrivilegeManager class is part of package netscape.security, which implements Netscape's Capabilities API. The string parameter is a capability "target," a resource or capability that is protected by the security manager. Table 1 lists the targets Netscape has defined that pertain to Java. You can get the Capabilities API classes from http://developer.netscape .com/library/documentation/signedobj/capsapi.html; you will need to download these and put them in your classpath in order to compile the sample program presented here.
The privilege granted (if any) as a result of enablePrivilege() terminates with the static scope in which the call is made. In other words, if you call enablePrivilege() inside method m(), then when m() returns, any privileges the program obtained while in m() are gone. Privileges are therefore specific to the particular thread which obtained them.
The enablePrivilege() method call can fail for several reasons. One is that access was denied, either because the applet is not signed or because the browser prompted the user with a security dialog and the user decided not to grant permission to access the clipboard. Another reason for failure is that the Java run time could not locate the class PrivilegeManager, which belongs to package netscape.security. For example, if you are running the applet inside of HotJava, it will throw a NoClassDefFoundError. You can avoid this by bundling package netscape.security with your applet, but this is not necessary, since you catch the error, and it does not inhibit the program from proceeding. In a less-secure browser, you do not have to ask for privilege; if the error crops up, you catch it and proceed anyway.
Next, you attempt to actually retrieve the data in the system clipboard; see Example 1(b). You use the Transferable.getTransferData() method to get the data. At present, the JDK lets you retrieve only text content. Content types in the JDK are based on the Internet MIME standard. The two currently supported types are text/plain and application/x-java-serialized-object. In the latter case, it is assumed that the data embodies a serialized Java String object. The getTransferData() method takes care of unserializing the data for you. However, the type of object it returns depends on what the MIME type (the flavor) of the data is. For example, if the data is plain/text, getTransferData() returns a StringReader object. If, on the other hand, it is an application/x-java-serialized-object containing a String, getTransferData() returns a Java String. You therefore need to be able to handle both cases; and in fact, when the JDK adds support for additional data flavors, you may want to be able to handle those as well.
In the getClipboardData() method, I attempt to retrieve the data as plain/text; if this fails, it throws UnsupportedFlavorException. In this case, I try to retrieve it as a String. If that fails, I give up, as the data is not text in nature and I cannot handle it.
Rendering and Pagination
The next step is to display the data. Since the JDK handles rendering to a printer and to a screen the same way, I only have to construct one document renderer. My rendering method is called render(). To support multipage information, I have a method called paginate(), which must be called prior to rendering, after the clipboard contents have been retrieved. This method uses the dimensions of the currently selected font, as well as the print page dimensions, to allocate lines of text to each printed page, allowing for a fixed-size margin. When it is done, you know how many pages there are, and the index of the beginning of each line within the text stream. It is then a simple matter for the renderer to display the correct lines for any selected page. The render() method therefore takes a page-number parameter, and renders that page onto a specified Graphics context, which may be a PrintGraphics context as well.
In printing or displaying documents, the application decides how to display the document content. Some content formats (PostScript, for instance) have precise requirements about how to display a document in that format. In the case of a text document, there is room for some variation (how to handle tabs, and so on). For simplicity, I convert every tab to a space; you might want to perform actual tab formatting.
In the end, displaying the text comes down to a Graphics.drawString() operation. You simply draw each line of text at a calculated position on the Graphics context.
ClipboardPrinter provides a preview function, so that users can see what will be printed before actually sending it to the printer. Previewing amounts to invoking the renderer and giving it a screen Graphics context instead of a PrintGraphics context. I create a preview frame and instantiate a canvas in that frame, then render onto the canvas. At the top of the preview frame, there are Prev and Next buttons, for stepping backward and forward through the document pages.
The preview frame size is based on the dimensions of a printed page. If these dimensions exceed the size of your display, the frame will be set to the size of your display instead, and you will not be able to see some of the contents of each page. A more sophisticated implementation would use a scrollable canvas, so that this does not occur. Also, if you invoke the preview frame prior to printing, the size of the printed page is not known, because that is returned only when you try to print; therefore, I set the default size for the preview frame to be the default size of a print page.
To print, I first have to request permission by calling Example 2(a). Whether this succeeds or fails, I then try to print, using Example 2(b). The first line in Example 2(b) creates a Properties object, which should return filled if the user changes printing properties (page orientation, for instance). At present, this does not have any functionality.
The second line causes a print dialog to pop up on the user's screen, allowing for the usual selection of printer, page orientation, and so on. Thus, this method blocks until the user clicks OK in this dialog. The method returns a PrintJob, or returns null if the user cancels. Once you have a PrintJob object, you can call printJob.getPageDimension() to get the size of the page, which you need to perform pagination. (Changing the page orientation has no effect on the dimensions of the page returned by getPageDimension().) After I call paginate(), I enter a loop that repeatedly calls pg = printJob.getGraphics(), render(pg, page), and pg.dispose(). The call to dispose() is what actually initiates printing and consumes the PrintGraphics object. To print another page, you create another PrintGraphics; hence printJob.getGraphics() is at the start of the loop.
The ClipboardPrinterApplet applet (see Figure 1) instantiates a ClipboardPrinter at the top, and can be tested by using AppletViewer or by running it from the command line, since it has a main method. (The code for ClipboardPrinterApplet is available electronically; see "Availability," page 3, or, go to the Digital Focus web site at http://www .digitalfocus.com /ddj/code/. For more information on how specific browsers handle ClipboardPrinterApplet, see the accompanying text box entitled "The Browser Matters.")
A better solution is to use a third-party server-based printing service. Novera (http://www.novera.com/) offers a tool called Epic, which lets you create and configure host-based services like printing, LDAP directory service, and file management. A more specialized tool is JPrint from Neva Object (http:// www.nevaobject.com/), which provides server-based print services. That these services are server based usually does not impact the convenience of printing if the server is within a private network, since you can select any printer in the network. If these approaches are not acceptable, possibly because they involve the purchase of a third-party tool or because you want to provide printing for Internet clients, you can write your own.
The upcoming Java2D API, which is part of JDK 1.2, advances Java by enabling applications to perform true page layout and graphics, using a unified screen and print model. Eventually the JDK clipboard will support a full repertoire of data types, as well as printing options. And just because your application needs to print does not mean it needs to access your hard drive. A properly designed Java security manager allows you to precisely control what an applet is allowed to do -- a critical concern when running unknown software. You need to retain final authority over your computer, and Java makes this possible.
Copyright © 1997, Dr. Dobb's Journal