Printer Sleuth
Volume Number: 3
Issue Number: 3
Column Tag: Resource Roundup
Printer Sleuthing 
By Joel West, Western Software Technology, MacTutor Contributing
Weekend diversions
Normally, the NFL season is considered to last from early September until some
time in December. However, around here, the season has ended early the last few
years, as the home team has, for all practical purposes, been eliminated a few weeks
into the season. When San Diego found itself well behind the four other teams in its
division, I was faced with a question of what to do on a Sunday afternoon. (Presumably
not a problem at MacTutor, since they’re a stone’s throw from the Anaheim Rams, who
always seem to have a winning record...)
About the same time as I gave up on the Chargers, I was discussing a design issue
with my friends at Silicon Beach Software, who recently released their
painting/drawing program, SuperPaint. The program treats documents prepared for
an Imagewriter slightly differently than those for a LaserWriter. The former are
edited at 72 dots per inch, while the latter are normally shown at 75 dots per inch,
since that is an even multiple of the LW’s 300 dpi resolution, allowing clean scaling
and accurate positioning of bit maps and objects. (The LaserBits™ uses the actual 300
dpi resolution.)
Anyway, I was suggesting that it would be nice to pick up the target printer from
the Chooser desk accessory, rather than have a separate option to specify the target
printer. The latter would mean that a novice user could easily have set the document
for an Imagewriter while printing on a LaserWriter, or vice versa. But, as was
pointed out to me, Mac 512 owners with a single 400k internal disk can hold System,
Finder, SuperPaint (about 140k) and either Imagewriter or LaserWriter, but not
both. As my floppy-swapping days are only a few months remote, I could understand
the problem.
Which led me to try to build a dummy LaserWriter driver, that would provide
“Page Setup” functionality similar to the actual driver, but taking up less disk space.
But since Inside Macintosh doesn’t say how to write your own printer driver, I
had to dig deeper into the many undefined nooks and crannies. In this article, I’ll
examine the application side of printing, while in a future article, I’ll examine how a
printer driver works and give some tips on how to write your own printer driver.
About Printing
When it comes time to print, what does your program do? First, it’s necessary
to introduce the concept of a printer driver, corresponding to those cute little icons in
the System Folder that handle whatever printer you’re using (Figure 1). How your
program communicates with those drivers will be discussed later.
If you’re a good, compatible Macintosh programmer (i.e., your software will
work with the next video display), everything your program draws on the Macintosh
screen is done with QuickDraw. QuickDraw does everything in terms of GrafPorts,
which are sometimes served ala carte, but usually part of a full course of data provided
by the Window Manager or the Dialog Manager.
Fig. A Output from our PrintTest Program
To print, you just draw everything again, only this time in the printer’s
GrafPort. The printer intercepts the QuickDraw operations and does whatever is
necessary to translate those operations into dots on the printed page.
Of course, there are some restrictions and limitations on what operations should
be used or how they should be used, particularly on the LaserWriter and LaserWriter
Plus. Also, you may need to perform certain printer-specific operations, particularly
involving PostScript, as will be discussed later.
But who does the work?
The specifications of the entire implementation of printing are not well defined.
Part of this is because it was a late topic into Inside Macintosh; part of it is because
some of the information is device-dependent, and your program should be designed to
work with any device. Also, Apple expected it would write the only Macintosh printer
drivers, although third party drivers have been written by independent software
developers.
Normally, it’s somewhat risky to delve below the defined specifications of a
system such as the Macintosh Operating System (the Toolbox isn’t much used here),
since there’s no guarantee that these implementation details will remain the same in
future releases. In fact, the fact that these details are not specified may mean that
Apple is deliberately reserving the right to change the implementation in the future.
However, there are a few safe exceptions. Sometimes, there are de facto
standards that exist that are just as ironclad as written standards, and it turns out that
printing is one such case.
When you open the chapter on “The Printing Manager” in Inside Macintosh, you
will see throughout the chapter the notation
[not in ROM]
What this means is that the entire Printing Manager, as such, is in the “glue”
that is linked into your application when it references printing operations.
This glue, in turn, references system resources and the appropriate printer
driver. Figure 2 illustrates the linkages for the LaserWriter driver; the AppleTalk
ImageWriter driver is similar (the ordinary ImageWriter doesn’t use AppleTalk.)
There are no traps to implement these routines and thus, no trap routines. For a
trap, Apple can issue a new ROM with new trap routines, or put a PTCH resource into
the System file. However, if you link your program this afternoon (or linked it two
years ago), there is exactly one version of the Printing Manager available to your
program, despite whatever changes Apple might make (or, more accurately, wish it
could make).
That doesn’t mean that the printer drivers can’t be improved, or new printer
drivers can’t be written. Rather, it means that the specifications are fixed up to the
entry points of the printer drivers.
What happens when your program prints? The Chooser desk accessory has
previously selected one of the available printers, and modified a system resource
accordingly. (For a full explanation, see Bob Denny’s excellent “How the Chooser
Works,” in the July 1986 MacTutor, or the “Device Manager” chapter of Inside Mac,
Volume IV.)
The Printing Manager opens the corresponding printer driver file. Once upon a
time, this had to be on the boot disk. Nowadays, it is expected to be in the “blessed”
HFS folder, i.e., the one with the Finder and System in it.
The glue code will reference the PDEF (Printer DEFinition) code in the printer
driver, which does the actual printing and user dialogs. For some control calls, it will
call the standard DRVR named “.Print”, which in turns calls the appropriate DRVR in
the printer driver.
If you’re only printing with your application, not writing a printer driver,
you’re primarily concerned with the Printing Manager calls, which will be the
subject of the remainder of this article.
User-level interface
From the user’s standpoint, there are three active steps that can be taken in
conjunction with printing:
1. Use the “Chooser” to select a printer. This is well covered by Denny’s earlier
article, so I won’t say more here.
2. Select the “Page Setup” dialog, referrred to as the style dialog (Figure 3).
This sets printing characteristics that may affect the size or layout of the edited
text. For example, selecting the (nearly obsolete) “US Legal” paper would
indicate to the application that a taller drawing size is available.
3. Select the “Print” dialog, or job dialog (Figure 4). This prepares a job
immediately for printing.
The user should be able to cancel any of these steps, without any affect. And it’s
not necessary, of course, for the user to go through all three steps each time,
particularly for similar documents (omit step 2) on the same printer (omit step 1).
It’s also possible for an application to allow printing without the third dialog, although
this should be unusual.
The user should, however, ALWAYS select “Page Setup” after changing the
printer using the Chooser, and it even includes a message to this effect. This allows
your application to assure that its current GrafPort is consistent (for page breaks,
rulers, etc.) with the actual target output device, since your application should
already allow for changing the page size, orientation, and resolution, which is
available to the user in the style dialog.
There are also several steps that are not seen by the user, but must be taken by
an application to assure consistency of the printing operations. To see how this works
first requires examining the mechanism used to communicate information about how to
print a document, the print record.
Print Records
Most of the control information for printing is carried by a TPrint Pascal record
(C struct.) Table 1 shows a psuedo-declaration of the TPrint record. TPrint is
normally used through a TPPrint pointer or a THPrint handle.
This is only a psuedo-declaration; each of the embedded records are actually
declared as new types (shown in the comments). For clarity’s sake, these embedded
records are shown inline, since only two fields are directly part of the TPrint record,
one of them the filler. The enumerations are also shown inline. Also not normally
shown are the offsets, which are essential if you have to reverse engineer a TPrint
from a hex dump, or want to debug application printing code at the assembly level.
The final field is a filler to round the record out to an even 120 bytes -- the
amount of memory returned by sizeof(Tprint) in both Pascal and C, and the amount of
memory you need to reserve in your document for holding the values. (See, for
example, the discussion of the MacWrite file format in Technical Note #12.) Why
isn’t the print record stored as a resource? It would have made sense, and there’s
even a resource type used by a few applications (PREC), but most programs --
including Apple’s -- include it in the data fork.
For our purposes, there are several fields of interest. Assuming the declaration
THPrint ph; /* C */
then, the ph^^.prInfo ((*ph)->prInfo in C) subrecord contains most of the
information the application will access directly. In particular, the program will want
to know the horizontal and vertical resolution used for drawing, so that it can put up,
for example, the appropriate rulers. (MacDraw worries about this, but MacWrite
just cheats and always assumes the same resolution for ruler purposes.)
Most significantly, the field ph^^.prInfo.rPage gives the size of the drawing area,
in units of the horizontal and vertical resolution. This works out to be 10.44 inches
vertically by about 8 inches for an Imagewriter using US Letter paper. (Tall adjusted
changes the horizontal resolution, but the equivalent measurement of rPage in inches
remains the same.)
For the LaserWriter and LaserWriter Plus, rPage works out to be 10.11 inches
by 7.66 inches using US Letter paper. This size is odd because there is not quite
enough RAM on either model to image a full-page bit map. The vertical .33 inches is a
missing line in MacWrite, while MacDraw rounds the horizontal 7.66 down to the
nearest half inch, breaking the display into multiples of 7.5 inches.
The size of ph^^.rPaper gives the size of the actual paper, since none of the
existing printers allow you to print to the margin. This is slightly wider on all four
sides of rPage. As shown in Figure 5, the local coordinate origin (as QuickDraw terms
it) for rPaper is the same as for rPage, in that (0,0) is the upper-left corner of the
actual drawing area. As you are usually concerned with rPage and not rPaper, this is a
reasonable choice. Don’t assume that there will always be a margin, since it’s
conceivable that rPaper could be the same as rPage: for example, “No Gaps Between
Pages” on the ImageWriter sets the top and bottom coordinates of the two rectangles
(for US Letter) to 0 and 792, respectively.
Another field of interest for our purposes is the byte that tells you which printer
you’re using. The upper half of the 16-bit word ph^^.prStl.wDev gives the device
you’re using. Why the upper half of a word is used and not a separate field, I don’t
know, but the lower half includes other information about the printing
characteristics, as shown in Table 2. (The “W” is not capitalized in the name of the
original Imagewriter, according to Apple product documentation, but all the other
Writers are capitalized.)
TPrint: RECORD
0 iPrVersion: INTEGER
prInfo: RECORD { TYPE TPrInfo }
2 iDev: INTEGER;
4 iVRes: INTEGER; { dots per inch }
6 iHRes: INTEGER; { dots per inch }
8-15 rPage: Rect;
END;
16-23 rPaper: Rect;
prStl: RECORD { TYPE TPrStl }
24 wDev: INTEGER; { printer type in upper byte }
26 iPageV: INTEGER;
28 iPageH: INTEGER;
30 bPort: CHAR;
31 feed: (feedCut,feedFanfold,feedMechCut,feedOther);
END;
prInfoPT: RECORD { TYPE TPrInfo }
32 iDev: INTEGER;
34 iVRes: INTEGER;
36 iHRes: INTEGER;
38-45 rPage: Rect;
END;
prXInfo: RECORD { TYPE TPrXInfo }
46 iRowBytes: INTEGER;
48 iBandV: INTEGER;
50 iBandH: INTEGER;
52 iDevBytes: INTEGER;
54 iBands: INTEGER;
56 bPatScale: Byte;
57 bUlThick: Byte;
58 bUlOffset: Byte;
59 bUlShadow: Byte;
60 scan: (scanTB,scanBT,scanLR,scanRL);
61 bXInfoX: Byte; { filler }
END;
prJob: RECORD { TYPE TPrJob }
62 iFstPage: INTEGER; { default 1 }
64 iLstPage: INTEGER; { default 9999 }
66 iCopies: INTEGER; { not used by LaserWriter }
68 bJDocLoop: (bDraftLoop, bSpoolLoop);
69 fFromUsr: BOOLEAN;
70 pIdleProc: ProcPtr;
74 pFileName: StringPtr;
78 iFileVol: INTEGER;
80 bFileVers: Byte;
81 bJobX: Byte; { filler }
END;
82-119 printX: ARRAY[1..19] OF INTEGER; { in C, 0..18}
END;
TPPrint = ^TPrint; { the pointer }
THPrint = ^TPPrint; { the handle }
Table 1: Contents of a Print Record
As you can see, except for the use of fPortrait, the interpretation of the lower