Do you have a printer that would print awesome text and graphics if only someone would write a driver for it? Have you looked at the driver specifications and become hopelessly confused? If you want to give your Apple IIgs some expanded printing capabilities, don't put this issue down until you've read this article!

In theory, printer drivers seem like a great solution. All you have to do is drop a printer driver file in your Drivers folder, and all of a sudden you'll be able to create dazzling text and graphics from whatever desktop application and on whatever kind of printer you happen to use with your Apple IIgs. No more writing to printer manufacturers or waiting for application upgrades to support your printer. 

Unfortunately, the reality isn't quite as nifty as the theory. Even though Apple released printer driver specifications in early 1988 (just before System Disk 3.2), only a few third-party printer drivers have surfaced. The specifications are complex and sometimes confusing, and they have not always been accurate. Most of all, printer drivers are intrinsically complicated and difficult to develop. The driver has to do all of the work in getting images printed, with no imaging help from the Print Manager.

This article explains the mysteries of the printer driver: what it does, how it does it, and how to write one. To illustrate the concepts, we've provided a sample printer driver called Picter. Picter takes the image to be printed and saves it to your boot disk as a QuickDraw II picture file. Picter allows you to literally print a graphic document to disk. Much of Picter's structure and code is directly applicable to any printer driver. What's more, the dialog routines in Picter, which are very similar to those in the new ImageWriter and ImageWriter LQ drivers released with System Software 5.0.3, will enable you to be consistent and stylish in your user interface. You will find Picter in the IIgs Printer Driver folder on the Developer Essentials disc.

HOWÊPRINTING WORKS

Printing from a desktop application appears to be a black box. You make some Print Manager calls and voila! - there's a piece of paper with a printed image of what you drew. The Print Manager uses some serious magic to turn your image into ink on paper, but that's all hidden from the application. 

So now that we know what we're getting into, let's briefly review how applications print through the Print Manager.

WHAT THE APPLICATION SEES
In the Apple IIgs desktop environment, documents are kept in windows, which are extended versions of the QuickDraw II drawing environment - the Grafport. The features defined by the Grafport include where drawing will and will not occur, what size pen will be used to draw lines and other objects, what method will be used to draw them, what colors and patterns will be used with the objects drawn, what style, size, font, and colors will be used for text drawing, and where the image resides in memory.

The model for printing is quite similar to drawing in a window. Instead of drawing into a window Grafport, your application draws into a printing Grafport, which defines the drawing environment for a single page. The clipping and visible regions (the clipRgn and visRgn) are set to the rectangular area of the page, for example.

An application prints by drawing into a printing Grafport, which it obtains by opening a printing document with the Print Manager call PrOpenDoc. The Print Manager responds by returning a printing Grafport in which the material to be printed should be drawn. The printing Grafport is initialized at the beginning of each new page (signified by PrOpenPage). The application then draws the page, closes it (with PrClosePage), and repeats this sequence until all pages have been printed. The application then closes the document (with PrCloseDoc) and prints any images the driver may have spooled with PrPicFile. The sequence of calls starting with PrOpenDoc and ending with PrPicFile is referred to as the print loop, since the middle calls (PrOpenPage and PrClosePage) are repeated once for each page to be printed. Note that PrPicFile should always end the print loop.

HOW IT REALLY WORKS
If the Print Manager does all this for the application, as the Apple IIgs Toolbox Reference says it does, where does a printer driver fit in?

To understand how printer drivers work, you first need to realize that the preceding description of how applications print is exaggerated. Everything listed above as done by the Print Manager is really done by the currently selected printer driver. Although the calls are Print Manager calls, the only action the Print Manager takes on these calls is to make sure the printer driver is available and to dispatch the calls to the driver. The application model says this work is done by the Print Manager to prevent application dependency on any particular driver. From the application's point of view, the Print Manager's role in printing allows the application to be independent of any particular driver. But in reality, your printer driver will handle all the work associated with several of these "Print Manager" calls.

While at first it might seem like a cop-out by Apple to require the printer driver to handle all the work in the print loop, this strategy actually makes a lot of sense. The printer driver must ultimately transform images into ink on paper, so for maximum flexibility Apple has given the printer driver control over the entire imaging process, from the opening of a document to the printing of spooled images. Since no one but the printer driver author knows what user-selectable features the driver will support, the printer driver should be responsible for the style and job dialog boxes through which these features will be chosen. And because the printer driver knows how to best handle internal errors, it's a good idea to make it responsible for returning and accepting error codes from the application.

Although the printer driver has to handle all the imaging, the Print Manager does provide a lot of support for other parts of the printing process. One of the tasks the Print Manager supports is communication - once an image has been converted into printer codes, the codes have to be sent to the printer. The Print Manager keeps track of a different kind of driver - the port driver - that handles this communication with the printer through the internal ports of the Apple IIgs (or through the slot-based peripherals). The port driver essentially relieves the printer driver of the work of communicating with the printer. All the printer driver has to do is ask the port driver to read or write data to the printer, and the port driver handles all the details. The Print Manager also keeps track of which printer and port drivers the user has chosen with the Control Panel desk accessory. 

Figure 1 shows the relationship of the printer driver and the port driver to the Print Manager. The Print Manager handles some duties alone while passing others directly through to the printer or port driver.

THE PRINT RECORD
Since the printer driver does all the interesting imaging work, it has to have some way to exchange vital information with the application. Applications need to know the size of the pages to be printed so that they can paginate properly.  They may need to know the vertical sizing factors so that better resolution graphics can be printed when higher resolutions are available. Or they may need to know the resolution of the printer for precise printing chores. This information is communicated through a data structure known as the print record. The print record is associated with every document to be printed, and it is the only way the printer driver can keep these parameters associated with a document. 

*** Figure 1: Print Manager Calls ***

Figure 2, on the next page, shows the print record in fully documented detail. Notice that some fields are marked simply as reserved - that means reserved for Apple. Using these fields is a really good way to make your application not print with other drivers or to make your driver not work with future system software.

The print record contains all the parameters associated with a printing job. It includes not only the page and paper sizes and the resolution of the printer and other hardware parameters, but also the values selected by the user in the Page Setup and Print dialog boxes, which are presented by the printer driver. The print record contains all the information necessary to print a document the same way as many times as necessary.


*** Figure 2: The Expanded Print Record***

PRINTING MODES
In addition to being concerned about what to print, you must be concerned about the way in which it's printed. There are two modes for printing. The differences between these modes amount to two different models for printing. 

Immediate mode. When you print in immediate mode, every page is printed as it's defined. The driver does not store an image of the page before it sends it to the printer. This strategy can limit the driver's options when printing a page. To see how, you first have to understand how immediate mode works.

When QuickDraw performs a graphic operation, it calls a standard set of low-level routines to do it - the QuickDraw bottleneck procedures. A pointer to them exists in every GrafPort's grafProcs field, where a value of 0 means that QuickDraw should use the standard procedures. This is briefly mentioned in Technical Note #35, but it is covered in great detail in the note just preceding it: Apple IIgs Technical Note #34, Low-Level QuickDraw II Routines.

To print in immediate mode, you install your own set of bottleneck procedures into the printing GrafPort. When the application draws any object into the printing GrafPort, QuickDraw calls your bottleneck routines to actually image that object.

Because immediate mode printing responds to object-drawing commands sent by QuickDraw, immediate mode printing works best for target devices that handle similar objects. For example, the LaserWriter has built-in PostScript code that can image objects in much the same way QuickDraw does. The LaserWriter driver installs bottleneck procedures that convert QuickDraw objects into PostScript objects and sends them immediately to the LaserWriter, printing the page when the page is closed with PrClosePage.

Unfortunately, most printers do not handle graphic objects. The graphics capabilities of most printers are of the "print a dot of this color at this location" variety. To print images to these devices, a driver has to convert the images into printer codes that place the dots where they need to go. Doing this properly requires waiting until all objects are drawn on the page before sending any codes to the printer. If you try to image and print each QuickDraw object as it's drawn, you'll get the wrong results when the application draws white pixels on top of previously colored pixels. (You will also have to move the paper backward and forward enough to inspire demonic possession stories.)

Because of this limitation, many dot-matrix printers ignore graphic objects when printing in immediate mode, transforming only text drawing into simple ASCII text printing using the printer's built-in font. Since this is not what you see on the screen, immediate mode printing is often referred to as draft mode, even though immediate mode printing can be of excellent quality on the right target device.

--- sidebar
SOME THINGS TO KNOW ABOUT PRINT RECORD FIELDS

Volume 1 of the Apple IIgs Toolbox Reference does a good job explaining most of the fields in the print record, but it contains some incomplete information. One such omission occurs in the Reference's description of the iDev field. The iDev field identifies the kind of printer. The Reference lists two values for this field - ImageWriter and LaserWriter - which leads to problem code in applications such as the following: 

if PrintRecord.iDev = 1 then
	{It's an ImageWriter}
else
	{It's a LaserWriter}
endif

In reality, there are at least six defined values for iDev:

$0001	ImageWriter
$0002	ImageWriter LQ
$0003	LaserWriter
$0004	Epson
$8001	Generic dot matrix printer
$8003	Generic laser printer

The $8001 and $8003 iDev values are provided for generic compatibility. If a driver has an iDev of $8001, it interprets the style subrecord of the printer record as is documented for the ImageWriter driver. If the iDev is $8003, it interprets the style subrecord as it would for the LaserWriter driver.

Unfortunately, because this is the only device identification field present in the print record, there is no way to uniquely identify printers assigned to these values. For instance, suppose you have two printers with printer drivers in your system - the GlopJet and the ImageStamper. Both drivers use an iDev of $8001. Applications are encouraged to save print records with documents so that the user's print settings are maintained across sessions. If you open a document with a print record created by the GlopJet driver but your currently selected printer is the ImageStamper, the ImageStamper driver will be passed the print record and asked to validate it. The ImageStamper driver looks at iDev and sees $8001, and it has no other way to know that this print record is not an ImageStamper record. 

Drivers with unique iDev values don't have this problem. For example, the LaserWriter driver knows that if the iDev value isn't $0003, it's not a LaserWriter print record and should be filled with default values.

Apple's Developer Technical Support group will assign new iDev values to printer driver authors if neither the $8001 or $8003 interpretation of the style subrecord is suitable, but you must be aware that some applications will not work with other formats of style subrecords. It's better for compatibility purposes to support one of the existing style subrecord formats if possible.

For instance, some applications don't like to let the user choose items that don't work very well. If an application doesn't print very well without color, it might do something unfortunate like set the "color" bit in the wDev field before starting the printing loop. If the driver doesn't support color printing, it will catch this error in the PrValidate routine and may reinitialize the print record with default values. If the driver author is lucky, the application will first check the iDev field to make sure that the "color" bit is supported in the style subrecord. If you're really lucky, the application will call PrGetPrinterSpecs and keep out of the print record altogether. Many applications just blast the bit.

WHAT DRIVERS AND APPLICATIONS SHOULD DO
To keep your handling of the print record kosher, there are a few things you should keep in mind.

First of all, since print records are associated with printing jobs, it would be nice to keep all parameters that go with a printing job in the print record. But since a field is either defined or reserved, it's not clear where you can put a new parameter. If your printer has 14 different internal fonts and you want the user  to choose one of them, where can you put that information?


Apple has set aside the 38 bytes in the print record labeled printX for printer driver use. Nonstandard parameters and values can go there. This area is left  to the discretion of the printer driver. It will always remain a miscellaneous storage area, no matter what Apple does with it in drivers it develops, and its interpretation will not depend on the iDev field. In other words, if the LaserWriter driver stores a parameter there, drivers with $8003 iDev values are not expected to do the same.

Applications absolutely must not tamper with the printX subrecord nor try to interpret any items in there. Applications have most of memory for parameters, while printer drivers only get these 38 bytes in the print record. Applications, keep out.

It's also important that neither drivers nor applications alter the print record fields marked reserved for Apple - in particular the prInfoPT and prXInfo subrecords. Older versions of Apple's drivers stored a private copy of the prInfo subrecord in prInfoPT (PT stands for "private"). Discovering this fact, some applications used this copy instead of the original. Since this feature was never documented, however, relying on it is likely to make your application not work with other drivers. As for the prXInfo subrecord, it may be defined in the future for the storage of parameters between spooling and printing (between PrCloseDoc and PrPicFile).

--- sidebar

Deferred printing. Since immediate mode printing is not suitable for graphics on many printers, most printing jobs will be deferred. In deferred or spool mode, everything that is drawn is captured to be printed later. Text is imaged together with graphics to return as accurate a reproduction of the document as possible.

How the printer driver captures the image is entirely discretionary. If you like, you can attach a pixel map large enough for the entire page to the printing GrafPort and let the application draw the page into the pixel map. This method would give you a premade pixel map, waiting for you to transform it into printer codes and send it out. At screen resolution, however, a full U.S. letter-sized page would take just over 56K of contiguous memory. That's per page - a 20-page document would require 20 such blocks.

For this reason, most printer drivers (including Picter and Apple's drivers) use QuickDraw pictures to capture the images. Pictures are an encoded history of the QuickDraw calls used to create an image. When you play back a picture using the QuickDraw auxiliary call DrawPicture, QuickDraw does all the drawing necessary to recreate the image. Instead of taking 32K to store a screen-sized rectangle filled with a given pattern, a picture stores the same information in the 
few bytes that encode the pattern, the rectangle size, and the "paint" command.

Because pictures contain recorded QuickDraw II objects, they can be redrawn at different resolutions or in different proportions with excellent results. If you call DrawPicture with a destination rectangle of a different size than the one the picture was recorded with, QuickDraw's picture algorithms are capable of changing the sizes and proportions of every object in a picture to match the changed destination rectangle.

This intelligent scaling behavior makes pictures perfect for the needs of most printer drivers. Since most printers are capable of screen resolution that is better than that of the Apple IIgs (80 pixels per inch horizontally by 36 pixels per inch in 640 mode), some kind of scaling will be necessary to create screen resolution images at the proper size regardless of resolution changes. For example, to achieve an image of the proper size when your target device supports 160 dpi horizontally by 72 dpi vertically, you'll need two printer pixels in each direction to represent one screen pixel.

Simply magnifying each screen pixel to be the appropriate number of printer pixels gives the image the right size, but the resolution is still the same as the screen's. To get better resolution, QuickDraw's picture algorithms are a good choice. For our sample target device that supports 160 dpi horizontally by 72 dpi vertically, your driver could call DrawPicture to image the stored page-picture in a rectangle twice as large as was used to record the picture. QuickDraw will then draw all the objects in the picture at twice their original resolution. Your driver can translate the resulting pixel map into printer codes at one screen pixel per printer pixel. The end result is a printed image with the same physical size as the original screen image but with a resolution twice as great.

Take a look at Figure 3. In Figure 3a, we show a circle and the letter A drawn at screen resolution. In Figure 3b, the same image is magnified, pixel by pixel, to about twice its normal size. It doesn't look any better, just bigger. However, if we have these objects in a picture, we can use DrawPicture to draw them at twice their normal size. The picture algorithm redraws the objects with increased resolution instead of simply magnifying existing pixels. The increased resolution allows QuickDraw to draw a much smoother circle (since the screen has the same resolution, but the circle has twice the radius) and a smoother-looking A since we use a 16 point font instead of an 8 point font. (Rather than drawing the font recorded in the picture and scaling the image, QuickDraw calls the Font Manager to get the best available font for the destination. Requesting a larger font size often returns a custom-designed font strike from disk, making a marked improvement in the appearance of text at higher resolutions.) The results of the picture scaling are shown in Figure 3c. Figure 3d shows Figure 3c scaled down to actual size.

*** Figure 3: A Demonstration of Picture Scaling vs. Magnification ***

Of course, if you want to draw objects at three times their normal size, you probably won't be able to draw an entire page at once. You can, however, draw them into a printing GrafPort with the clipping region set to a small rectangle of the picture. If you divide the page into ten such "bands," you only need one-tenth the memory the entire page would need. You just have to call DrawPicture ten times to complete printing for the page.

This technique is referred to as banding and is done by most printer drivers in deferred mode to work even in low-memory conditions. To image a full 8 1/2 by 11-inch page at three times resolution would require 506K per page (56K at normal resolution magnified by three horizontally and by three vertically), but dividing it into 20 bands requires only 25K per band - and the band buffer is reusable. Dividing it into 51 bands requires under 10K per band. Since applications are instructed not to call PrPicFile if a 10K buffer isn't available (see Volume 1 of the Apple IIgs Toolbox Reference, pages 15Ð30), you can always use a 10K buffer and you may be able to use a much larger one if memory is available. You'll have to divide it into 102 bands if vertical condensed mode is selected, since that doubles the vertical resolution.

The drawback to this method is that it's slow. QuickDraw can't know before interpreting the stored picture operations which ones will be clipped out and which will actually be drawn, so it spends a lot of time drawing the 50/51sts of the page that don't show up each time. If there are a lot of fonts on the page, the Font Manager spends time installing versions of them three times larger than the original, which in turn takes a lot of memory and makes things even slower. Generally, the more memory you can use for the band buffer, the faster printing will go. The fastest method would be to get the entire page imaged at once, but that's not always feasible.

WHAT YOU'LL NEED

The printer driver author has to create a set of routines that can accurately reproduce a graphic image on the printer or other reproduction device - FAX modem, graphic language device, and so on. Besides this article, you'll need information from a range of sources to write a good driver.

¥ Apple IIgs Technical Notes #35 and #36. Technical Note #35 is the only document that completely and authoritatively defines what each printer driver routine must do, as well as the structure for printer drivers. There have been mistakes in this note in the past. Since few developers have written printer drivers, we haven't gotten much feedback. This article was written using the September 1990 revision of the note, as well as corrections to the March 1990 version. 
	
Technical Note #36, Port Driver Specifications,  is the complete specification for port drivers, listing the parameters for each call. The calls are made through the Tool Locator.

¥ Apple IIgs Toolbox Reference series, published by Addison-Wesley.  The Print Manager and its data structures are defined in Volume 1; necessary QuickDraw routines are in Volume 2; and corrections and new calls to all the tools are in Volume 3. The beta drafts of any of these books are not good enough.
¥ Knowledge of your target printing device. If you can write a routine (in a desk accessory, perhaps) that can print a pixel map (like the entire screen), you have a good start for some of the imaging routines you'll need in your driver. 
¥ Familiarity with QuickDraw. Since printing occurs when the application draws into a printing GrafPort, you have to be able to manipulate GrafPorts and their clipping components. To print in deferred mode, you have to be able to store images and reproduce parts of them for translation to printer codes.
¥ Knowledge of the Print Manager architecture. In addition to the 17 calls your driver will handle, you should be familiar with the other Print Manager and port driver calls so that you can use them to your advantage. 

THE PHYSICAL STRUCTURE

There is a standard physical structure for printer drivers to follow so that the Print Manager can perform its dispatching properly.

--- sidebar

Apple's printer drivers have dominated the print driver development environment. This dominance has discouraged the creation of third-party drivers, which has in turn made a bad situation worse. Since there are few drivers other than Apple's to test with, applications tend to do unsavory things with drivers because they're expedient. Since applications do unsavory things, developers who want their drivers to be backward-compatible with applications tend to disassemble the Apple drivers to figure out what to do. Since everyone does unsavory things, the system winds up in an unusable and unmaintainable state because no one wants to rock the boat.

The ImageWriter driver
Of all Apple's drivers, the old (pre-5.0.3) ImageWriter driver has caused the most headaches. The main problem with this driver is that it's a hybrid. Long ago, the structure of the Print Manager was quite different from what it is today. The Print Manager had "high-level drivers" and "low-level drivers." High-level drivers would communicate with the application, and low-level drivers would do the actual imaging or communication tasks. You will still see evidence of these things in some printing discussions. The giveaway is usually the abbreviations HLD for high-level driver and LLD for low-level driver. 

When the Print Manager architecture was changed to its current design, the ImageWriter driver was converted -  not rewritten as it should have been. The conversion created a lot of source files and put nearly every routine in a place where you wouldn't expect to find it. As new features were added, the entire thing became more and more unwieldy, until at last Ben Koning broke from the beast by creating a new, vastly improved ImageWriter driver for System Software 5.0.3 (with some imaging routines by Apple IIgs graphics wizard Jason Harper). 

The Print Manager has a few features in it for the questionable use of the old ImageWriter driver. These have never been documented and now that the old ImageWriter driver is going away, these features may go away as well. If you have ever disassembled the driver (not kosher according to the license agreement anyway), you may have discovered some of these less-than-desirable programming practices: 

¥	The ImageWriter driver used the value in the accumulator at entry time as a direct-page register. 
¥	The ImageWriter driver tended to print correctly when print record fields were set to totally invalid parameters.
¥	Sometimes the ImageWriter driver played loose with the parameters. It was known to work acceptably when printGrafPortPtrs were passed where print record handles were expected. 

Walking a thin line
 All of this leads to the question of how you will write your driver: will you create your driver strictly by the book, or will you program defensively in an attempt to work with those who broke the rules? If you use only the defined print record fields and stay clear of undocumented structures, your driver will work fine with future versions of the Print Manager and most applications. On the other hand, if you don't support the unorthodox use of the print record, your driver is less likely to work with some of the bigger and more widely used Apple IIgs applications.

The scariest thing about continuing to support these structures is that it gives application authors no reason to stop using them. For practical reasons, it may be impossible to avoid using some of these undocumented structures. Keep in mind, however, that the less of this you can get away with, the better off everyone will be in the long run.

--- sidebar

A printer driver begins with two zero bytes and a count of the number of routines the driver supports. The Print Manager will transform the call number into a precomputed index for a four-byte per entry jump table, and put this index in the X register. Thus an indirect indexed jump, jmp(driverTable,X), will call the routine. 

Note that each jump table entry is four bytes long, but a jmp(driverTable,X) instruction will only use the low word of each entry. This requires all your entry points to be in the same segment. To get around this, you can have a short entry segment that JSLs to routines in other segments. If you like, you can rewrite the entry code to use all four bytes of the address instead of the low two. Just remember to preserve the X register, as it's your only indication of which routine to call.

The entry point for the driver is at the fifth byte (just after the function count). Note that before September 1990, Technical Note #35 always had the table entries for PrPixelMap and PrDriverVer backward, and that PrGetPgOrientation was misspelled in the note.  Also, the count of routines should be 17. A correct driver header looks like this:

DriverStart	START

		dc	i2'0'			; identifying word
		dc	i2'(ListEnd-PrDriverList)/4'	; count
EntryPoint	jmp	(PrDriverList,x)

PrDriverList	dc	a4'PrDefault'
		dc	a4'PrValidate'
		dc	a4'PrStlDialog'
		dc	a4'PrJobDialog'
		dc	a4'PrDriverVer'
		dc	a4'PrOpenDoc'
		dc	a4'PrCloseDoc'
		dc	a4'PrOpenPage'
		dc	a4'PrClosePage'
		dc	a4'PrPicFile'
		dc	a4'InvalidRoutine'
		dc	a4'PrError'
		dc	a4'PrSetError'
		dc	a4'GetDeviceName'
		dc	a4'PrPixelMap'
		dc	a4'PrGetPrinterSpecs'
		dc 	a4'PrGetPgOrientation'
ListEnd	anop


On entry to each routine, the stack looks just as it would for a Toolbox call. There are two RTL addresses, then any parameters, and finally any result spaces. The Print Manager dispatches to printer driver routines without adding any information to the stack, so you can imagine that the Tool Locator dispatches directly to your driver routine when a printer driver call is made.
Your entry code must be reentrant. Because the Print Manager will call some of your routines when you make port driver calls (like GetDeviceName when a port driver is first loaded), be sure you have no reentrancy problems.

The physical structure of printer drivers is the only constant thing about them. You can implement the rest of the driver in any way you choose, using resources, dynamic segments, and even multiple files. When you consider using other components like these, however, keep in mind that loading any of them may require users to insert the boot disk. Even if you make your resources have the preload attribute, most resources used by the system, like window and control templates, are released when the Toolbox is done with them. Marking them preload means the user won't have to insert the disk to use those resources the first time, but once they're released they're very likely to go away. You can get around this by loading the resources yourself and passing them to the Toolbox as handles instead of as resources - in which case preload resources work very well indeed.

THE LOGICAL STRUCTURE 

In addition to the physical structure, there is a standard logical structure that printer drivers should follow so that printing actions are consistent from printer to printer. The driver consists of three functional parts: calls that do the printing loop, routines to maintain and access the print record, and other stuff - the few routines that don't fit either of the other categories.

PRINT LOOP ROUTINES
The printing routines will be called by the application to make printing happen. The application just opens a document, opens some pages, draws, closes the pages and the document, and when PrPicFile is called, printing just kind of happens. The printer driver is what makes it happen.

Although the printing routines are described fairly well in Technical Note #35, the following summary highlights the most important points about using these routines.

PrOpenDoc. PrOpenDoc is the beginning of the regular print loop. This is where you create (if necessary) and initialize the printing GrafPort for the application to draw pages into. You should also make sure to validate the print record, since it contains the settings you must use to image this document. If you want a "Preparing data" dialog box, this is the place to display it. Before you exit PrOpenDoc, you should have allocated most of the resources you'll need to print (memory, disk space, and so on).

PrOpenPage. PrOpenPage is the application's way of telling you "I'm going to draw into this Grafport to image the next page." You get to initialize the Grafport to be ready for printing, including setting the clipping regions to the size of the page rectangle (or the rectangle passed to PrOpenDoc, if there is one), and to make the printing Grafport the current one, saving the old port. If you're printing in immediate mode, you should install your bottleneck procedures in the Grafport here with the QuickDraw II call SetGrafProcs.

PrClosePage. PrClosePage undoes whatever it was that PrOpenPage did. Close the picture for this page here (or eject the page if you are printing in immediate mode). Be sure to restore the old Grafport (from PrOpenPage) before returning.

PrCloseDoc. PrCloseDoc similarly undoes what PrOpenDoc did. If PrOpenDoc allocated a new printing Grafport, PrCloseDoc must dispose of it (after making sure it's closed so you don't orphan any region handles). You should close the printing Grafport with the QuickDraw II call ClosePort.  (It's not a port driver call, no matter what Note #35 says). You should also erase the dialog box you drew in PrOpenDoc, presuming you drew one.

PrPicFile. PrPicFile does nothing if you're in immediate mode, but it does nearly everything if you're in deferred mode. Given the model of recording pages in pictures, the instructions described in Note #35 are pretty good - they lead you through the process one step at a time.

There's one very important part of most printer drivers that's not covered by the note - imaging. The process of turning pixel images into printer codes is so dependent on the target device that neither this article nor the note can tell you how to do it. However, there are a few strategies that apply to all printer drivers:

¥	Doing fewer DrawPicture calls makes printing faster. The best way to do this is to use as large a band buffer as you can. Remember that MaxBlock doesn't reveal how much memory could be available after purging and out-of-memory routines, so just ask the Memory Manager for what you want, and ask for something smaller if you don't get it. Also remember to leave at least 16K available for the Toolbox and GS/OS : don't use all the available memory. See the Apple II Technical Notes for more memory management strategies.
¥	The conversion of pixels to printer codes will occupy most of your driver's executing time, so make it as efficient as possible. You should handle large areas of white space quickly by optimizing your conversion routines to scan for similarly colored areas as fast as possible.
¥	If your target device supports any kind of compaction or data compression, use it. Examples of compression include printer commands to "print this pattern 400 times" instead of sending "print this pattern" 400 times. Tests during Apple's development of IIgs  printer drivers have shown that even on a full page of text, the compression rate is always more than 50 percent.
¥	If you have any control over the port drivers, try to make them as fast as possible. Send data through the port driver in large chunks to let the port driver work as fast as possible. For a 300 dpi target device, there may be as much as one megabyte of data necessary to print all the pixels on an 8 1/2 by 11-inch page. Compaction will help here as well.

The status record is a method the application has of communicating with your printer driver, since printing can take such a long time. The job subrecord contains a pointer to a procedure to be called during idle time - that is, the time between pages, bands, or copies. If you're passed nil for the StatusRecPtr, it's probably easier for you to allocate a status record yourself and update it as if it were provided by the application.

Be sure to dispose of everything you've allocated during printing before leaving PrPicFile. Although the application should make all the print loop calls in order, if an error occurs inside one of the calls (or if the application calls PrSetError), the rest of the print loop must handle it gracefully and still deallocate all allocated resources at the end of PrPicFile. 

PrPixelMap. PrPixelMap takes an arbitrary pixel map and prints it. You're passed a QuickDraw locInfo structure (the pixel map defining portion of a Grafport), a rectangle enclosing the portion of the Grafport to print, and a flag indicating whether to use color. PrPixelMap is a quick and dirty way to print graphics without going through the print loop.

Your imaging code should have a routine to print an arbitrary pixel map anyway, and PrPixelMap can just call it. Alternatively, as suggested by Technical Note #35, you can allocate a new print record, make a picture that contains just the pixel map, and call your normal deferred printing routines.

PRINT RECORD METRICS ROUTINES
The print record metrics routines set and get values in the print record. The print record is the only way your driver can communicate with the application about printing parameters, making it vitally important that the print record be correct. Only you know if the values in the print record make sense, so you get to check it for consistency. You also get to present the most logical option choices to the user, since no one else knows what they are. In addition, there's a new call for System Software 5.0 and later that lets you return the page orientation so that applications don't have to go reading the print record.

PrDefault. This routine copies the default print record into the supplied handle. The default print record's contents will vary depending on the current screen resolution. Be sure not to set the handle size on this handle. Some applications keep extra stuff beyond the end of the record. This isn't kosher, but leaving the print record handle size unchanged is an easy work-around to a potential problem. 

PrValidate. PrValidate checks a supplied print record for consistency. If any of the values are inconsistent or invalid, you should correct them. If the supplied print record isn't a print record from your driver, you should fill it with the default values.

PrStlDialog. PrStlDialog is responsible for the dialog box the user sees after choosing the Page Setup command in the File menu. You should initialize the controls in the dialog box based on the print record and save all the changes from the dialog box in the print record (if the OK button was pressed, of course). 

PrJobDialog. PrJobDialog is responsible for the Print dialog box. As with the Page Setup dialog box, no one but your driver knows the best options and their default choices for your printer. PrJobDialog should initialize the iCopies field in the job subrecord to 1, iFstPage (the first page to be printed) to 1, and iLstPage (the last page to be printed) to the largest value your driver allows. By setting these values, you ensure that one copy of each page is printed if the user does not change these items. That's how the human interface should work.

PrGetPgOrientation. PrGetPgOrientation returns a 0 for portrait (small side on top) mode and a 1 for landscape (sideways) mode. No one cares where you store this in your print record, just return it here. For print records with iDev values $8001 and $8003, you must store this information in the wDev field.

MISCELLANEOUS DRIVER SUPPORT
There are a few routines involving port driver communication, printer identification, and internal functions that you get to provide as well.

PrError. You maintain an internal error code for your printer driver. This is so that if PrOpenDoc returns an error, you can look at the error code and do nothing for the rest of the print loop. PrError simply returns your internal error status.

PrSetError. PrSetError sets your internal error status to the supplied value. This call allows an application to clear an error state if it was able to resolve a specific problem.

GetDeviceName. GetDeviceName is also known as PrChanged - it's called by the Print Manager when your driver is first loaded. This routine takes the AppleTalk Name Binding Protocol or NBP-format name of your target device and passes it to the port driver routine PrDevPrChanged. This allows the network port driver to communicate with your target device over the network. If you don't have a network-compatible target device, pass nil to PrDevPrChanged. An example of an NBP-type name can be found in Picter.

PrDriverVer. PrDriverVer returns your driver's version number, so that applications can scope out your driver for features. If you document features that are available in a given version of your driver, this is how other code can find out if that version is here or not.

PrGetPrinterSpecs. PrGetPrinterSpecs tells the caller things about your driver without forcing any monkeying around with the print record. Your driver gets to return its iDev value identifying the kind of printer or style subrecord and the characteristics of the target device. Currently, the only defined characteristic is whether or not you're color capable. This stuff is defined for all existing iDev drivers, but it's good to keep people out of the print record anyway.

OUR SAMPLE DRIVER, PICTER

Picter is a very simple driver. It creates QuickDraw pictures of all the pages and saves them as picture files in the *:System:Drivers directory. (Picture files have the file type $C1, auxiliary type $0001.) The first file is named screen.a, and the last letter is incremented for each additional file until a pathname syntax error occurs.

Picter does not support many print record options. It prints only in color, portrait mode, full size. Picter has an iDev of $8001, so it interprets the style subrecord as the ImageWriter driver does. If someone sets a bit in the print record to an invalid value, Picter's PrValidate routine corrects it. 
Picter is intended to be a working sample that shows the structure and content of a printer driver. It is a learning tool, not a release-quality utility. No printer driver with this many interface holes should see the light of day as a finished software product.

Picter is written in APW/ORCA assembly and uses the Make utility by 360 Microsystems for source code file management. If you don't have the Make utility, you can look in the make file to see the commands to build each of the components and the link order. 

---- sidebar

ABOUT BEN'S DIALOG BOX ROUTINES

Included with our sample printer driver are some dialog box routines from Ben Koning, the guy behind the new ImageWriter and ImageWriter LQ drivers. Ben has spent a lot of time creating drivers that are more powerful, faster, and easier to maintain so we can add more features in the future. Our thanks to Ben for sharing his routines, which have been slightly modified for use in this driver. If you ever see Ben around, buy him something really expensive - like a house, or a few cars, or a hot dog at the average trade show.

By using these routines, you can easily make your style, job, and status dialog boxes appear like those in Apple's printer drivers. Users will be less confused, everything will seem to fit together better, and the world will be a happier place.

There are two types of dialog boxes in these routines - status dialog boxes and interactive dialog boxes. The status routines make it very easy to keep the user informed during the printing process. There are three status dialog routines - one to display the empty dialog box, one to show a message in this box, and one to close the box:

¥	StartStatusMessage draws a small, blank dialog box centered on the screen, regardless of the mode (320 or 640).
¥	StatusMessage takes a pointer to a Pascal string in a direct-page location and displays that string centered in the status dialog box.
¥	FinishStatusMessage closes the dialog box and removes it from the screen.
Call StartStatusMessage at the beginning of PrPicFile, and every time you do something different, call StatusMessage with a descriptive string. Several descriptive strings are included as examples of what the new ImageWriter driver does. Call FinishStatusMessage before returning to 
the caller.

There are also two other specific dialog routines that our sample driver does not use. StatusMesgFeedPrompt fills the status dialog box with the string "Insert sheet for page: XXXXX", where you pass the page number necessary as an integer on direct page. NotCorrectDevDialog displays a small box with a Cancel button that indicates that this is not your target device.

StatusMesgFeedPrompt must be called between StartStatusMessage and FinishStatusMessage, but NotCorrectDevDialog can be called at any time.

The style and job dialog boxes are largely defined by the controls in them. ConductStyleDialog and ConductJobDialog each have predefined templates linked in as data. This way, you can avoid the disk-insertion problems that resources and dynamic segments entail. The item IDs are equated to match values in the print record. Picter shows how you can write the standard metrics routines to use Ben's dialog box routines. 

---- sidebar

THE WORLD ROUTINES
To ensure that our driver has a consistent environment, Picter includes a few environmental routines around every call and some at the main entry point.
Our entry point is the short, indirect jump, as we saw when we looked at the driver's physical structure earlier. This is acceptable because all of our entry points are in the same segment. Before making the jump, we call the environmental routine MakeOurWorld.

Because there are no printer driver startup and shutdown calls, some people have wondered how printer drivers can obtain direct-page space and release it. MakeOurWorld is a way to do this. It relies on the fact that when printer drivers are unloaded, they are not marked as restartable. Every time the driver is reloaded, we get a fresh copy of the driver from disk. So we link in a storage word of zeros, allocate our direct-page space, and store the address of this space in the zero word. Then on every entry, we just check that word. If it's zero, we were just loaded from disk, so we go get the direct-page space again. If the word isn't zero, it's our direct-page space: transferring it to the direct-page register after saving the current value sets our direct page.

We give the direct-page memory the same user ID as the driver. Thus when our driver is unloaded, the direct-page memory is likewise released. 

If you don't need static direct-page space, by all means don't allocate any. If you use the application's existing stack frame instead of allocating the new direct-page space, you can conserve bank zero space.  However, since allocating direct-page space is a little trickier, a solution is included in MakeOurWorld.

MakeOurWorld returns with the accumulator zero and the carry clear if everything was right. If the accumulator is zero and the carry is set, we were just loaded and our direct page is not initialized. If the accumulator is nonzero and the carry is set, there was a real error.

Immediately in every subroutine, Picter puts the number of bytes of input parameters in the Y register and calls CheckTheWorld. If there was a real error, CheckTheWorld calls EndOurWorld to get out of the printer driver with the error code. If there was no error, CheckTheWorld quickly returns to the caller.

EndOurWorld removes the saved values of the direct-page and data bank registers we pushed on the stack in MakeOurWorld. On entry, X contains an error code or the value $FFFF to indicate the internal error code should not be changed. The Y register contains the number of bytes of input parameters to pull. The routine that removes the input parameters is quite generic and is very similar to those used in the Toolbox's common exit routines.

PICTER'S METRICS ROUTINES
Because Picter is limited in its scope and abilities, its actual printer driver calls function slightly differently than they would in a full-blown printer driver. Here's a description of how Picter implements the standard print record metrics routines.

PrDefault. PrDefault does nothing more than copy a linked-in default print record to the handle passed as input. It then fixes the rPage and rPaper rectangles to match the current screen resolution.

PrValidate. PrValidate examines the print record values Picter knows about to make sure they match the values we support. If they don't, they are modified to be supportable and consistent. 

PrStlDialog. PrStlDialog calls the ConductStyleDialog routine to do the actual Page Setup dialog box. The dialog routines call several very small subroutines in Picter to read the print record values. ConductStyleDialog never accesses the print record itself. This is an example of a method of print record management that I prefer.

PrJobDialog. PrJobDialog is very much like PrStlDialog in that it calls one of the dialog routines to conduct the dialog, and those routines call us for information on the print record.

PrGetPgOrientation. PrGetPgOrientation returns the value for page orientation out of the supplied print record. It reads the values directly, although it could call a metrics subroutine just as easily.

PICTER'S PRINT LOOP ROUTINES
These routines are Picter's implementation of the routines that do the actual printing. 

PrOpenDoc. The actual print loop itself is also slightly unorthodox, due to the nature of the target device (QuickDraw picture files).

PrOpenDoc sets up a printing Grafport, validates the print record, and displays a small status message dialog box. It also initializes other printing parameters, like the internal error and page number variables.

PrOpenDoc stores variables on direct page, making it very bad if the driver were to become unloaded before PrPicFile. Since MakeOurWorld lets us check for this easily, we return a new error if it happens. The error is defined as $13FF and the equate is PrBozo. Any meaning this equate has is the interpretation of the reader.

PrOpenPage. PrOpenPage checks to make sure our direct page is still around and returns PrBozo if not. If all is well, we increment the page number and check the job subrecord to make sure this page is one we're supposed to be printing. If it is, we initialize the printing Grafport to contain rectangular clipping and visible regions the size of the rPage rectangle (or of the supplied frame rectangle, if any). 

We update the status dialog box and call our subroutine OpenPICTFile, which creates the new picture file, opens it, and opens a QuickDraw picture for recording the page images.

PrClosePage. PrClosePage calls ClosePICTFile, which closes the picture, writes it to disk, and kills the picture. We then close the printing Grafport, update the status dialog box, and return. (None of this happens if the driver was just loaded. The caller gets PrBozo instead.)

PrCloseDoc. PrCloseDoc disposes of the memory for the printing Grafport if it was allocated by PrOpenDoc. We restore the old Grafport, close the status dialog box, and exit.

PrPicFile. PrPicFile doesn't really do anything in Picter. We do all our actual "printing" in the page routines, but our job record indicates that we are in deferred mode for compatibility with applications that don't think they print in immediate mode. Nevertheless, Picter shows how to do several of the more common PrPicFile actions, like setting up a status record, allocating and initializing a new Grafport for imaging the pages, calling the idle procedure in the job subrecord, and displaying the status dialog box.

PICTER'S MISCELLANEOUS ROUTINES
These routines are Picter's implementation of the routines that make your driver complete. They allow your driver to respond to requests for error, network, and version information.

PrReserved. PrReserved is the name we picked for what Note #35 calls InvalidRoutine.  It is, in fact, the remnants of an old Print Manager architecture call named PrControl. This had varying parameters and was generally not Your Friend. To be safe here, we return error $0002, which as a Tool Locator error indicates to the caller that he should pull his parameters back off the stack.

PrError and PrSetError. PrError returns the value in our internal direct-page error location. PrSetError takes the value and puts it in our error location on direct page.

GetDeviceName (PrChanged). GetDeviceName really has no meaning for us, since our target device doesn't (and can't) exist on an AppleTalk network, but an NBP-type string is included anyway to demonstrate the technique. This will cause the network port driver to report that no devices of our type are available.

PrDriverVer. PrDriverVer returns the version word for our driver. You might want to stop in the middle of writing your PrPicFile call to write PrError, PrSetError, and PrDriverVer just to remind yourself that it's not always that hard.

PrGetPrinterSpecs. PrGetPrinterSpecs returns our iDev word and the color capabilities of this printer (picture files are always in color). If you need to check your target device's capabilities (for example, an ImageWriter doesn't always have a color ribbon in it), this is the place to do it.

WHAT YOU CAN ADD
Picter is intended as a workbook, a shell from which you can learn printer driver technique. There are many more things you can do with it before starting your own printer driver. By examining these areas now - before you actually try to implement them in a driver - you will avoid future frustration.

More picture types. Picter writes only QuickDraw picture files as supplied. You could add a pop-up "Picture type" menu to the job dialog box and allow the user to pick any of the popular graphics formats. Apple Preferred is a good choice because its line-oriented structure makes it a good candidate for banding. Banding will be necessary unless you have a pixel map large enough to hold the entire image at once. Other easy additions are packed QuickDraw pictures and 32K screen dumps (if you can get a 32K block for the pixel map). Remember that screen files aren't 32K of pixels - they're 32,000 bytes of pixels and 768 bytes of scan-line control bytes and color tables.

More page types. As supplied, Picter only supports two types of page metrics - screen size and U.S. letter size. Try adding more sizes (legal, label, envelope). The code to handle different page metrics is directly applicable to any other printer driver. In fact, you could add line edit controls to let the user type the size of the page rectangle in inches or centimeters and thus have no limit to the number of paper sizes you support.

Communicating with the port driver. Picter doesn't communicate with the port driver (except in GetDeviceName). Try writing the name of each call to the port driver as it executes. If you have an ASCII printer connected to the hardware controlled by the port driver, you should get a hard copy of each call name as it executes. You could also write debugging information this way, such as parameters or print record addresses.

More options. You can also add more standard print record options - such as condensed and landscape modes - to Picter. Supporting landscape mode involves swapping the horizontal and vertical coordinates of the rPage and rPaper rectangles as well as the horizontal and vertical printer resolutions - just be sure your validation routines know how to deal with it! You can make vertical condensed mode happen by passing a rectangle that is half the correct height of the framing rectangle for OpenPicture. Other reduction values, both horizontal and vertical, come by changing the framing rectangle for DrawPicture as well.

GO FORTH AND IMAGE

Printing doesn't have to be a big mystery. The task is divided into components so that no one part of it becomes insurmountable. Turning imaging into printer codes is the responsibility of the printer driver, talking to the hardware is the responsibility of the port driver, and the Print Manager holds it all together. While supporting different printers and interfaces would normally be beyond the scope of most applications, the Apple IIgs  printing architecture makes it easy for applications. All you need is a printer driver - and now you know how to create those as well.

----
*** photo ***

MATT DEATHERAGE  used to think he was a cynic, but two and a half years in Developer Technical Support for the Apple II has made him doubt even that.  His perpetual quest for sleep has been interrupted by his new responsibility for the ProDOS partition on the Developer CD and Developer Essentials disc, as well as by his resuming the role of DTS technical lead for Apple IIgs system software.  It would be enough to make his head spin, he says, "if my head were jointed that way."  His musical pursuits continue with work on an album, The Fruited Computer Follies of 1990, which will never be released as all of the songs on it are entirely unsuitable for polite company.  He's currently conspiring with Robert Thurman to withhold the definition of "PPG." ¥


----
For More Information
Apple IIgsÊToolbox Reference, Volumes 1Ð3
Apple IIgs Technical Note #34, Low-Level QuickDraw II Routines 
Apple IIgs Technical Note #35, Printer Driver Specifications
Apple IIgs Technical Note #36, Port Driver Specifications
Apple IIgs Technical Note #51, How to Avoid Running Out of Memory
Apple IIgs Technical Note #72, QuickDraw II Quirks
Apple IIgs Technical Note #93, Compatible Printing ¥

----
Thanks to Our Technical Reviewers
Pete "Luke" Alexander, Ben Koning, Suki Lee, 
Jim Luther, and Dave Lyons ¥


















































