The Perils Of PostScript--The Sequel
The Perils Of PostScript--The Sequel
SCOTT "ZZ" ZIMMERMAN
Developers are discovering the advantages of using PostScript ® dictionaries in
applications, but along with the advantages come some perils. One peril awaits if you
download a dictionary using PostScriptHandle. Another can trip you up after
downloading a dictionary if you then download a font using the SetFont procedure I
described in develop, Issue 1. How to avoid these perils? Read on to learn some tricks
for dicts in picts.
More and more developers are beginning to use direct PostScript code in their applications. In my
"Perils of PostScript" article in develop, Issue 1, I addressed a couple of problems that arise when
you use PostScript code to print documents. In this sequel, we'll look at some problems you will
encounter if you attempt to use PostScript dictionaries in your applications.
ABOUT POSTSCRIPT DICTIONARIES
A PostScript dictionary is a collection of predefined variables and/or procedures. Using a PostScript
dictionary can significantly reduce the size of the PostScript code generated by your application and
make it more efficient. For instance, consider a large PostScript file in which the operator
currentpoint
is used frequently.
You can define in your dictionary a PostScript procedure called
cp
that makes a call to the
currentpoint
operator.
You can then replace
currentpoint
with
cp
throughout the file, thus reducing its size. Similarly, by defining a PostScript procedure to
represent a series of operators, you can express a compound operation much more efficiently. And
storing procedures in a dictionary that you create can also prevent you from inadvertently redefining
something that has already been defined.
One great example of a PostScript dictionary is the one used by the LaserWriter ® driver, variously
called LaserPrep (after the file it resides in, at least until System 7), AppleDict (the Apple name for
it), and good ol' md (the PostScript name for it, and the one I prefer to use). The LaserWriter driver
generally uses one or more md
routines to perform a particular QuickDraw operation. (See the
sidebar on the next page for a review of how the LaserWriter driver
works.) For example, a call to the QuickDraw CopyBits
routine is
translated by the driver into a call to the db
or cdb
operators
stored in md. As another example, during font downloading the
LaserWriter driver uses bn
and bu
,
both stored in md, to call save
and restore
( bn
calls save and bu
calls restore).If you want to record a piece of PostScript code that references
procedures contained in a dictionary, you must also record the
dictionary. I describe how to download a dictionary, and how to
avoid the pitfalls involved, in the next section.
Once your dictionary has been downloaded, you should be able to
continue to reference it until the end of the job. But alas, this is not
so, at least until the new printing architecture ships sometime after
System 7. Under the current architecture, font downloading
interferes with PostScript dictionaries. I discuss this problem and how
to get around it under "The Perils of Font Downloading."
THE PERILS OF DICTIONARY DOWNLOADING
One of the easiest methods for downloading a PostScript dictionary is
by using the
PostScriptHandle
picture comment. You can use
this comment to download directly to the LaserWriter a block of
PostScript code stored in a handle. (See Technical Note #91,
Optimizing for the LaserWriter--PicComments, for more
information.) When you use the
PostScriptHandle
comment,
you must insert the
PostScriptBegin
and
PostScriptEnd
picture comments around the block of PostScript code you are trying
to download, like this:
PicComment(PostScriptBegin, 0, NIL);
(************************************************************)
(*** Your PostScript representation of document goes here.***)
(************************************************************)
PicComment(PostScriptHandle, size, handle);
(***********************************************************)
(*** Your QuickDraw representation of document goes here.***)
(***********************************************************)
PicComment(PostScriptEnd, 0, NIL);
As described in my first article, the PostScriptBegin/End
comments are markers that ensure that the right piece of code will
execute on the right device. When the LaserWriter driver sees PostScriptBegin
, it ignores all QuickDraw drawing calls and just
executes picture comments. When a PostScriptEnd
is received,
the LaserWriter driver will once again interpret QuickDraw calls. So
when printing to a LaserWriter printer, only the picture comments are
executed, while the QuickDraw code between PostScriptBegin
and PostScriptEnd
is ignored. But PostScriptBegin
and PostScriptEnd
also save and
restore at least part of the state of the device, which can cause
problems for your dictionary. To avoid this, you should use the
picture comment PostScriptBeginNoSave
(comment kind =
196) to prevent the save and restore from occurring, like this:
(***************************************************)
(*** Your definition of the dictionary goes here.***)
(***************************************************)
PicComment(PostScriptBeginNoSave, 0, NIL);
PicComment(PostScriptHandle, dictsize, dicthandle);
PicComment(PostScriptEnd, 0, NIL);
(**********************************)
(*** Now you send the document. ***)
(**********************************)
PicComment(PostScriptBegin, 0 NIL);
(************************************************************)
(*** Your PostScript representation of document goes here.***)
(************************************************************)
PicComment(PostScriptHandle, size, handle);
(***********************************************************)
(*** Your QuickDraw representation of document goes here.***)
(***********************************************************)
PicComment(PostScriptEnd, 0, NIL);
If you don't need to export your dictionary into picture files, you can
get the LaserWriter driver to auto-download your dictionary by
keeping the dictionary code in a PREC(103) resource. After the
LaserWriter has saved its state, it does a blind GetResource
(that
is, from any open resource file) on PREC(103). If one is found, it is
downloaded to the printer after the md dictionary, and before the
job. You can use this method of downloading for dictionaries that are
used only to contain state information about the current job. (When a
graphic is copied onto the clipboard, only the state information, not
the entire dictionary, is required for the code to execute.)
However, use of the PREC(103) resource does have some limitations.
It only works at print time, and there can be only one. That is, the
LaserWriter driver does not attempt to download all the PREC(103)
resources in all the open resource files. The first one it finds wins.
(This method of downloading dictionaries is documented in
Technical Note #192, Surprises in LaserWriter 5.2 and Newer.)
THE PERILS OF FONT DOWNLOADING
In my "Perils of PostScript" article in Issue 1 of develop,
I showed a small procedure called
SetFont
that downloaded a font using QuickDraw, while maintaining the ability to reference that
font using PostScript. The problem with that method is that the process of font downloading
executes the PostScript
restore
operator. This operator restores the state of the printer to a state
that was saved before your dictionary was defined. Because of this, any reference to your dictionary
is lost.
Another way to understand what happens in this case is to look at what the LaserWriter driver does
during printing. At the start of a print job, the LaserWriter driver configures the LaserWriter's
graphics state to look more like QuickDraw. This includes moving the origin (0,0) from the bottom
left (PostScript style) to the top left (QuickDraw style), and setting the default resolution to 72 dpi.
After the driver has configured the printer, it performs a save, which saves the complete state of the
device. The driver then begins downloading the rest of the job, containing the PostScript code
generated by the LaserWriter as well as any additional PostScript code sent by the application.
The LaserWriter driver fully restores the state of the device, by executing the PostScript restore
operator, before downloading a font. During font downloading, the characters of the font are
actually defined, sometimes using normal PostScript drawing operators. Because of this, the
LaserWriter driver restores the state of the printer before defining the characters. Once the
characters have been defined, the state is saved again. This way, the LaserWriter driver can assume
it knows the state of the device. Since the state saved by the LaserWriter driver does not contain any
of the symbols defined by the application, all of them are lost after any attempt to download a font.
WHICH WAY OUT?
Now that we understand the problem, let's discuss potential solutions. The restore
operator
affects everything that has changed except two areas: some of the PostScript stacks (specifically the
operand, dict, and execution stacks), and the contents of PostScript strings. This suggests that to
save small units of information, you can simply push them onto the stack, or convert them and store
them as PostScript strings.
Unfortunately, it's not quite that easy.
PostScript makes a distinction between simple and composite objects. Simple objects (like numerical
values and booleans) contain their value within the object. Composite objects (like strings,
procedures, and dictionaries) contain only a pointer to the real data, which is stored elsewhere in
PostScript Virtual Memory. Simple objects on the stack are indeed preserved across a restore
,
but if there are composite objects on the stack that are new (that is, newer than the state being
restored), an invalidrestore
error is generated. If your
dictionary only contains simple objects, then you can indeed push
each of the variables defined in the dictionary onto the stack
separately and rebuild the dictionary after the restore
. The
overhead here is obviously enormous, though, and most useful
dictionaries contain procedures and/or strings, rendering this
technique useless.
In the case of strings being preserved across a restore
, let me
quote from the PostScript Language Reference Manual , p. 44: "In the
current PostScript design, restore
actually does not undo
changes made to the elements of strings. We consider this behavior
to be a defect, and do not recommend that PostScript programs take
advantage of it." Beyond this easily ignored admonishment, though,
is another problem. The strings in question must be preexisting:
strings you create just before the restore
will, of course, be
destroyed by the restore
, or, if they are on the stack, will cause an
error. You could probably find some scratch strings in one of the
standard dictionaries to use, but this is not recommended, for obvious
reasons.
AN END TO BN AND BU
Another way to solve the problem would be to
redefine save
and restore
to not do anything. This way, font downloading would
not cause the state to be restored. This would make the application
developer responsible for preserving the state, which is easily done
using other PostScript operators. But unfortunately, the definitions of
save
and restore
cannot be changed without exiting the server
loop. That is, you cannot override their definitions from within a job.
Because of this, you have to fall back on plan B: override the
operators that call save
and restore
. In the case of font
downloading, these operators are bn
and bu
, as mentioned earlier.
This method is the most widely used solution to our problem, has the
fewest limitations, and is the method recommended here. Please note,
however, that tinkering with md operators outside of this specific use
is strongly discouraged. (See the sidebar on the next page.)
The main job of bu
and bn
is to preserve the state of the
PostScript device. As long as your PostScript code preserves the state,
these calls aren't even required. In the fragment that follows, we first
create our own dictionary, called mydict, with room for ten symbols,
although we don't define them all. Next we define killbu
.
killbu
is responsible for first saving the old definition of the
bu
routine, and then setting its value to the empty procedure ( {}
), which
does nothing. The original definition of bu
is simply pushed onto
the stack. Next we write a routine restorebu
, to restore the
definition of bu
when we are through. This routine is responsible
for popping the original value off the stack and storing it back into
the bu
symbol; it assumes that the definition of bu
is on the top
of the stack. Then we define two similar routines, killbn
and restorebn
, which take care of the bn
operator. Finally, we define a fun little routine to call to make sure
our dictionary is actually being preserved after font downloading. We
call this one titleshow
. So now we have a dictionary, all ready to
use.
SendPostScript('/mydict 10 dict def');
SendPostScript('mydict begin');
SendPostScript('/killbu {//md /bu get //md /bu {} put} def');
SendPostScript('/restorebu {//md exch /bu exch put} def');
SendPostScript('/killbn {//md /bn get //md /bn {} put} def');
SendPostScript('/restorebn {//md exch /bn exch put} def');
SendPostScript('/titleshow {dup gsave');
SendPostScript('currentscreen 3 -1 roll pop 120 3 1 roll setscreen');
SendPostScript('.5 setgray show grestore true charpath gsave');
SendPostScript('1 setlinewidth 0 setgray stroke grestore');
SendPostScript('.5 setlinewidth 1 setgray stroke }def');
SendPostScript('end');
Okay, now that we have the routines for killing bu
and bn
, we
need to call them. It's very important at this point to check for their
existence before attempting to alter their definitions. This is because,
as mentioned earlier, the new printing architecture that will ship
sometime after System 7 will handle font downloading differently.
The bu
and bn
operators will no longer exist; in fact, it's not clear
that the md dictionary will still exist. The following PostScript
commands check for the existence of both the dictionary and the
symbol. If they don't exist, our code assumes it is running under the
new printing architecture, and does nothing to insulate the
dictionary. The code fragment executes fine on LaserWriter drivers
up to and including System 7.0. It has also been tested in both
foreground and background. Considering the future of bn
and bu
,
it is very likely that this code will continue to work even under the
new printing architecture. Here, then, is the code to check for and
kill bn
and bu
:
SendPostScript('mydict begin');
SendPostScript('//md /bu known {killbu} if');
SendPostScript('//md /bn known {killbn} if');
SendPostScript('end');
Pretty straightforward: if the routine exists, call the correct routine to
kill it. The most important thing to note here is the order of the
routines. Since killbu
and killbn
push things onto the stack, restorebu
and restorebn
must be called in opposite order to
get the correct results. So after the job is finished, we call:
SendPostScript('mydict begin');
SendPostScript('//md /bn known {restorebn} if');
SendPostScript('//md /bu known {restorebu} if');
SendPostScript('end');
TO SUM IT ALL UP
PostScript dictionaries are useful because they can significantly
reduce the size of the PostScript code generated by your application,
and can be exported into pictures. Perhaps the easiest way to record
PostScript into a picture is by using the
PostScriptHandle
picture comment. In this case, remember to use the
PostScript
BeginNoSave
comment to prevent
PostScriptBegin
and
PostScriptEnd
from saving and restoring at least part of the state
of the device, which can cause problems for your dictionary. To
prevent font downloading from interfering with your PostScript
dictionaries, you can override
bn
and
bu
, the PostScript operators
that call
save
and
restore
. Outside of this solution, you should
absolutely avoid using md operators.
The code included in the Perils of PS II folder on the Developer
Essentials disc is basically the same code that has been shown here,
rolled into an application shell that opens and initializes the Printing
Manager. Also included is the definition of the SendPostScript
procedure referenced in this article.
A REVIEW OF HOW THE LASERWRITER DRIVER WORKS
The LaserWriter driver is a complex piece of software that handles communications between an application
and the LaserWriter printer. To print a document, the application opens the Printing Manager, which in turn
loads and initializes the LaserWriter driver. The application then makes standard QuickDraw calls similar to
those used to render the document on the screen. The LaserWriter driver intercepts these calls and converts
them into the equivalent PostScript code for rendering the document on the LaserWriter printer.
(See Figure 1.) In some cases, one QuickDraw operation translates into one PostScript operation, but more frequently, the
QuickDraw operation translates into several PostScript operations. To abbreviate these operations, the
LaserWriter driver stores them as procedures in a PostScript dictionary.
When the LaserWriter driver first connects to the LaserWriter printer, it checks to see if its dictionary exists
and if the version of the dictionary matches the version of the driver being used. If not, it downloads the
correct dictionary before proceeding. (This is what the message "initializing printer" means when you print
for the first time after turning on the printer.)
Once the correct dictionary is in place, the job of translation becomes much easier. Each QuickDraw
operation now becomes one line of PostScript code, referencing a procedure defined by the dictionary.
Complex QuickDraw operations (like font downloading) still require many lines of PostScript code, but in
general, the translation is one for one. Since the QuickDraw code is translated rather than rendered, the
LaserWriter driver doesn't need to spool the data to disk. Instead, each operation is translated and sent to
the printer as it is received.
Figure 1 How the LaserWriter Driver Works
WARNING: CALLING MD ROUTINES MAY BE HAZARDOUS TO YOUR CODE
Many developers have started to call md routines from within the PostScript code generated by their applications.
This is dangerous, for a number of reasons.
The first is that the md dictionary is defined and maintained by the LaserWriter driver. This means that it is always
subject to change, and code that depends on the md dictionary must be version dependent. This is possible, but far
from elegant.
Another problem with using md operators is that they may not work the same way on all devices.
Remember that the LaserWriter driver is used to drive a lot more devices than just an Apple LaserWriter.
Use of md operators has already led to compatibility problems with major applications, and most developers
have realized the danger in using them. The easiest way to avoid problems with these routines is to not call
them. If you really need the functionality of a particular md operator, simply redefine it in your own dictionary.
Using tools like LaserTalk (formerly from Emerald City Software, now from Adobe), you can "disassemble"
md operators back to their PostScript primitives. You can then redefine them using a different name in your
own dictionary. Now you have a routine that does exactly what the md routine did, but you remain in
control of its definition. Most of the md operators are very small, so the storage penalty of redefining them in
your own dictionary is minimal.
Now that I've warned you, I'm going to show you how to tinker with two operators stored in md: bn and bu.
All routines, including these two, are subject to change; by special arrangement with engineering, bn and bu
will change in a compatible way, but this isnot true for any of the other routines defined in md. This article
shows a specific use of bn and bu, and checks for their existence before attempting to access them. This is
not meant to endorse other uses of these or any other md routines.
SCOTT "ZZ" ZIMMERMAN is a DTS
printing guru. After two and a half years at Apple he says he's particularly impressed with the strictly enforced dress code.
In his spare time he sails, scuba dives for lobsters, and plays the piano, guitar, and saxophone. Zz has a penchant for
pets. His doorway is adorned by a melted gummy rat, a good luck charm left over from his Intel days.
At home, atop his monitor is perched a rare Asian black
scorpion (behind glass, we hope). His other cuddly pets include two geckos and a lovable iguana. *
The definitive references on the PostScript language are the PostScript Language Reference Manual (Addison-
Wesley, 1985, available from APDA--#T0182LL/A), the PostScript Language Tutorial and Cookbook (Addison-Wesley,
1985), and PostScript Language Program Design (Addison-Wesley, 1988). *
Thanks to Our Technical Reviewers Pete "Luke" Alexander, Jay Patel, David Williams*