Implementing printing in a Macintosh application should be pretty straightforward,
right? There are currently 18 high-level printing calls (listed on pages 9-92 and
9-93 of Inside Macintosh: Imaging With QuickDraw), which is only three more than
were listed in Inside MacintoshVolume II. Calling them in the right order gives you a
printing port that you can treat just like a graphics port -- and every Macintosh
application knows (or at least ought to know) how to draw into a graphics port.
But in spite of this apparent simplicity, there are an astounding number of Macintosh
applications that have problems printing. (Even products from Apple make the list
once in a while.) I think one of the reasons for this is that, while basic QuickDraw
printing is simple, printing is something that can be -- and has been -- made more
complex by various "extensions" to the original printing architecture. These
extensions offer greater control of the printing process, allowing you to take advantage
of special features available on some printers and to draw in more sophisticated ways
than QuickDraw allows. But they also introduce complexities that can get you in
trouble if you're not careful. In this column I'll give a few examples of places where
control comes only at the price of complexity, and therefore places where you need to
tread very carefully, if at all.
Picture comments are, on the face of it, wonderful things. They let you embed
commands in your output that can take advantage of particular printer features if
they're available, and they're automatically ignored by printer drivers that don't
support them. But there's a flip side: for every picture comment you use, you have to
provide an alternative for those printers that don't support it. There are also a
number of picture comments that should be avoided, as listed on page B-40 of Inside
Macintosh: Imaging With QuickDraw. As with any complex and powerful tool, the
potential for getting things wrong with picture comments is ever-present.
The SetLineWidth picture comment is a perfect example: not only is it supported by
only a few printer drivers, but it's implemented slightly differently in each of them.
On some printers the value you pass for the line width is used to modify the current
line width (for instance, passing 1/2 will halve the current line width), and on others
it's used as an "absolute" value (passing 1/2 will set the line width to 1/2 point,
regardless of the previous width). To obtain the desired results, you have to write
your code very carefully, and even then the SetLineWidth picture comment may not
work on the printer driver that the user happens to be using -- and there's no
QuickDraw alternative. The territory here is treacherous. Unless you really need
fractional line widths, it may be better to take the nice safe QuickDraw path.
The PrGeneral call added complexity to the Printing Manager -- and even more
complexity could be added by driver developers, often without accompanying
documentation. After all, since supporting the various PrGeneral opcodes (in fact,
supporting PrGeneral itself) is optional, printer drivers can define their own new
opcodes and nobody need be the wiser -- nobody, that is, except for the one developer
who needs the new opcode and the functionality it provides. Things get even more
confusing when the same added functionality is available via a different mechanism in a
different printer driver, so the application has to start using special-case code for
each printer driver it knows about. If you find yourself writing special-case code for
particular printer drivers, stop! Back up and look for another solution.
One commonly used PrGeneral capability, provided by the getRslOp and setRslOp
opcodes, is finding the resolution(s) supported by the printer you're using and setting
the resolution you want to print with. There's clearly a need for this sort of capability.
An application that shows graphs of curves or of raw data gathered from some source
wants the graphs to look good. Plotting individual pixels at 72 dpi doesn't make for
smooth-looking curves, so an application might be justified in asking to print at the
highest resolution the printer is capable of. But is PrGeneral the right approach?
A potential problem with using PrGeneral to get and set resolutions is that you're
depending on the printer driver to keep up with the times. The LaserWriter driver,
for example, is used for printers from the original LaserWriter all the way up to
high-end typesetters. The driver reports that the maximum physical resolution of the
printer is 300 dpi, even if you're printing to a typesetter that's capable of 1270 or
even 2540 dpi. The reason for this is that reporting a higher resolution could cause
applications that create bitmaps at the printer's resolution to run into QuickDraw's
limitations, such as the limit on rowBytes and the 32K maximum region size. This is
something that we plan to address in future versions of LaserWriter 8, but currently
an application that wants to know a PostScript(TM) printer's real maximum
resolution has to either parse the PostScript Printer Description file (PPD)
associated with it or query the printer directly, both of which are functions that the
driver should have to worry about, not the application.
In this case, there's an alternative to PrGeneral: If you're going to be generating your
data in a GWorld, just make sure the GWorld's resolution is whatever you need for best
results. Then take that same GWorld and use CopyBits to copy the PixMap in it to the
printer. If you provide appropriate source and destination rectangles, the
implementation of CopyBits in the printer driver will scale the PixMap, and you'll be
taking advantage of the resolution of the printer without having to worry about new
coordinate systems.
Determining just what resolution you need is, however, still a tricky issue. For
example, if you're printing a color image to a LaserWriter that can print only
black-and-white images and only at 300 dpi, the color image you're displaying
onscreen already has more detail than the printer can reproduce, so you don't need to
worry about sending a higher-resolution image at all. The way to tell for sure if you
have enough data is that your pixel density (in dpi) should be between one and two
times the "screen frequency" (in lpi) for the printer. The default screen frequency for
PostScript printers is listed in the PPD file for the printer, and in the future we'll be
providing access to the PPD file parsing code that's contained in LaserWriter 8's
PrintingLib, but for now you may just want to ask the user rather than parse it out
yourself.
If you're generating line art or other data that needs to have "hard edges" in a GWorld
that's going to be sent to the printer, you've got a different problem: unless you specify
the data at the printer's resolution (or higher), it will need to be scaled up to the
printer's resolution, producing large, blocky pixels. Your users will think you're a
bozo, unless of course your product is supposed to make large, blocky pixels. The right
solution is to avoid sending data that needs to have hard edges as bitmapped images, if at
all possible. This is the sort of data that really should be maintained as objects. If you
want to draw the letter A, for instance, ask QuickDraw to draw it to the printer for you
if possible, rather than image it into a bitmap first. If you really need to generate
bitmaps of hard-edged data, be aware that you'd better have your machete sharpened
and ready, since you're heading into the brush. On the other hand, this may be a great
opportunity to generate your own PostScript code.
Generating your own PostScript code is another powerful technique that can get your
application into trouble. In many cases, it's the right answer to a thorny dilemma; for
instance, if you need drawing primitives that QuickDraw doesn't supply, this may be
the only way to get them. After all, cubic BÉzier curves are neat and powerful. The
problem arises when the application developer either doesn't understand how to write
compatible PostScript code or takes shortcuts in the PostScript code.
An excellent example is an old version of a certain very popular graphics program
that saved its pictures with an EPS version of the graphic embedded in the PICT data.
Unfortunately, the PostScript code in the EPS version depended on the md dictionary, a
private dictionary used by the LaserWriter driver. After warning developers for
years that the md dictionary was private, Apple Engineering felt justified in changing
it. When the new version of the LaserWriter driver shipped, suddenly many graphics
quit printing. The problem was made even worse by the fact that many of these
graphics had been shipped as clip art, and they still occasionally pop up to bedevil us
today.
The solution isn't to avoid PostScript code entirely. Just make sure that if you do
generate it, the code is compatible and portable. Obviously, it shouldn't use any of the
LaserWriter driver's private PostScript operators. If you make graphics with
PostScript code embedded in them, be sure that the PostScript code they contain
conforms to the EPS specification that's described by Adobe(TM) in the PostScript
Language Reference Manual, Second Edition, Appendix H. Also, be sure to send the
PostScript code with the PostScriptBegin, PostScriptHandle, and PostScriptEnd
picture comments, as described beginning on page B-38 of Inside Macintosh: Imaging
With QuickDraw.
Another thing application developers have tried over the years (with mixed success)
is to do their own PostScript font management by talking directly to the printer. This
is something applications really need to avoid. The LaserWriter driver knows how to
handle the PostScript fonts needed to print a page (or series of pages). Applications
that attempt to manage the fonts themselves are more likely to get poor font
management for their efforts, since the LaserWriter driver (or the LaserWriter GX
driver) will have a much harder time recognizing which fonts are needed on which
page. When this happens, the drivers err on the side of safety: if there's any doubt
about when a font is used, it will be included for the whole job, which is usually
exactly what the developer was trying to avoid. Let the driver handle font management.
I've given a few of the more common examples of how printing has grown in
complexity over the years, and how application developers can sometimes get in
trouble by trying to take advantage of it. Printing doesn't need to be much harder than
drawing to the screen if you stick to the rules, and even when you want to take
advantage of particular printer capabilities, you can usually do so in safe, compatible
ways. As tempting as it sometimes is to wander off the known path and plunge headlong
into the uncharted jungle of possibilities, doing so usually just results in trouble --
for you and for your users. In printing, as in all programming, remember: keep it
simple.
RECOMMENDED READING
DAVE POLASCHEK (davep@best.com), formerly of Apple's Developer Technical
Support group, got so confused by the lack of weather in California that he moved back
to Minnesota. This probably won't seem like such a smart move when Celsius and
Fahrenheit show the same temperature and Dave starts singing that verse from Jimmy
Buffett's "Boat Drinks" that goes, "This morning, I shot six holes in my freezer. I
think I've got cabin fever. Somebody sound the alarm."*
Thanks to Rich Blanchard, Paul Danbold, Dan Lipton, and Steve Simon for reviewing
this column.*