PRINT HINTS

Sending PostScript Files to a LaserWriter

Dave Polaschek

A question that popped up pretty often for me when I worked in Apple's Developer
Technical Support group, and that continues to appear in the Mac programming
newsgroups on the Internet, is "How do I send PostScriptTMfiles to a LaserWriter?"
The simplest answer appears to be to use the PostScriptHandle picture comment.
Wrong! In the Print Hints column "The Top 10 Printing Crimes Revisited" in develop
Issue 26, I said that this would be a misuse of the PostScriptHandle picture comment,
but I didn't give a full explanation of just how to send files to a printer. This column
will explain how to send complete PostScript files the correct way.

First, though, I'd like to backtrack a little and explain more thoroughly why you
shouldn't use the picture comment to send complete PostScript files. After all, if you
do implement code that uses this technique, the files get to the printer, pages come out,
and things seem mostly to work. What's the problem?

WHY NOT USE POSTSCRIPTHANDLE?

The problem with using the PostScriptHandle picture comment to send files to a
printer is that you're using the Printing Manager to do some of the work for you and
then bypassing it unexpectedly. The problems that will show up when you do this may
not be obvious, but when a user does notice them it can lead to confusion.

First, when you use the PostScriptHandle picture comment, the LaserWriter driver
has already set up the PostScript state for QuickDraw imaging. It has changed the
coordinate system and has sent down the md dictionary that it will need to draw pages.
At best, this is extra baggage that your PostScript files don't need in the way. At worst,
since the driver has changed the coordinate system, your PostScript file may not print
correctly. There's also a chance that the extra memory used by the LaserWriter's
PostScript dictionary may cause your job not to print at all.

Second, using PostScriptHandle is wasteful. If background printing is enabled, the
complete PostScript file, plus the extra things you don't need, will be spooled to the
user's hard drive. Since what you're interested in is just getting the file to the
printer, this is wasteful. In some cases, the job won't even be able to print, since your
user won't have enough free space on the hard drive to hold a second copy of the
PostScript file.

Finally, there's an aesthetic problem. The Printing Manager counts the pages that are
going to be sent by looking at how many times PrOpenPage is called during your print
job. If you've got a multiple-page PostScript file, the page count displayed by the
Printing Manager will be out of whack. While messages like "Printing Page 4 of 1"
won't actually hurt the user, spreading confusion is bad.

THE RIGHT WAY(S)

There are actually two right ways to send PostScript files to the printer. They're
essentially the same in that you open a connection directly to the printer and send it
the file you want to print, but the implementations are very different. You can either
use classic networking and the PAPWorkStation.o library, or you can use Open
Transport, which contains support for the PAP protocol. (For more on PAP, see "PAP?
What's That?")

          ______________________________

       PAP? WHAT'S THAT?

          The Printer Access Protocol (PAP) is the protocol spoken by all LaserWriters
          (and third-party Macintosh-compatible PostScript printers). It's designed
          around a few simple ideas:

          This last point means that there are actually multiple connections open for the
          one connection to the printer. This is a major source of the complexity of the
          code in the classic networking case. Open Transport hides this complexity
          from you.

          You can find out more about PAP in Chapter 10 of Inside AppleTalk.

          ______________________________

The classic networking solution has the advantage that it's available in all Macintosh
computers, out of the box. But it's more difficult to implement. Since you're working
at a lower level, there's more room for you to get things wrong -- but there's also less
worry of having library code that doesn't do things the way you want. Note that your
code (at least part of it) can't be PowerPC native, because the libraries are 680x0
only.

Open Transport is a much simpler way to implement sending PostScript files to your
printer. You talk to the networking library at a higher level, and you get a
PowerPC-native implementation of networking. But of course you lose compatibility
with older Macintosh models.

We'll look at both these solutions; sample code for each one accompanies this column on
this issue's CD and develop's Web site. The choice of which technique to use is up to
you. Note also that there may be other, more attractive alternatives in the future. For
now, however, these are the two best options.

The sample code for the classic networking solution is the SendPS tool -- an MPW tool
rather than a full application, but still useful for demonstrating how things work. The
process is also described in the MacTutor article "Laser Print DA for PostScript." If
you're interested in the nitty-gritty details, consulting the code and that article is
your best bet. Here I'm just going to give an overview of the code. In a few cases, I'll
make suggestions for how you might enhance the code to make a real-world application.

The main thing you need to understand about the PAP library that Apple supplies for
classic networking is that it reports the connection status to you via a few variables
rather than by way of completion functions. It's not truly asynchronous code, so if you
want to write a program that will run happily in the background, sending PostScript
files to a printer, you've got some extra work to do. It's possible, but a little tricky.

When we're in the process of opening a connection (in the code near the call to
PAPOpen in sendps.c), we look at the wstate variable to tell us what's happening. All
these state variables have three basic states: a positive value means we're waiting for
the printer; negative values are errors; and 0 means the operation we're watching has
completed. We also call PAPStatus periodically to get the printer's status so that we
can display it to the user.

Once the connection is opened, we issue a PAPRead call. This is necessary because when
the printer wants to talk to us, it can't just tell us it's ready to talk, but rather we
must have asked it for some data first. (Remember, PAP is a protocol where the
parties have to ask for data.) We check the rstate variable periodically to see if the
printer has said anything to us. When it does, we need to read the data it has sent us and
issue another PAPRead call.

Now we're ready to start sending blocks of data. We issue a PAPWrite call, and for each
call, we watch the wstate variable to see when the write is done. While we're waiting
for the writes to complete, we need to remember to check the printer's status and
display it to the user (our write might be "stuck" because the printer is out of paper
and can't print a page, for example; the user should see this so that she can replenish
the paper).

Once we've sent all our data, we send the end-of-file to the printer. We do this by
sending a packet with no data, but with the eof flag set. If we've got another job to send,
there's no need to close the connection and reopen it; we can just start sending it as
soon as the printer acknowledges our end-of-file.

That's the quick overview of the SendPS tool. If you need more guidance, see the source
code; I've tried to comment it so that everything will make sense.

          You're not allowed to redistribute the PAPWorkStation.o library included
          with the classic networking sample code without first licensing it from Apple.
          To license the code, contact sw.license@apple.com for more details. It's really
          not that expensive, and you don't want to write it all yourself. Trust me.*

THE OPEN TRANSPORT CODE

The main code for the Open Transport solution is considerably easier to follow than the
classic networking code, primarily because you don't have to worry about multiple
state variables in order to send. You simply create PAP and PAPStatus endpoints
(connections), and the main loop calls the Snd and Rcv functions for the PAP endpoint.
There's a callback function that gets notified when the state of the endpoint changes and
then sets a single state variable to match the state of the endpoint. Exception handling
can be localized to this callback, which makes the code easier to read, and the status
reporting can be handled in one place as well.

Because we don't need to continually poll the state variables, it's much easier to fit
this code into an application, and the application will cooperate better with other
applications. Getting good performance is also quite a bit easier with the Open
Transport-based code than with the classic networking code, since you don't have to
worry about waiting in the wrong place, polling a state variable.

WRAPPING UP

Implementing code to send PostScript files directly to a printer is more difficult than
using the PostScriptHandle picture comment, but it offers a number of benefits to
your users. The process is more straightforward, no extra print dialogs need to be
shown, and no extra storage is required for spool files. You can provide as much or as
little status information as you like (for example, you could add progress bars
showing how much of the job is completed). And performance should improve in most
cases, since you'll be talking directly to the printer, rather than through the layer of
the Printing Manager. Convinced? I should hope so.

RECOMMENDED READING

DAVE POLASCHEK  (davep@best.com, http://www.best.com/~davep/) now works
for LaserMaster Corp., where he does "Mac things" that confuse the people who
actually build printers, since Dave seems to spend most of his time designing spiffy
icons and then dragging them around his screen. In his spare time, Dave scrapes ice off
his windshield, jump-starts cars, and de-ices locks. Earlier this year he cut a hole in
a lake and spent hours sitting on the ice waiting for fish to find his hook (while he
consumed a judicious amount of antifreeze). Who said life in Minnesota isn't
exciting?*

Thanks to Rich Kubota, Quinn "The Eskimo!", Steve Simon, and Tony Wingo for
reviewing this column.*