TweetFollow Us on Twitter

Printer Resource 2
Volume Number:3
Issue Number:12
Column Tag:Advanced Mac'ing

Printer Resource File, Part II

By Earle Horton, Dartmouth College, Hanover, NH

Part 3: The dialogs, or Communicating with an application

The routines which handle the Style (“Page Setup ”) and Job (“Print ”) dialogs are contained in a resource of type ‘PDEF’, ID 4. These routines communicate user choices to the application via a data structure known as the “Print Record”, which is always accessed by its Handle. The dialog routines also provide to the application the ability to modify the dialogs, and insert its own event filters and item handlers. Because of this, the structure of the dialog handling routines is quite rigid. In order to understand the necessity for this way of doing things, it will be necessary to obtain a copy of Macintosh Technical Note #95: “How to add items to the print dialogs.” This Technical Note provides an excellent description of the workings of the print dialog code, and it is not my intention to summarize it here. It would also be wise to print out the header file which defines the Print Record data structure for your development system. This part of the Printer Resource File is quite possibly more complicated and harder to understand than even the printing code.

Fig. 2 Our Driver has a set-up option!

The dialog handling code starts off with the following header:

 bra.w  PrintDefault
 bra.w  PrStlDialog
 bra.w  PrJobDialog
 bra.w  PrStlInit
 bra.w  PrJobInit
 bra.w  PrDlgMain
 bra.w  PrValidate
 bra.w  PrJobMerge

This code provides several levels of support for applications, and we must be careful to allow each application to use it in the way it is accustomed to. Each level of support implies certain information which is supplied by the Printer Resource File, and other information which the application wants to fill in itself. If an application calls PrintDefault, then it wants us to fill in all the fields of the Print Record passed to PrintDefault with suitable default values for the printer. (Note to C programmers who cut their teeth on UNIX: Print Records are accessed by the Handle; get used to accessing objects by the Handle RIGHT NOW.) An application may call PrintDefault, and no other routines in this overlay. For this reason, the default print record, contained in ‘PREC’ 0, should provide a reasonable page format, and all the fields should have values which imply behavior which the user expects from, say, the ImageWriter driver. My default Print Record is similar to that used by the ImageWriter, but I have set all margins to the edge of the page. Study the Print Record data structure, and those portions of Inside Macintosh which tell application programmers how to use it.

Assume that the Print Record is contained in a data structure named “uPrint”. uPrint.iPrVersion is equal to 3, the current version of the Printing Manager. uPrint.prInfo.iDev contains -3 in its high-order byte, and a device-specific byte in its low-order byte. I use the low-order byte to keep track of whether the character pitch is 10, 12, or 15 characters per inch. The ImageWriter and LaserWriter drivers use this field for resolution information. My driver will be called by the font manager with this byte, and my text drawing code will use the value to determine where to place text in its output buffer. uPrint.prInfo.iVRes and uPrint.prInfo.iHRes provide the printer’s vertical and horizontal resolution, in units of pixels per inch. I provide values here which are close to those used by the ImageWriter and the standard Macintosh screen, thus insuring that font choices made by the Font Manager will be similar to what my printing code expects. uPrint.prInfo.rPage is the “Page Rectangle” of the specialized GrafPort used by applications in printing, in units of pixels per inch. uPrint.rPaper is the “Paper Rectangle” and encloses the Page Rectangle.

If you want to provide applications with maximum flexibility in choosing margins, then set uPrint.prInfo.rPage equal to uPrint.rPaper. Remember that the lengths of the sides of uPrint.rPaper, divided by the appropriate resolution, should equal the physical paper size, so that the application knows how big the paper really is. The conventional way to do this is to inset uPrint.prInfo.rPage inside uPrint.rPaper, thus providing default margins. In this case, the origin of the coordinate system is the top left corner of uPrint.prInfo.rPage, and the top left corner of uPrint.rPaper has negative coordinates. An application may attempt to print outside of uPrint.prInfo.rPage, but never outside of uPrint.rPaper. If you set up these fields with margins, you should probably print in the margins when requested, and if physically possible. Attempts to print outside the page rectangle should, of course, be ignored.

The prStl sub-record contains four types of information. Why these four should be grouped here, I don’t know. uPrint.prStl.wDev is private, you may put whatever you want here. The next two fields, uPrint.prStl.iPage[VH], give the physical dimensions of the paper, in units of 1/120 inch. Make sure that these agree with the page rectangle, or something strange may happen. uPrint.prStl.bPort contains which serial port to use. I do not use this field, but rather obtain this information from my Chooser device interface. The next field is uPrint.prStl.TFeed, and indicates the paper field mechanism. That this is in the style sub-record makes little sense, since the information is set from the Job dialog. It may be in here for historical reasons, and it is certainly too late to change it now.

uPrint.prInfoPT is a copy of uPrint.prInfo; structure assignment is a convenient way to copy one onto the other. uPrint.prXInfo contains information used in spool printing, and you can either put in reasonable values or zeroes here if you don’t do spool printing (this example does not do spool printing). uPrint.prJob contains information particular to a particular print job. uPrint.prJob.iFstPage and uPrint.prJob.iLstPage give the first and last pages to print. The printing code is responsible for keeping track of which pages get printed in draft printing, and not the application. uPrint.prJob.iCopies is the responsibility of the application; if the user wants multiple copies of the page, then the application must print them. uPrint.prJob.bJDocLoop is always bDraftLoop, since we only support draft printing. uPrint.prJob.pIdleProc is a ProcPtr to a background procedure to run during printing. The default value is ‘nil’, and the application may put its own ProcPtr in here. Usually this procedure just checks for a user abort, although it can do anything except use the Print Manager (thankfully, we don’t have to make the Print Manager reentrant!).

The rest of the fields of the Print Record are either for spool printing or are private. If you need to store extra information here, then use the uPrint.printX array. The only private field I have found it necessary to use is uPrint.prInfo.iDev, although you may need to use more.

Fig. 3 Our Set-Up dialog sets baud rate, control chars

When an application calls PrValidate, it has a completely filled in Print Record obtained from either PrintDefault, the dialog handling routines, or perhaps fabricated by the application itself. Check the Print Record here to see that the resolution is correct, and verify that the page size can be handled by the printing code. If the Print Record cannot be used, use PrintDefault to convert it to the default values, and return TRUE. Otherwise, do nothing and return FALSE.

PrJobMerge is used when it is desired to copy fields of from Print Record to another, such as when printing multiple files. Copy the job sub-record and update the printer information, band information, and paper rectangle in the destination print record. These instructions are from Inside Macintosh and are admittedly somewhat vague, since I don’t really know what “update” means here. I just copy the stuff which requires updating from the source Print Record.

Read Technical Note #95 at this point to see how the dialog routines work. There is not much room for orginality in either PrDlgMain, PrStlDialog, or PrJobDialog. The dialog initialization routines fetch the appropriate dialog template from the Printer Resource File, usually with GetNewDialog, and set the initial values of radio buttons, check boxes, and the like. The dialogs are not actually drawn until PrDlgMain. Keep in mind that the application may be modifying the dialogs after your dialog init routine, then passing the pointer to the modified dialog to PrDlgMain. This means that your dialog event filter and item handler may be called after routines supplied by the application. The Print Record associated with the TPrDlg dialog object is not modified unless the “Done” or “Ok” button is hit (item number 1 in the dialog). The fDoIt and fDone fields signal to PrDlgMain whether the Print Record should be validated or not, and whether the cancel or finished button has been hit.

My style dialog contains three radio buttons for printer pitch, and two check boxes for portrait and landscape mode. The item handler sets the appropriate control values when one of these is hit, and records the user choices only if the Ok button is hit. It gets information about which values to put in the print record from the control settings. This code only supports 8 1/2" by 11" paper, in one of two orientations. If you want to provide more paper sizes, and perhaps controls to set the margins, then provide more dialog items and a place for the item handler to get the other settings. Several paper sizes could be provided merely by having several Print Records stored in the Printer Resource file as ‘PREC’ resources. Remember to fill in only the fields having to do with paper size, orientation, and page size when doing the style dialog. The device field can also be set here, since it is a private field. After the item handler fills in these fields, the application’s item handler, if used, gets a shot at the dialog object and the Print Record, then PrDlgMain cleans things up and disposes of the dialog object.

The job dialog is handled in identical fashion to the style dialog. The item handler fills in the job record from the user’s choices. The job dialog, in comparison to the style dialog, is pretty well standardized. Provide a mechanism whereby the user can specify multiple copies and a page range, as well as sheet or fanfold feed. After filling in the appropriate fields in either the style or job dialogs, set the value of the fDone and if necessary the fDoIt fields of the Print Dialog object, and return. PrDlgMain will validate the Print Record if fDoIt is set, and return the value of fDoIt when done. This is how the application knows whether the Print Record has been selected by the user and, in the case of the job dialog, whether to proceed with printing.

The standard print dialogs are not a good place from which to obtain information which is not part of the regular ImageWriter or LaserWriter dialogs, since the application is not required to use them. If you need information which is particular to only your printer driver, then use the Chooser device interface instead.

Fig. 4 Our Print Setup dialog box

Part 4: The high-level printing code

This is the part everybody has been waiting for, I’m sure of it! How to translate QuickDraw drawing commands into the printed word (or even pretty pictures). The code found in the ‘PDEF’ resources 0, 1, 2, and 3 does exactly this. Each one of these resources starts with an offset table to the four externally callable routines.

 bra.w PrOpenDoc
 bra.w PrCloseDoc
 bra.w PrOpenPage
 bra.w PrClosePage

Applications use these routines to print a document in the following manner:

a) Call PrOpenDoc to get a printing GrafPort to draw in.

b) For each page in the document:

i) Call PrOpenPage to reinitialize the port.

ii) Draw in the printing GrafPort, using ordinary QuickDraw commands.

iii) Call PrClosePage to signal that the page is done.

c) Call PrCloseDoc to dispose of the printing GrafPort.

PrOpenDoc is just like any of the routines which create a new GrafPort for the application to draw in: NewWindow, for example. The port is customized for printing, however, by means of ProcPtrs which point to our drawing routines. In addition, we get four long integers to play with as we see fit. My printing code works in the following fashion: Attempts to draw text in the printing port results in the text being stored in a big rectangular array of characters. When a page is completed, the application signals this to the printing code, and the text is sent to the printer, all at once. I do this because it is the easiest way for me to do it, it avoids keeping track of the print head, and it allows me to emulate a reverse line feed. Think about it: an application may print some text, then attempt to move up the paper, rather than down. If I keep all the text in an array, then I don’t have to figure out how to reverse the direction of paper travel.

The way I do things in this code depends to some extent on the demands imposed by this method of printing. This method is fine for text, but I can think of one disadvantage when you (or I) want to do graphics. Memory on a Macintosh is finite. We cannot expect to print in high resolution on a dot matrix printer by first buffering up a bitmap of the whole page, especially when the Macintosh gets a real multi-process capability operating system. In order to handle graphics properly, one has to develop the routines to do either draft mode printing or spooling. It’s a good thing for me I don’t have to come up with them for this article! Either true draft mode or spool printing will have some similarities to the printing method I employ here, but both will require more bookkeeping. To summarize, my printing code is a good start, and is good for text, but can only go so far.

Fig. 5 Print Dialog

Central to printing is a data structure known as a TPrPort. This is merely a GrafPort, with a QDProcs structure, four longs, and a pair of Booleans added. A TPrPort is accessed by pointer, or TPPrPort. An application obtains a TPrPort to draw in by calling our PrOpenDoc routine. It must pass to PrOpenDoc a valid Print Record, and may pass us one or two storage pointers. If we get a pointer to storage to use for the printing port, then we fill out the TPrPort using the application’s storage, and flag that we did so in the fOurPtr field. Otherwise, we obtain a pointer to the required storage, and begin to initialize the printing port. This is a fairly complicated process, and I only handle text! I also obtain storage for my output buffer, and store the pointer in the lGParam4 private field of the printing port. The process is given, step by step, below.

First, obtain from NewPtr sufficient storage for the text output buffer and the printing port. If storage is not available, stuff the constant iMemFullErr into the low memory global PrintErr and return ‘nil’. A side note may be appropriate here. PrintErr is the integer located at 0x0944. According to some early documentation I have, PrintErr is only part of “PrintVars: 10 print code variables [16 bytes]” stored starting at this location. Presumably, if you are writing a printer driver, then you own all 16 bytes. I am reluctant to use any of these, however, until I find out how Apple uses them. PrintErr is the only variable in PrintVars I can find documentation for. Accordingly, the only use I make of PrintVars is to check PrintErr for user abort, and to stuff it with an error code if I have to. I don’t need low memory globals, however, since I can share storage with my driver. The function DrvrStorage returns a pointer to the printer driver’s private storage, and I access the driver variables from within my printing code. Programmer’s advice: don’t use low-memory globals unless you are given no other choice, and you really know what they are for!

After obtaining a pointer to the driver’s private storage, stash some useful information there for later use. A copy of the entire Print Record should be enough, but I also take this opportunity to calculate the number of lines per page, and set a page number counter to 1. Initialize here any information you want to save that is asociated with a particular printing job. At this point, we are supposed to save a copy of the current print record in ‘PREC’ #1 in our resource file. Why, I don’t know. Presumably, either our printing code or applications will benefit from having available a copy of the last-used Print Record. I certainly don’t use it.

Now we open and configure the printing port. The printing port is opened like any other GrafPort, just call OpenPort with the pointer to it. Now we customize it for printing. Whenever possible, use ToolBox calls to manipulate the fields of the printing port, rather than storing into them directly. It’s easier, and is the recommended method. The TPrPort contains a sub-record, gProc, of type QDProcs:

typedef struct{
 QDPtr  textProc,/* Draw text. */
 QDPtr  lineProc,/* Draw a line. */
 QDPtr  rectProc,/* Draw a rectangle. */
 QDPtr  rRectProc, /* Draw rounded-corner rect.*/
 QDPtr  ovalProc,/* Draw an oval. */
 QDPtr  arcProc, /* Draw an arc. */
 QDPtr  polyProc,/* Draw a polygon. */
 QDPtr  rgnProc, /* Draw a region. */
 QDPtr  bitsProc,/* Bit-transfer procedure. */
 QDPtr  commentProc, /* Handle picture comments. */
 QDPtr  txMeasProc,/* Return the width of text. */
 QDPtr  getPicProc,/* Get info from QD pict. */
 QDPtr  putPicProc /* Stash info in picture. */
}QDProcs,*QDProcsPtr;

All drawing routines which affect a GrafPort are funnelled through one of these thirteen routines! First, call SetStdProcs, to fill in the gProcs field of the TPrPort with the default QuickDraw primitives. Then, install ProcPtrs to point to the QuickDraw calls you implement. (If we are only drawing text, we must use SetStdProcs to fill in the ProcPtrs we do not supply, if only to keep track of the Pen location for us.) I now fill in the textProc and txMeasProc fields of gProcs with pointers to my text drawing and text measuring routines. Next, the grafProcs field of the printing port’s GrafPort is made to point to the gProcs field of the printing port. The device field of the GrafPort is filled in with the iDev field of the Print Record. This will be used later by our driver when it is called by the Font Manager.

Set the portBits.bounds rectangle of the printing port to an empty rectangle, so QuickDraw routines will not try to set bits in it. (If you are doing graphics printing, it is up to you how the port’s bitmap is handled.) Call SetPort to make the printing port the current port. Set the portRect to be equal to the page rect from the printing record, using PortSize. Using MovePortTo, translate the printing port’s coordinate system so that the upper left corner of the paper is the global origin, if you desire to allow printing on the entire page. We will use LocalToGlobal later to find the correct location to place text. Do this even if you consider the page rectangle to be equal to the paper rectangle, since an application may create its own Print Record. The application’s page rectangle and paper rectangle might be anything which we allow in PrValidate, and are not limited to values which we supply in the PDEF 4 routines.

Issue a reset command to the printer driver. Initialize whatever variables you need to maintain throughout a printing job. If you use a buffer, clear it out. Save a copy of the Print Record in resource ‘PREC’ #1, in case anybody is still interested. If the application hasn’t installed a pointer to an idle procedure, install one of your own. (The default idle procedure checks for command-’.’ and aborts printing if it finds one.) Return the pointer to the printing port to the application when all tasks are complete.

The QuickDraw primitives which I replace in the printing port are StdText and StdTextMeas. All QuickDraw text drawing routines are glue to call StdText. Most, but not all, routines which determine the width of text call StdTextMeas. These routines work in my printing port as follows:

When MyStdText is called, it first calls GetPen to get the current QuickDraw Pen location. The device field of the printing port is used to find the width of a character. Since I only support mono-spaced fonts, I have but three values for the width of a character. I call LocalToGlobal to translate the pen location from local (page) to global (paper) coordinates. Characters which are to be printed are stored in a big array of lines, and the global pen location is used to determine the starting array index of where to put them. Once the proper line and offset is found, a call to BlockMove stashes the text for later printing. Finally, a call to Move is used to update the pen location by the proper horizontal offset. Note: I have “drawn” text, therefore I have moved the QuickDraw pen. The total horizontal offset depends upon the character pitch, as determined in the page setup dialog.

When MyStdTextMeas is called, I am being asked “How wide is this chunk of text?” I am passed a pointer to a text buffer, an integer telling how many characters will be drawn, a pointer to a FontInfo record, and two scaling Points. The idea here is that I now get an opportunity to modify the font information if my printer cannot supply exactly what is asked for. I change to font information to reflect a constant size font, and modify the two scaling points according to what is in the GrafPort device field. Numer.h over denom.h gives the horizontal scaling, and numer.v over denom.v gives the vertical scaling. I return the width times the number of characters. The width depends upon the character pitch, as in MyStdText. If your printer has a proportional font, then you will have to be considerably more sophisticated here, and in your StdText routine, too.

You may install as many QuickDraw primitives in the gProcs field of your printing GrafPort as your printer, and your programming abilities, can handle. If you want to attempt graphics printing on a dot matrix printer, then most of the routines you install will be involved with translating some QuickDraw command into a bitmap, presumably as high as your print head and as wide as your paper. Then you will have to store the bitmap somewhere, or print it directly. The standard ImageWriter driver provides two methods of doing this. In draft mode printing, bitmaps are printed immediately. In spool printing, all QuickDraw calls between PrOpenPage and PrClosePage are stored in a QuickDraw picture, which is kept in a special file for this purpose. A number of pages are buffered up in the Print File, then the routine PrPicFile is called to print them out. There is nothing mysterious about this, but the translation process from QuickDraw calls to dot-matrix printer commands may mean some work!

I recommend starting with the simplest method of graphics printing, then working up. If you want to implement graphics printing on your printer, then make the “command-shift-4” routine in the driver work first. Get a copy of the QuickDraw manual and a copy of the Printer manual, go to a quiet place, and work out the tranlsation algorithm from screenBits to paper. The feeling of accomplishment you get will be a tremendous boost!

PrOpenPage is called to clear out the printing port and make it ready for the next page. All variables associated with your printing port should be returned to the initial state. Anything appropriate for the printer at top of page should be done here. PrOpenPage also makes the printing port the current port, although not all applications need this feature.

PrClosePage is called when the application is finished with printing the current page in the document. My method of printing waits until PrClosePage is called, then prints out the whole page at once. This method is appropriate for text-only printing, but may not be for other methods, especially if memory is tight. Between PrOpenPage and PrClosePage, I store text in a dynamically allocated array of structures called “line”s. I use an integer to flag whether the line actually has text in it, and a character array to hold the text. If I handle up to 66 lines on a page, and 164 characters in a line, then it takes my code 11 kilobytes to hold the array of lines. If we wish to handle multiple styles (italic, underline, etc.) then we could modify the line structure to hold style information as well without increasing the size of the array of lines very much. For graphics printing, this method is probably not appropriate because of the large amount of memory that would be needed to hold bitmaps.

Here is the algorithm used in MyPrClosePage to actually print the text. I obtain a pointer to the driver’s global storage, where I have stashed information about the current print job’s parameters. From the prJob.iFstPage and prJob.iLstPage fields, I determine whether the current page is within the range of lines which get printed. If the current page is before the first page to be printed, MyPrClosePage returns without doing anything except incrementing the page counter. If current page is after the last page, I stuff an error code in PrintErr. (I don’t know whether this is the standard way to stop printing at this point, but it seems to work.) Then I cause the user’s “end-of-file” string to be sent to the printer. After checking the page range, I begin the process of printing out the lines in the output buffer.

Actual printing is implemented by calling the driver’s control routine with a csCode = iPrIOCtl, lParam1 = the pointer to the base of the current line’s text array, and lParam2 = the number of bytes to print. End of line is handled by calling the driver’s control routine with the appropriate code for end of line. For each line in the buffer:

Call the pIdleProc procedure of the current print record.
If PrintErr is equal to iPrAbort then return.
Else
 Check the flag to see if the line has text in it.
 Find out how many characters (index of last non-space         
 character +1)
 Call the driver to print the text.
 If at end of page, then
 Call the driver to do a form feed.
 Else
 Call the driver to advance the paper.

Calling the driver’s control routine to print the text is only one method. It is also possible to send the text to the serial driver directly from the printing code, or use any other method you can think of here.

PrCloseDoc is used to dispose of a printing port, and to signal to the printing code that the current print job is finished. Any storage which has been allocated for printing is disposed of here, and the port is closed. The TPrPort must be closed prior to disposing of the port, or the memory allocated by the visRgn and clipRgn will not be reclaimed. If the printing port storage was allocated by the printing code, dispose of it here. Don’t dispose of storage which belongs to the application!

Part 5: PrPicFile

The PDEF 5 overlay contains the routine PrPicFile. The file “PDEF5.c” merely shows the proper header to use, and the parameters passed to this routine. When spool printing, the printing code stores all drawing commands passed to it in a disk file. The routine PrPicFile is called by the application after the print job has been stashed away in the picture file. PrPicFile then prints the file. Recommended procedure is to swap as much of the application out of RAM as possible, then call PrPicFile. This means that if you choose to implement spool printing, then you can use lots and lots of RAM when you are actually printing. You may need lots and lots of RAM to translate a QuickDraw picture to a nice looking dot matrix output page.

I am glad I didn’t have to implement spool printing for the purposes of this article, but I certainly wish everyone the best of luck.

Part 6: Installation, or Talking to the user

The Chooser desk accessory provides a standardized interface for new device drivers to obtain configuration information from the user. This means there is no need to provide a configuration program in order to be able to handle multiple device types. When a device icon is selected in the Chooser dialog, the Chooser looks in the device resource file for a resource of type ‘PACK’, ID -4096. This resource has the “standard” header for stand alone code resources as implemented by LightspeedC and as documented in the Device Manager chapter of Inside Macintosh Volume 4. That chapter is recommended reading for what follows.

The format of the ‘PACK’ resource header is as follows:

Offset (hex) Word

0 short branch to offset 0x10

2 Device ID (word)

4 ‘PACK’ (long word)

8 0xF000 (-4096)

A Version word

C Flags (long word)

10 Start of code

LightspeedC formats this resource header correctly, but provides no way to set the version and flags. A separate utility program is provided to show how to set these. My resource has a version number of 1, and uses the Chooser right button. I do not use the List Manager here, but provide a string for the Chooser to label the list when choosing my device file. When my icon is selected in the Chooser, this string appears as a label to tell the user that more configuration options are possible. Setting the flags to the proper value tells the Chooser that I will use the right button, and ‘STR ‘ number -4092 gives it a button title to use: “Setup ”. The Chooser communicates with my ‘PACK’ resource as if it were the following Pascal function:

FUNCTION Device(message,caller: INTEGER;     objName,zoneName: StringPtr; 
p1,p2: LONGINT) :  OSErr;

I declare the function in C as:

pascal OSErr main(message,caller,objname, zonename,p1,p2)
intmessage,caller;
StringPtr objname,zonename;
long    p1,p2;

The only field which is of interest to me here is the integer message, and only when that is equal to the constant buttonMsg (19). When called with the button message, the ‘PACK’ resource puts up a dialog box, and gives the user a set of configuration options. The baud rate can be set here, as can flow control. I provide five editText items for the user to enter printer control strings. Most, but not all, printers will produce a carriage return and then a line feed when they receive the string: “\r\n” or “^M^J” or a decimal 13 followed by a decimal 10. Using the second notation, I let the user edit these strings so that his printer can be used. For example, with my Tandy DMP-110, one uses “^M” for end-of-line, “^L” for end-of-page, and there are several choices for the string for initializing the printer. I also provide at this time strings for top-of-page and end-of-job, although I usually don’t use either with my printers.

Another way to handle this would be to provide several configuration files, with the same creator type as your Printer Resource File. The Chooser maintains a list box for use by device packages; you have probably seen it in use if you have chosen a LaserWriter or used AppleShare. The list box could be used to select between configuration files in the same manner that the LaserWriter driver selects between devices on the AppleTalk network. Details of how to do this can be found in IM4.

When my device package is called, I get the user’s choices via ModalDialog, and store the results in the printer resource file if the “Save” button is hit. The Printer Resource File is always the last-opened resource file when the package is called by the Chooser, so my configuration resources are always right at hand. I use an array of integers to store configuration information, and a string list to store the printer control strings. When changing the string list, make sure to resize the Handle to the resource in case the user has entered longer or shorter strings than were there before.

There is one problem here. When the device package is chosen, it may be because the user wants to change some of my configurations, and not to change printers at all. My driver will not load a new set of settings, however, until its open routine is called. This may cause some delay in realizing the effects of changing the settings from the Chooser, particularly if an application calls PrOpen only once, which many older applications do.

Part 7: Nuts and bolts, or Putting it all together

The Daisy printer Resource File requires six LightspeedC project files, seven resource files in addition to the Printer File, and ten source files to create. Because of the environment used by LightspeedC, there is no Makefile, so you will have to follow instructions here in order to get it right. (This task will be much easier with a “batch” oriented development system.)

Files used to create the Daisy Printer Resource File: (All files are contained on disk “src” and in folder “Printer”, available on the source code disk for this issue of MacTutor. See the MacTutor Mail Order Store page for details on obtaining this disk.) See Table 1.

Note: You must obtain the SysEnvirons glue and header file for your development system, also.

LightspeedC is an excellent choice for a development system, but there are things which it will not do. It will not allow you to produce the correct headers for the PDEF resources, for instance, because it uses a standard code resource header. I get around this limitation by hacking the code resources after LightspeedC is finished with them. Prior to the offset table at the beginning of each of these resources, I put an illegal instruction (0x4afc). A utility program I wrote reads in the code resource, strips off everything up to and including the illegal instruction, and writes out the modified code resource to its file. Doing things this way imposes certain restrictions on what you put in the code. Specifically, you cannot have anything in the PDEF code which will cause LightspeedC to insert code in front of your main() function. Things I know of which will cause this to happen are:

Linking libraries before the main code.

Switch statements.

The PDEF resources must not have switch statements when using LightspeedC. Any libraries used in them must have names which are lexically greater than your printing code. Name the printing code source file “AAAwhatever.c” to accomplish this, or perhaps name the library file “ZZZlib.lib”. These restrictions of course do not apply to other development systems, which may, however, impose other ones.

The PACK resource and the DRVR both require flags to be set in their headers. The PACK resource must have flags to tell the Chooser what its capabilities are, and the DRVR must respond to the correct subset of driver calls. My utility program sets the flags correctly for the PACK resource and the DRVR.

What follows is a detailed description of how to make the printer resource file using LightspeedC and RMaker. Too bad there is no “make” utility for LightspeedC! Instructions for other development systems may be more or less similar to these. Change to suit your disk/machine/development system configuration.

File name Type Creator What

LightspeedC “Project” files:

DRVR_proj PROJ KAHL Driver project

PACK_proj PROJ KAHL Chooser interface project

PDEF0__proj PROJ KAHL Draft printing code project

PDEF4_proj PROJ KAHL Dialog code project

PDEF5_proj PROJ KAHL PrPicFile project

UTILS_proj PROJ KAHL Utility programs project

resource files:

DaisyDRVR ???? ???? ‘DRVR’ -8192, ‘DATA’ -16320

DaisyPACK ???? ???? ‘PACK’ -4096

DaisyPDEF0 ???? ???? ‘pdef’ 0 -> ‘PDEF’ 0

DaisyPDEF4 ???? ???? ‘pdef’ 4 -> ‘PDEF’ 4

DaisyPDEF5 ???? ???? ‘pdef’ 5 -> ‘PDEF’ 5

DaisyPREC0 ???? ???? ‘PREC’ 0

dialogs.rsrc ???? ???? resources created by RMaker

Daisy ???? ???? The final actual printer resource file!

text files:

Daisy.r TEXT KAHL RMaker source to put it all together

dialogs.r TEXT KAHL RMaker source to resources created by RMaker

mkDefault.c TEXT KAHL C code to create default Print Record

PACK.c TEXT KAHL Chooser device interface code

PDEF0.c TEXT KAHL Draft printing code

PDEF4.c TEXT KAHL Dialog code

PDEF5.c TEXT KAHL PrPicFile stub code

prglobals.h TEXT KAHL Daisy header file, #included in all sources

Utils.c TEXT KAHL Utility program to reformat code headers

XPrint.c TEXT KAHL Driver code

Application files:

Code Fixer compiled Utils project

Fig. 6 All the files required to build a printer resource file

Obtain a 512ke or better Mac. Create a system disk named “bin”, with LightspeedC and a folder of “#include” files on it. Call the system folder “sys”. Create another disk named “src” with a folder named “Printer” on it. Put the printer driver sources in this folder. Boot from “bin” and put “src” in the other disk drive.

Make a project for the driver. Set the project type to “Device Driver”, number 2, name “.XPrint”. Use the “Add ” item under the “Source” menu to add “XPrint.c” and “Environs.lib”. When you choose the “Build Device Driver” menu item, LightspeedC will create a file which contains ‘DRVR’ #2 and ‘DATA’ #-16320. The RMaker command file converts the driver to ‘DRVR’ #-8192, named “.XPrint” before installing it in the Printer Resource File. The ‘DATA’ resource is converted to ‘PREC’ #-8192. (The driver file created by LightspeedC is to be placed in the folder “src:Printer” if you wish to use the RMaker command file and utility program without modification.) The driver file is named “DaisyDRVR”.

Make a project for the Chooser device interface. Project type is “code resource”, type ‘PACK’, ID -4096. Add the “PACK.c” source to it and create the code resource, placing it in the file “DaisyPACK”.

Make a project for each of the ‘PDEF’ resources. Set the project type to “code resource”, type ‘pdef’, ID number from the source file name. (The utility program converts ‘pdef’ to ‘PDEF’ when it reformats the code.) Create the three resource files shown. Example: PDEF0.c is used in PDEF0_proj to create DaisyPDEF0, containing ‘pdef’ 0. Later, the utility program reformats the header and converts it to ‘PDEF’ 0. And so on. (Please, Father, buy me a hard disk and MPW C.)

Create an empty resource file in the “Printer” folder named DaisyPREC0. You can use the LightspeedC editor to create an empty text file for this purpose, since LightspeedC text files have resource forks. Just create the text file, and set the tabs in it, so LightspeedC creates an ‘ETAB’ resource. Alternately, use RedEdit. The utility program puts the default Print Record in this file, but does not have the ability to create the file by itself.

Create a LightspeedC project file for the utility program, “Utils.c”. Type is Application, name is whatever you like. Add the source file “Utils.c” and select “Run ” from the “Project” menu. There is no need to create the application, unless you want to use it under MultiFinder. When you run Utils.c, it will set all the flags properly in the ‘DRVR’ and ‘PACK’ resources, reformat the ‘PDEF’ code resources, and create a default Print Record, ‘PREC’ 0.

Run RMaker. Compile “dialogs.r” to create the dialogs, strings, string list, and private resource types. RMaker will create “src:Printer:dialogs.rsrc” at this point. Compile “Daisy.r”. RMaker gathers together all the resources for the Printer Resource File, and creates “bin:sys:Daisy”. Edit the RMaker source files if your pathnames differ or if you want to change any of the dialogs or other resources. Place Daisy in your system folder.

Run the Chooser desk accessory to install the driver in the system file, and to set any options which appear in your Chooser interface dialog. Chooser may not want to fetch the new ‘DRVR’ resource from your Printer Resource File if Daisy is your only Printer Resource File. (Chooser is meant for the end user, who usually does not compile new printer drivers several times a day.) It may be necessary to have two Printer Resource Files during the development stages. When you create a new Printer Driver, first install the extra Printer File, then re-install Daisy. Do this just to make sure the Chooser clears out the old copy of the driver for you. Do not attempt to use RMaker to install the new driver in the system file (I tried this once). The Printer Resource File is installed on other system disks by copying it into the system folder, just as you would install an ImageWriter or a LaserWriter printer driver.

Part 8: What else, how to find out more.

Much of the information I needed to make my Printer Resource File work was buried in the depths of the Printer Manager Chapter of the Promotional Edition of Inside Macintosh. This is the copy that looks like a Manhattan phone book. The Chooser device interface information is found in the Device Manager Chapter of Inside Macintosh 4. In order to use the Chooser interface in a more advanced fashion, it will be necessary to learn how to use the List Manager, also in Inside Macintosh 4. The secrets of the Print dialogs were revealed in Macintosh Technical Note #95. There is a new low-level printer call, PrGeneral, whose calling sequence is given in Technical Note #128. This routine is found in ‘PDEF’ #7, I believe, and the resource apparently has a standard header (like the ‘PACK’ -4096 resource).

As you may know, Macintosh Technical Support is pretty sparse on documentation for writing a Printer Resource File. The following features of the printer interface were incorrectly or incompletely documented in my Macintosh documentation. More may be found.

° The driver’s Font Manager control call is passed a pointer to an FMInput, and not an FMOutput, record.

° Some applications require PrOpenPage to do a SetPort to the Printing Port, while most do not. My PrOpenPage documentation does not mention this.

° The numer and denom parameters to StdText are Points, and not integers. (Perhaps it is time to retire the “PhoneBook”.)

° PrintVars: What are they, and how may we use them?

Nevertheless, Apple has released all (or most of) the information that is necessary to define the specifications for a Printer Resource File, and they have told application developers “This is how the sucker is called.” Without commenting on the prettiness (or lack thereof) of the Printer Manager interface, one can say that this interface will not change (much) in the near future, unless Apple is ready to break every application which prints on the Macintosh. For good or ill, then, if you want to write a Printer Resource File, you have to do it (mostly) this way.

Daisy was compiled on a 512ke with 1 Meg of RAM installed and an external 800k disk drive. The LightspeedC compiler version 2.11, System 4.1, Finders 5.5 and 6.0b3, Chooser 3.1, and some version of RMaker were used to produce it. The WriteNow word processor was used to test it. The following applications were known to be able to print with it as of September 28, 1987:

DarTerminal 3.2 (Dartmouth AppleTalk emulator)

Finder 5.5, 6.0b3 (Print catalog)

MockWrite 4.3 desk accessory

LightspeedC 2.11

MacTerminal 2.2

MacWrite 4.5,4.6

MDS Edit 2.0 d1

Microsoft Word 1.05

Microsoft Word 3.01

Pretty Print

Teach Text

WriteNow 1.00

This is a real bonus for me, since I was only trying to get something to work with WriteNow! The structure of the Printer Resource File is complicated, the interface is hard to understand, and the number of example programs is now exactly one, but the Macintosh Printer Manager fulfills its primary objectives: It is Device Independent, and works with all (properly written) Macintosh applications.

I would like to thank David Oster, who insisted that it would be possible to write one of these things with the existing documentation. David, you were right!

Control Key Codes

Control characters are sent to the printer by entering “^M” in the dialog box. This would send a “control-M” to the printer, which is an ascii 13 or a carriage return. Here are the control characters on the Mac II extended keyboard. The first column gives the control-key sequence as you would type it, the second column gives the decimal ascii character generated, the third column gives the ascii code name, and the last column gives an alternative control-key sequence on the extended keyboard. Use this table to figure out the printer control strings needed to make the printer operate properly. There does not appear to be any printable character that would generate ascii codes 0, 30 and 31 when used with the control key on this keyboard. This is a serious problem when using this driver with the imagewriter II, since some of it’s programming requires the NULL character and there is no way to generate this. The apple (command) key in combination with the tilde (`) key does produce ascii 0, but this is not a printable combination so can’t be used in our set-up dialog. Normally control-@ produces ascii 0 on normal keyboards, but on the Apple keyboard it does not! Note that all the function keys return the same ascii value! The option, apple and shift keys do not generate a code. A simple Basic program generated this table, as shown below:

{1}
REM This program finds control codes
CLS
PRINT “enter your control character...”
key:
    key$=INKEY$
    IF key$=”” THEN GOTO key
    PRINT “The ascii code is “;ASC(key$)
    GOTO key
CONTROL-ASCII  NAMEOTHER KEY
A1 SOH  HOME
B2 STX
C3 ETX  ENTER
D4 EOT  END
E5 ENQ  HELP
F6 ACK
G7 BEL
H8 BS DELETE
I9 HT TAB
J10LF
K11VT PAGE UP
L12FF PAGE DOWN
M13CR RETURN
N14SO
O15SI
P16DLE  F1-F15
Q17DC1
R18DC2
S19DC3
T20DC4
U21NAK
V22SYN
W23ETB
X24CAN
Y25EM
Z26SUB
[27ESC  ESCAPE
\28FS L.ARROW
]29GS   R.ARROW
 30RS U.ARROW
 31US D.ARROW
SPACE 32SPACE
{2}
Sources
/*
 * LS C source for PDEF 0, to implement draft mode printing
 * on a serial device. 
 * Earle R. Horton, September 19, 1987.
 * All rights reserved.
 */
 
#include “prglobals.h”
#include <EventMgr.h>

pascal  TPPrPort myPrOpenDoc();
pascal  voidmyPrCloseDoc();
pascal  voidmyPrOpenPage();
pascal  voidmyPrClosePage();
pascal  voidmyStdText();
pascal  int myStdTextMeas();
void    myClearPage();
Ptrallocate();
void    free();
void    bcopy();
DPstorage DrvrStorage();
void    checkabort();

main(){
 asm{
 dc.w ILLEGAL    ;;  So I can find it...
 jmp  myPrOpenDoc
 jmp  myPrCloseDoc
 jmp  myPrOpenPage
 jmp  myPrClosePage

 }
}
/*
  This function is supposed to return a pointer to a specialized GrafPort 
(a TPrPort) customized for printing.  Due to the paucity of documentation 
on how to go about this, I do not know whether I am going about this 
in exactly the right way, but I sure hope so.  I set portBits.bounds 
for the port to the empty Rect {0,0,0,0} and then install the standard 
QuickDraw routines as GrafProcs.  In place of StdText, I put my own StdText 
routine. Hopefully, QuickDraw will keep track of the correct pen location 
for me, and call my routine whenever it is necessary to draw text.  I 
just put the text in a big buffer for now, and then print it out when 
I get called to close the current page.  This takes up some memory, but 
solves the problem of what to do when the application wants a reverse 
line feed.
 Other tasks: save a copy of the user print record for later use in formatting 
the output page; save a copy of the user print record in the printer 
resource file
 */

pascal TPPrPort myPrOpenDoc(hPrint,pPrPort,pIOBuf)
THPrint hPrint;
TPPrPort  pPrPort;
Ptr     pIOBuf;
{
TPPrPort  thisport;
pline   thepage;
Handle  us;
register  DPstoragedsp;
THPrint savePrint;
PrParam *pb;

 us = (Handle)(GetResource(‘PDEF’,0));
 asm{
 move.l us,a0
 HLock
 }
/* Assign storage for printing port and page buffer. */

 if((thepage = (pline)allocate((long)(NROWS*sizeof(line)))) == nil){
 PrintErr = iMemFullErr;
 return nil;
 }
 else if(pPrPort == nil){
 if((thisport = (TPPrPort)
 allocate((long)sizeof(TPrPort))) == nil){
 free(thepage);
 PrintErr = iMemFullErr;
 return(nil);
 }
 thisport->fOurPtr = TRUE;
 }
 else {
 thisport = pPrPort;
 thisport->fOurPtr = FALSE;
 }
/* Copy print record into private storage area. */
 dsp = DrvrStorage();
 pb = &dsp->prpb;
      dsp->Print = **hPrint;
 thisport->lGParam4 = (long)thepage;
 dsp->Print.prJob.bJDocLoop = bDraftLoop;
 OpenPort(thisport);
/* Fill out gProcs for this port. */
 SetStdProcs(&thisport->gProcs);
 thisport->gProcs.textProc =  (QDPtr)myStdText;
 thisport->gProcs.txMeasProc = (QDPtr)myStdTextMeas;

 thisport->gPort.grafProcs = &thisport->gProcs;
/*
 * Set up the port Rect in the proper coordinates, relative to the page
 * and to the paper.
 */
 thisport->gPort.device = dsp->Print.prInfo.iDev;
 thisport->gPort.portRect = dsp->Print.prInfo.rPage;
 thisport->gPort.portBits.bounds.top =
  thisport->gPort.portBits.bounds.left =
   thisport->gPort.portBits.bounds.bottom =
    thisport->gPort.portBits.bounds.right = 0;

 
 SetPort(thisport);
/* Offset the page (portRect) relative to the paper. */
 PortSize(dsp->Print.prInfo.rPage.right,dsp->Print.prInfo.rPage.bottom);
 MovePortTo(- dsp->Print.rPaper.left, - dsp->Print.rPaper.top);
 GrafDevice(dsp->Print.prInfo.iDev);
 myClearPage(thepage);
      pb->csCode = iPrDevCtl; /* Init the printer. */
      pb->lParam1 = lPrReset; /* Driver code does work. */
 asm{
 move.l pb,a0
 PBControl
 }
 dsp->pagenum = 1;
      if(dsp->Print.prJob.pIdleProc == nil)
 dsp->Print.prJob.pIdleProc = (ProcPtr)checkabort;
      savePrint = (THPrint)GetResource(‘PREC’,1);
      if(savePrint != nil){
 LoadResource(savePrint);
 **savePrint = dsp->Print;
 ChangedResource(savePrint);
 ReleaseResource(savePrint);
      } /* Determine proper margins. */
      dsp->nlines = (dsp->Print.rPaper.bottom - dsp->Print.rPaper.top)/CHARHEIGHT;
 return(thisport);
}
pascal void myPrCloseDoc(pPrPort)
TPPrPortpPrPort;
{
pline   thepage;
Pfgsettings;

 free(pPrPort->lGParam4);
 ClosePort(pPrPort);
 if(pPrPort->fOurPtr) free(pPrPort);
}
/* 
 * This routine opens a new page.  Actually, all it does is clear out 
the array of lines in preparation for more fun with QuickDraw.  I suppose 
it could also send a reset command to the driver...
 */
pascal void myPrOpenPage(pPrPort,pPageFrame)
TPPrPortpPrPort;
TPRect  pPageFrame;
{
register DPstorage dsp;
 dsp = DrvrStorage();
 if(pPageFrame != nil)
 *pPageFrame = dsp->Print.prInfo.rPage;
 SetPort(pPrPort);

 myClearPage(pPrPort->lGParam4);
}
/*
 * This is the routine which does the actual printing.  QuickDraw calls 
which have called our StdText substitute routine have filled up a buffer 
with lines of text.  Now, we just get the buffer and print it.
 */
pascal void myPrClosePage(pPrPort)
TPPrPortpPrPort;
{
register DPstorage dsp;
register pline theline;
register inti,iocount;
PrParam *pb;

 dsp = DrvrStorage();
 pb = &dsp->prpb;
      if(dsp->pagenum < dsp->Print.prJob.iFstPage){
        dsp->pagenum++;
        return;
      }
      if(dsp->pagenum++ > dsp->Print.prJob.iLstPage){
        PrintErr = iPrAbort;
      if (dsp->preofstr[0] != ‘\0’){
 pb->csCode = iPrIOCtl;
        pb->lParam1 = (long)(&dsp->preofstr[1]);
        pb->lParam2 = (long)dsp->preofstr[0];
        asm{
        move.l pb,a0
        PBControl
        }
      }
      return;
 }
      if(dsp->Print.prStl.feed != feedCut || waitnextpage()){
 theline = (pline)pPrPort->lGParam4;
      if (dsp->prtopstr[0] != ‘\0’){
      pb->csCode = iPrIOCtl;
        pb->lParam1 = (long)(&dsp->prtopstr[1]);
        pb->lParam2 = (long)dsp->prtopstr[0];
        asm{
        move.l pb,a0
        PBControl
        }
      }
 for(i=0;dsp->nlines - i;i++){
      (* dsp->Print.prJob.pIdleProc)();
      if(PrintErr == iPrAbort)return;
      if((theline+i)->dirty == DIRTY){
        iocount = WIDTH;
        while( (theline+i)->text[--iocount] == ‘ ‘){}
        ++iocount;
        pb->csCode = iPrIOCtl;
        pb->lParam1 = 
        (long)(&(theline+i)->text[0]);
        pb->lParam2 = (long)iocount;
 asm{
 move.l pb,a0
 PBControl
 }
      }
 pb->csCode = iPrDevCtl;
 if(i < dsp->nlines - 1)
     pb->lParam1 = lPrLineFeed;
 else pb->lParam1 = lPrPageEnd;
 asm{
 move.l pb,a0
 PBControl
 }
 }
   }
 else PrintErr = iPrAbort;
}
/*
 * All text drawing calls in the TPrPort get sent here.  Find the current 
pen location and translate it to row and column of the page buffer, squirt 
the text into the buffer.
 */
pascal void myStdText(byteCount,textBuf,numer,denom)
intbyteCount;
QDPtr textBuf;
Point numer,denom;
{
Point   thepoint;
TPPrPorttp;
pline   thepage;
intwidth;
intx,y;
 GetPort(&tp);
 if(tp->gPort.device == IDEV10) width = 7;
 else if(tp->gPort.device == IDEV15) width = 5;
 else width = 6;
 thepage = (pline)tp->lGParam4;
 GetPen(&thepoint);
 /* (Local is page, Global is paper.) */
 LocalToGlobal(&thepoint);
 x = thepoint.h/width;
 y = thepoint.v/CHARHEIGHT;
 bcopy(textBuf,&((thepage+y)->text[x]),byteCount);
 (thepage+y)->dirty = DIRTY;
 Move(width*byteCount,0);
}
pascal  int myStdTextMeas(byteCount,textBuf,numer,denom,info)
intbyteCount;
QDPtr textBuf;
Point *numer,*denom;
FontInfo *info;
{
TPPrPorttp;
 GetPort(&tp);
 if(tp->gPort.device == IDEV10)info->widMax = 7;
 else if(tp->gPort.device == IDEV15)info->widMax = 5;
 else info->widMax = 6;
 info->ascent = 9;
 info->descent = 2;
 info->leading = 0;
 numer->v = denom->v = 1;
 denom->h = 6;
 numer->h = info->widMax;
 return (info->widMax * byteCount);
}
void  myClearPage(theline)
pline theline;
{
intcount;
unsigned char  *ch;

 count = NROWS;
clear:
 theline->dirty = ~DIRTY;
 ch = &theline->text[0];
 asm{
 move.l ch,a0
 move.w #((WIDTH/4)-1),d0
 move.l #0x20202020,d1
loop:
 move.l d1,(a0)+
 dbra d0,@loop
 }
 if(--count){
 theline++;
 goto clear;
 }
}
Ptr allocate(size)
long size;
{
 asm{
 move.l size,d0
 NewPtr
 move.l a0,d0    ;; LightspeedC returns function
 } /* value in d0. */
}
void free(ptr)
Ptrptr;
{
 asm{
 move.l ptr,a0
 DisposPtr
 }
}
/*
 * A UNIXism.  Want to make something of it?
 */
void bcopy(src,dst,count)
unsigned char *src,*dst;
int count;
{
 asm{
 move.l src,a0
 move.l dst,a1
 clr.l  d0
 move.w count,d0
 BlockMove
 }
}
#define UTableBase 284
/*
 * This function returns a pointer to the printer driver’s private
 * storage.  We don’t need to lock the Handle, since it is always locked
 * when the driver is open.
 */
DPstorage DrvrStorage()
{
DCtlHandleourDCtlEntry;
DHstorage ourdCtlStorage;
 asm{
 move #2,d0
 asl.l  #2,d0    ;; d0 = 8L
 move.l UTableBase,a0;; a0 -> base of unit table
 adda d0,a0 ;; a0 -> second entry
 move.l (a0),ourDCtlEntry ;; handle to DCtlEntry[2]
 }
 ourdCtlStorage = (DHstorage)(*ourDCtlEntry)->dCtlStorage;
 return(*ourdCtlStorage);
}
/*
 * Abort printing if command ‘.’ pressed.
 */
void checkabort()
{
EventRecord myevent;
intc;
 if (GetNextEvent(keyDownMask, &myevent)){
 if(LoWord(myevent.message & charCodeMask) == ‘.’ &&
       (myevent.modifiers & cmdKey) ){
 PrintErr = iPrAbort;
 }
 }
}
/*
 * Modal dialog box: “Insert next sheet.”
 */
waitnextpage()
{
DialogPtr sheetdialog;
WindowPtr tempport;
intitemhit,donetype;
Handle  doneitem;
Rect  donebox;
 if((sheetdialog = GetNewDialog(SHEETDIALOG, 0L,(WindowPtr) -1)) == nil)
 return FALSE;
 InitCursor();
 GetDItem(sheetdialog,DONEITEM,&donetype,&doneitem,&donebox);
 GetPort(&tempport);
 SetPort(sheetdialog);
 PenSize(3,3);
 InsetRect(&donebox,-4,-4);
 FrameRoundRect(&donebox,16,16);
 ModalDialog(0L,&itemhit);
 DisposDialog(sheetdialog);
 SetPort(tempport);
 if(itemhit == STOPITEM) return FALSE;
 return TRUE;
}


/*
 * PDEF4.c.
 *
 * Generic daisy/dot matrix text printer driver
 * Earle R. Horton August 31, 1987
 * All rights reserved.
 *
 *      This module contains the code for validating, creating,
 * and modifying print records.
 */
/*
 * This module is to be placed into a code resource project using LightspeedC, 
version 2.01 or greater.  The project is to be made into PDEF resource 
number 4.  All of the code up to and including the first illegal instruction 
 is to be stripped off, so that the PDEF will have the standard format 
for resources of this type.  Switch statements cannot be used, since 
LightspeedC compiles them into separate code which is added to the beginning 
of the code resource, before our standard header.  I do not know at this 
point which types of flow control constructs are safe, but I have determined 
by disassembly that the following will produce useable code:
 * if, if/else blocks
 * gotos
 *
 * Instructions, or “How I did it.” Create from this module a code resource 
of type ‘pdef’ ID 4. Run the program utils.c to make it into PDEF ID 
4 and to strip off the standard header.
 */

#include <DialogMgr.h>
#include <EventMgr.h>
#include “prglobals.h”
#define STYLEDIALOG(0xE000)
#define JOBDIALOG(0xE001)
#define XTRA24 /* Six extra longs for our use. */
#define DLGSIZE ((long)(sizeof(TPrDlg) + XTRA))
#define TPSIZE ((long)(sizeof(TPrint)))
/* Items we handle in the job and style dialogs. */
#define CANCELITEM 2
#define CPI10BUTTON4
#define CPI12BUTTON5
#define CPI15BUTTON6
#define STRAIGHTUPITEM  7
#define SIDEWAYSITEM 8
#define ALLBUTTON5
#define RANGEBUTTON6
#define FROMNUM  7
#define TONUM    9
#define COPIES   11
#define FANBUTTON13
#define SHEETBUTTON14

pascal void MyPrintDefault(); /* Fill default print record*/
pascal Boolean MyPrStlDialog();  /* printer style dialog. */
pascal Boolean MyPrJobDialog();  /* printer job dialog. */
pascal TPPrDlg MyPrStlInit(); /* Set up style dialog. */
pascal TPPrDlg MyPrJobInit(); /* Set up job dialog. */
pascal Boolean   MyPrDlgMain();    /* Print dialog supervisor*/
pascal Boolean MyPrValidate();/* Validate print record. */
pascal void MyPrJobMerge(); /* Copy a job subrecord. */
pascal Boolean MyFilter();/* Filter dialog events. */
pascal void HandleStyleItems();  /* Handle Style Items. */
pascal void HandleJobItems(); /* Handle Job Items. */
TPPrDlg TPPrDlgallocate();
void    pushradiobutton();
intNumToString(),StringToNum();
Boolean Valid();
void    mkDefault();
void    free();
main()
{
 asm{
 dc.w ILLEGAL    ;;  So I can find it...
 jmp  MyPrintDefault
 jmp  MyPrStlDialog
 jmp  MyPrJobDialog
 jmp  MyPrStlInit
 jmp  MyPrJobInit
 jmp  MyPrDlgMain
 jmp  MyPrValidate
 jmp  MyPrJobMerge

 }
}
/* 
 * This function fills a print record with defaults.  The default values 
are stored in the Printer resource file, in PREC 0.  This is easy.  Then 
we check the fields of the print record for anything obviously illegal. 
If the default print record contains stuff that is bad, then we correct 
it and update the copy in the printer resource file.  This should never 
happen, but some wise guy with a copy of ResEdit and more brains than 
sense may think he knows more than we do.
 */
pascal void MyPrintDefault(hPrint)
THPrint hPrint;
{
THPrint thedefault;
 thedefault = (THPrint)(GetResource(‘PREC’,0));
 if(thedefault == nil){
 mkDefault(hPrint);
 }
 else{
 LoadResource(thedefault);
 if(MyPrValidate(thedefault)) {
 ChangedResource(thedefault);
 WriteResource(thedefault);
 }
 **hPrint = **thedefault; /* What the hell. */
 }
}
pascal Boolean MyPrStlDialog(hPrint) /*print style dialog.*/
THPrint hPrint;
{
 return(MyPrDlgMain(hPrint,MyPrStlInit));
}
pascal Boolean MyPrJobDialog(hPrint) /* Conduct printer job dialog. */
THPrint hPrint;
{
 return(MyPrDlgMain(hPrint,MyPrJobInit));
}
/* 
 * The style dialog initializer.
 */
pascal  TPPrDlg  MyPrStlInit(hPrint)
THPrint hPrint;
{
TPPrDlg tp;
 tp = TPPrDlgallocate();
 (GetNewDialog(STYLEDIALOG,tp,-1));
 if((*hPrint)->prInfo.iDev == IDEV10)
 pushradiobutton(tp,CPI10BUTTON,CPI10BUTTON,CPI15BUTTON);
 else if((*hPrint)->prInfo.iDev == IDEV12)
 pushradiobutton(tp,CPI12BUTTON,CPI10BUTTON,CPI15BUTTON);
 else if((*hPrint)->prInfo.iDev == IDEV15)
 pushradiobutton(tp,CPI15BUTTON,CPI10BUTTON,CPI15BUTTON);
 pushradiobutton(tp,STRAIGHTUPITEM,STRAIGHTUPITEM,SIDEWAYSITEM);
 tp->pFltrProc = (ProcPtr)MyFilter;
 tp->pItemProc = (ProcPtr)HandleStyleItems;
 tp->hPrintUsr = hPrint;

 return(tp);
}
/* 
 * The job dialog initializer.
 */
pascal TPPrDlg MyPrJobInit(hPrint)
THPrint hPrint;
{
TPPrDlg tp;
int     thenum;
Handle  theitem;
Rect    thebox;
Str36   title;
 tp = TPPrDlgallocate();
 (GetNewDialog(JOBDIALOG,tp,-1));
 pushradiobutton(tp,ALLBUTTON,ALLBUTTON,RANGEBUTTON);
 if( (*hPrint)->prStl.feed == feedCut)
 pushradiobutton(tp,SHEETBUTTON,FANBUTTON,SHEETBUTTON);
 else 
 pushradiobutton(tp,FANBUTTON,FANBUTTON,SHEETBUTTON);
 GetDItem(tp,FROMNUM,&thenum,&theitem,&thebox);
 thenum = (*hPrint)->prJob.iFstPage;
 NumToString((long)thenum,title);
 SetIText(theitem,title);
 
 GetDItem(tp,TONUM,&thenum,&theitem,&thebox);
 thenum = (*hPrint)->prJob.iLstPage;
 NumToString((long)thenum,title);
 SetIText(theitem,title);

 GetDItem(tp,COPIES,&thenum,&theitem,&thebox);
 thenum = (*hPrint)->prJob.iCopies;
 NumToString((long)thenum,title);
 SetIText(theitem,title);

 tp->pFltrProc = (ProcPtr)MyFilter;
 tp->pItemProc = (ProcPtr)HandleJobItems;
 tp->hPrintUsr = hPrint;
 return(tp);
}
pascal Boolean   MyPrDlgMain(hPrint,pDlgInit)
 /* Printing dialog supervisor function. */
 /* Reference: Macintosh Technical Note  */
 /* 95.  Good luck! */
THPrint hPrint;
ProcPtr pDlgInit;
{
TPPrDlg tp;
WindowPtr tempport;
int   donetype,itemhit;
Handle  doneitem;
Rect  donebox;
ProcPtr itemproc;
 asm{
 subq.l #4,a7    ;;  Room for function return.
 move.l hPrint,-(a7);;  Pass handle to print record.
 move.l pDlgInit,a0;;  Get addr. of dialog init routine.
 jsr  (a0);;  It’s a “Pascal” routine.
 move.l (a7)+,tp ;;  Pop return value.
 }
 itemproc = tp->pItemProc;
 tp->fDone = FALSE;
 tp->fDoIt = FALSE;
 GetPort(&tempport);
 SetPort(tp);
 ShowWindow(tp);
 GetDItem(tp,DONEITEM,&donetype,&doneitem,&donebox);
 PenSize(3,3);
 InsetRect(&donebox,-4,-4);
 FrameRoundRect(&donebox,16,16); 
 while(!(tp->fDone)){
 ModalDialog(tp->pFltrProc,&itemhit);
/*
 * Reverse parameters on the call to pItemProc.  The application is allowed 
to trap our pItemProc and insert its own, so we must use the Pascal calling 
conventions here.  Programming novices can use the LightspeedC™  CallPascal 
library if they want.  You will have to be careful if you do this to 
make sure the library is linked AFTER the module containing these functions, 
or you won’t get the PDEF resource header right.
 */
 asm{
 move.l tp,-(a7)
 move.w itemhit,-(a7)
 move.l itemproc,a0
 jsr  (a0)
 }
 }
 SetPort(tempport);
 CloseDialog(tp);
 free(tp);
 if(tp->fDoIt) (void)MyPrValidate(hPrint);
 return(tp->fDoIt);
}
/*
 * Validate/update a print record.  Check all the fields for compatibility 
with our driver.  This is a three stage process.  First, we check all 
fields to see whether they are within the bounds which our driver can 
handle.  If they are, we return FALSE (no change).  If not, we obtain 
a copy of the current default values from the printer resource file. 
 Then we inspect these to see if they are valid.  If the default values 
in the printer resource file are valid, we use them, update the user’s 
print record, and return TRUE (changed).  Otherwise, we fall back on 
default values which we store in the code here.  This three stage process 
provides some protection against the user who attempts to adjust the 
print record using a resource editor, and screws up.
 */
pascal Boolean MyPrValidate(hPrint)/* Validate/update a print record. 
*/
THPrint hPrint;
{
THPrint thedefault;
 if(Valid(hPrint)) return FALSE;
 else{
 thedefault = (THPrint)(GetResource(‘PREC’,0));
 LoadResource(thedefault);
 if(thedefault == nil || !Valid(thedefault)) mkDefault(hPrint);
 else{
 **hPrint = **thedefault;
 }
 return TRUE;
 }
}
pascal void MyPrJobMerge(hPrintSrc,hPrintDst)
/*
 * Copy a job subrecord.  Update the destination record’s printer information, 
band information, and paper rectangle, based on information in the job 
subrecord.
 */
THPrint hPrintSrc,hPrintDst;
{
 (*hPrintDst)->prInfo.iDev = (*hPrintSrc)->prInfo.iDev;
 (*hPrintDst)->prJob = (*hPrintSrc)->prJob;
 (*hPrintDst)->prXInfo = (*hPrintSrc)->prXInfo;
 (*hPrintDst)->rPaper = (*hPrintSrc)->rPaper;
 (*hPrintDst)->prInfo = (*hPrintSrc)->prInfo;
 (*hPrintDst)->prInfoPT = (*hPrintSrc)->prInfoPT;
}
pascal Boolean MyFilter(thedialog,theEvent,itemhit)
DialogPtr thedialog;
EventRecord *theEvent;
int*itemhit;
{
 if(theEvent->what == keyDown && 
 (theEvent->message & charCodeMask) == 13){
 *itemhit = 1;
 return TRUE;
 }
 return FALSE;
}
/* 
 * The next routine handles the style dialog.  Two possibilities exist. 
If the cancel button is hit, then we signal quit.  The print record is 
not changed. If the done button is hit, we validate the user’s print 
record.
 * (MyPrDlgMain() calls MyPrValidate() to do this.)
 * We use three radio buttons to determine the number of characters per 
inch (resolution), and two to determine paper orientation.
 */
pascal void HandleStyleItems(tp,itemhit)
TPPrDlg tp;
intitemhit;
{
intthenum;
Handle  theitem;
Rect  thebox;
 if(itemhit >= CPI10BUTTON && itemhit <= CPI15BUTTON)
 pushradiobutton(tp,itemhit,CPI10BUTTON,CPI15BUTTON);
 else if(itemhit >= STRAIGHTUPITEM && itemhit <= SIDEWAYSITEM)
 pushradiobutton(tp,itemhit,STRAIGHTUPITEM,SIDEWAYSITEM);
 else if(itemhit == DONEITEM){
 TPrint Print;
 TPPrint pPrint;
 pPrint = &Print;
 mkDefault(&pPrint);
 (*tp->hPrintUsr)->iPrVersion = Print.iPrVersion;
 (*tp->hPrintUsr)->prInfo = Print.prInfo;
 (*tp->hPrintUsr)->prXInfo = Print.prXInfo;
 (*tp->hPrintUsr)->rPaper = Print.rPaper;
 (*tp->hPrintUsr)->prStl = Print.prStl;
 (*tp->hPrintUsr)->prInfoPT = Print.prInfoPT;
 GetDItem(tp,CPI10BUTTON,&thenum,&theitem,&thebox);
 if(GetCtlValue(theitem) == 1){
 (*tp->hPrintUsr)->prInfo.iDev = IDEV10;
 }
 GetDItem(tp,CPI12BUTTON,&thenum,&theitem,&thebox);
 if(GetCtlValue(theitem) == 1){
 (*tp->hPrintUsr)->prInfo.iDev = IDEV12;
 }
 GetDItem(tp,CPI15BUTTON,&thenum,&theitem,&thebox);
 if(GetCtlValue(theitem) == 1){
 (*tp->hPrintUsr)->prInfo.iDev = IDEV15;
 }
 GetDItem(tp,SIDEWAYSITEM,&thenum,&theitem,&thebox);
 if(GetCtlValue(theitem) == 1){
 int temp;
 flipRect(&(*tp->hPrintUsr)->prInfo.rPage);
 flipRect(&(*tp->hPrintUsr)->rPaper);
 flipRect(&(*tp->hPrintUsr)->prInfoPT.rPage);
 temp = (*tp->hPrintUsr)->prStl.iPageV;
 (*tp->hPrintUsr)->prStl.iPageV = 
 (*tp->hPrintUsr)->prStl.iPageH;
 (*tp->hPrintUsr)->prStl.iPageH = temp;
 }
 tp->fDone = TRUE;
 tp->fDoIt = TRUE;
 }
 else if (itemhit == CANCELITEM){
 tp->fDone = TRUE;
 tp->fDoIt = FALSE;
 }
}
pascal void HandleJobItems(tp,itemhit)
TPPrDlg tp;
intitemhit;
{
int     thenum;
long    thelong;
Handle  numitem;
Rect    numbox;
Str36   title;
 if(itemhit == DONEITEM){ /* NO SWITCHES! */
 tp->fDone = TRUE; /* At least until I get a */
 tp->fDoIt = TRUE; /* better compiler. */
 GetDItem(tp,ALLBUTTON,&thenum,&numitem,&numbox);
 if(GetCtlValue(numitem) == 1){
     (*(tp->hPrintUsr))->prJob.iFstPage = iPrPgFst;
     (*(tp->hPrintUsr))->prJob.iLstPage = iPrPgMax;
 }
 else{
     GetDItem(tp,FROMNUM,&thenum,&numitem,&numbox);
     GetIText(numitem,&title[0]);
     StringToNum(&title[0],&thelong);
     (*(tp->hPrintUsr))->prJob.iFstPage = thelong;    
     GetDItem(tp,TONUM,&thenum,&numitem,&numbox);
     GetIText(numitem,&title[0]);
     StringToNum(&title[0],&thelong);
     (*(tp->hPrintUsr))->prJob.iLstPage = thelong;    
 }
         GetDItem(tp,COPIES,&thenum,&numitem,&numbox);
         GetIText(numitem,&title[0]);
         StringToNum(&title[0],&thelong);
         (*(tp->hPrintUsr))->prJob.iCopies = thelong;
 GetDItem(tp,SHEETBUTTON,&thenum,&numitem,&numbox);
 if(GetCtlValue(numitem) == 1){
     (*(tp->hPrintUsr))->prStl.feed = feedCut;
 }
 else (*(tp->hPrintUsr))->prStl.feed = feedFanfold;
 }
 else if (itemhit == CANCELITEM){
 tp->fDone = TRUE;
 }
 else if (itemhit == SHEETBUTTON){
 pushradiobutton(tp,SHEETBUTTON,FANBUTTON,SHEETBUTTON);
 (*(tp->hPrintUsr))->prStl.feed = feedCut;
 }
 else if (itemhit == FANBUTTON){
 pushradiobutton(tp,FANBUTTON,FANBUTTON,SHEETBUTTON);
 (*(tp->hPrintUsr))->prStl.feed = feedFanfold;
 }
 else if (itemhit == ALLBUTTON){
 pushradiobutton(tp,ALLBUTTON,ALLBUTTON,RANGEBUTTON);
 }
 else if (itemhit == RANGEBUTTON){
 pushradiobutton(tp,RANGEBUTTON,ALLBUTTON,RANGEBUTTON);
 }
}
TPPrDlg TPPrDlgallocate()
{
TPPrDlg thepointer;
 asm{
 move.l #DLGSIZE,d0
 NewPtr
 move.l a0,thepointer
 }
 if(thepointer == nil){ /* We’re in deep six now! */
 asm{
 move.w #25,d0
 SysError ;;  SysError
 }
 }
 return(thepointer);
}
void pushradiobutton(thedialog,itemhit,first,last)             
 /* push a radio Button */
DialogPtr thedialog; /* set itemhit, unset  */
int itemhit,first,last;   /* all others in range */
{
   int itemtype,i;
   Handle itemhandle;/* Does check boxes, too. */
   Rect itemrect;
 if(first ==0) return;
 for(i=first-1;last-i++;){
 GetDItem(thedialog,i,&itemtype,&itemhandle,&itemrect);
 if(i == itemhit) SetCtlValue(itemhandle,1);
 else SetCtlValue(itemhandle,0);
 }
}
NumToString(thenum,thestring)
long  thenum;
char  *thestring;
{
 asm{
 move.l thestring,a0
 move.l thenum,d0
 move.w #0,-(a7)
 Pack7
 }
}
StringToNum(thestring,thenum)
char  *thestring;
long  *thenum;
{
 asm{
 move.l thestring,a0
 move.w #1,-(a7)
 Pack7
 move.l thenum,a0
 move.l d0,(a0)
 }
}
/*
 * This function answers the question:  Can we possibly use this print 
record?
 */
Boolean Valid(hPrint)
THPrint hPrint;
{
if (((*hPrint)->iPrVersion != VERSION)
  || ((*hPrint)->prInfo.iDev != IDEV12 &&
      (*hPrint)->prInfo.iDev != IDEV10 &&
      (*hPrint)->prInfo.iDev != IDEV15)
  || ((*hPrint)->prInfo.iVRes != VREZZ)
  || ((*hPrint)->prInfo.iHRes != HREZZ12)
  || ((*hPrint)->prInfo.rPage.top !=0 ||
   (*hPrint)->prInfo.rPage.left !=0)
  || ((*hPrint)->rPaper.right - 
      (*hPrint)->rPaper.left > 11 * HREZZ12) 
  || ((*hPrint)->rPaper.bottom -
      (*hPrint)->rPaper.top > 11 * VREZZ)    
  || ((*hPrint)->prStl.feed != feedCut && 
   (*hPrint)->prStl.feed != feedFanfold)     
  || ((*hPrint)->prJob.bJDocLoop != bDraftLoop))   

 return FALSE;

else  returnTRUE;
}
void free(ptr)
char  *ptr;
{
 asm{
 move.l ptr,a0
 DisposPtr
 }
}
flipRect(rect)
Rect  *rect;
{
 asm{
 move.l rect,a0
 move.l (a0),d0
 swap d0;; swap top, left
 move.l d0,(a0)+
 move.l (a0),d0
 swap d0;; swap bottom, right
 move.l d0,(a0)
 }
}
 
#include “mkDefault.c”




/*
 * PDEF5.c
 *
 * Although this printer driver does not do spool printing, I provide 
a duplicate PDEF to pretend to do so.  That is just a copy of my PDEF 
0, and it really does draft printing.  This module is so the application 
can call PrPicFile() when it is done.  Some applications just don’t get 
the hint, and attempt to spool print even when the print record says 
otherwise.
 */
#include“prglobals.h”
pascal  voidmyPrPicFile();
main()
{
 asm{
 dc.w ILLEGAL
 jmp  myPrPicFile
 }
}
pascal  voidmyPrPicFile(hPrint,pPrPort,pIOBuf,pDevBuf,prStatus)
THPrint hPrint;
TPPrPortpPrPort;
PtrpIOBuf,pDevBuf;
TPrStatus *prStatus;
{
}


/*
 * PACK.c
 *
 * Code for PACK ID -4096 to be used with the Chooser to set the printing 
port options.  Set the Flags longword in the PACK resource to 0400E000 
after building the PACK resource to make sure we get the right-hand button 
message.
 */

#include <WindowMgr.h>  /* includes QuickDraw.h, MacTypes.h */
#include <DialogMgr.h>
#include “prglobals.h”
/* 
 * Resources which are standard type and accessed by the PACK -4096 resource 
have ID # RES1ID.  Resources which are non-standard type and/or belong 
without a doubt to our driver code are accessed with RES2ID.  These include 
the Stng resource and our PREC #-8192.  (IM 4 says standard resource 
types to be used by this PACK resource should have IDs in the range -4080 
to -4065.)
 */

/* Printer Setup Dbox item numbers  */
#define SAVEITEM 1
#define MODEM    5
#define PRINTER  6
#define BAUDBUTTON 8
#define EOLITEM  11
#define INITITEM 12
#define TOPITEM  13
#define EOPITEM  14
#define EOFITEM  15
#define CTSITEM  19
#define XONXOFFITEM20
#define CANCELITEM 21
#define NUMSTRINGS 5
#define RESPAD   24

pascal OSErr main(message,caller,objname,zonename,p1,p2)
intmessage,caller;
StringPtr objname,zonename;
long  p1,p2;
{
 if (message == buttonMsg){
      prsetup();
 }
 return (noErr);
}
pushradiobutton(thedialog,itemhit,first,last)      /* push a radio Button 
*/
DialogPtr thedialog; /* set itemhit, unset  */
int itemhit,first,last;   /* all others in range */
{
   int itemtype,i;
   Handle itemhandle;/* Does check boxes, too. */
   Rect itemrect;   /* (when range is 1 in size.) */
 if(first ==0) return;
 for(i=first-1;last-i++;){
 GetDItem(thedialog,i,&itemtype,&itemhandle,&itemrect);
 if(i == itemhit) SetCtlValue(itemhandle,1);
 else SetCtlValue(itemhandle,0);
 }
}
prsetup()
{
DialogPtr printdialog;
WindowPtr tempport;
int   itemhit,i,baudtype,edittype,donetype;
Handle  bauditem,doneitem,edititem;
Rect  baudbox,donebox,editbox;
Str255  thestring;
unsigned char  *strptr;
long  length,result;
BAUDS **mybauds;
Pfgsettings;
StrList mystrings;
 mybauds = (BAUDS **)GetResource(‘PREC’,RES2ID);
 if(mybauds == nil) return;
 if ((settings = (Pfg)(GetResource(‘Stng’,RES2ID))) == nil || (mystrings 
= (StrList) (GetResource(‘STR#’,RES1ID))) == nil)
 return;
 if (pbaud<0 || pbaud>9) pbaud = 0;
 if((printdialog = GetNewDialog(RES1ID, 0L,(WindowPtr) -1)) == nil)
 return;
GetDItem(printdialog,BAUDBUTTON,&baudtype,&bauditem,&baudbox);
GetDItem(printdialog,SAVEITEM,&donetype,&doneitem,&donebox);
SetCTitle(bauditem,((*mybauds)+pbaud)->label);
/* This gets the printer control strings from a string list, then sets 
the editText items in the dialog box to contain the strings.
 */
 strptr = &((*mystrings)->thestrings[0]);
 for(i = EOLITEM-1;EOFITEM - i++;){
     GetDItem(printdialog,i,&edittype,&edititem,&editbox);
     SetIText(edititem,strptr);
     strptr += (*strptr) + 1;
 }
 GetPort(&tempport);
 SetPort(printdialog);
 ShowWindow(printdialog);
 PenSize(4,4);   /* Time to frame some buttons. */
 InsetRect(&donebox,-5,-5);
 FrameRoundRect(&donebox,16,16);
 PenSize(2,2);
 InsetRect(&baudbox,-3,-3);
 FrameRoundRect(&baudbox,12,12);
 pushradiobutton(printdialog, pport + MODEM,MODEM,PRINTER);
 pushradiobutton(printdialog,CTSITEM + XonXoff,CTSITEM,
     XONXOFFITEM);
 itemhit = 0;
        while(itemhit !=1){
              ModalDialog(0L,&itemhit);
              switch(itemhit){
/* Port change.  It might be nice to check and see whether AppleTalk 
is active if the user selects the Printer Port  */
                 case MODEM:
                 case PRINTER:
                 pport = itemhit - MODEM;
 pushradiobutton(printdialog,itemhit,
    MODEM,PRINTER);
                 break;
                 case BAUDBUTTON: /* next baud rate change */
             /* Ten radio buttons would be just too much. */
                 if(++pbaud == 10) pbaud = 0;
 SetCTitle(bauditem,((*mybauds)+pbaud)->label);
 break;
                 case CTSITEM:
                 case XONXOFFITEM:
                 XonXoff = itemhit - CTSITEM;
 pushradiobutton(printdialog,
     itemhit,CTSITEM,XONXOFFITEM);
                 break;
                 case CANCELITEM:
        DisposDialog(printdialog);
          SetPort(tempport);                             
                 return;
                 break;
        }
 }
 /* The user has set the baud rate and the port, and also possibly edited 
the printer control strings.  Since we used ModalDialog() with no filterproc 
we don’t know whether any of the strings have been changed.  Therefore 
we just rebuild the whole string list. First, determine the length. */

 length = (long) (sizeof(int)+RESPAD);
 for(i = EOLITEM-1;EOFITEM - i++;){
     GetDItem(printdialog,i,&edittype,&edititem,&editbox);
     GetIText(edititem,thestring);
     length += (long) thestring[0];
 }
 /* Size might have changed, so we unlock the handle and attempt to resize 
it. */
 asm{
 move.l mystrings,a0 ;; save loading MacTraps
 _HUnlock
 move.l mystrings,a0
 move.l length,d0
 _SetHandleSize
 move.l mystrings,a0
 _GetHandleSize
 move.l d0,result
 }
 if ( result != length ){ /* Abort on error. */
     DisposDialog(printdialog);
     SetPort(tempport);                            
     return(FALSE);
 }
 asm{
 move.l mystrings,a0
 _HNoPurge
 move.l mystrings,a0
 _HLock
 }
 /* Rebuild the STR# from the item list. */
 strptr = &((*mystrings)->thestrings[0]);
 for(i = EOLITEM-1;EOFITEM - i++;){
     GetDItem(printdialog,i,&edittype,&edititem,&editbox);
     GetIText(edititem,strptr);
     strptr += (*strptr) + 1;
 }
 DisposDialog(printdialog);
 SetPort(tempport); 
 ChangedResource(settings);
 ChangedResource(mystrings);                       
 WriteResource(settings);
 WriteResource(mystrings);
 return;
}


/*
 * mkDefault.c
 *
 * This function fills a print record with defaults, using coded values. 
It is used only when the default print record stored in the printer resource 
file is found to be invalid.  It’s also for the code to make the first 
copy.  According to a compile-time switch, margins are either zero or 
one inch all around, zero on the right. To support paper which has more 
than 66 lines per page or 164 columns, you have to make changes here 
and in PDEF0.c.
 */
#define ONE_INCH_MARGIN 1
void mkDefault(hPrint)
THPrint hPrint;
{
 (*hPrint)->iPrVersion = VERSION;
 (*hPrint)->prInfo.iDev = IDEV12;
 (*hPrint)->prInfo.iVRes = VREZZ;
 (*hPrint)->prInfo.iHRes = HREZZ12;
 (*hPrint)->prInfo.rPage.top = 0;
 (*hPrint)->prInfo.rPage.left = 0;

#if ONE_INCH_MARGIN
 (*hPrint)->prInfo.rPage.right = HREZZ12 * 7;
 (*hPrint)->prInfo.rPage.bottom = VREZZ * 9;
 (*hPrint)->rPaper.top = -VREZZ;
 (*hPrint)->rPaper.left = -HREZZ12;
 (*hPrint)->rPaper.bottom = VREZZ * 10;
 (*hPrint)->rPaper.right = HREZZ12 * 7 + HREZZ12/2;
#else
 (*hPrint)->prInfo.rPage.right = HREZZ12 * 8 + HREZZ12/2;
 (*hPrint)->prInfo.rPage.bottom = VREZZ * 11;
 (*hPrint)->rPaper = (*hPrint)->prInfo.rPage;
#endif

 (*hPrint)->prStl.wDev = iDevDaisy;
 (*hPrint)->prStl.iPageV = 11 * iPrPgFract;
 (*hPrint)->prStl.iPageH = (int)((float)iPrPgFract * 8.5);
 (*hPrint)->prStl.bPort = 0;
 (*hPrint)->prStl.feed = feedFanfold;
 (*hPrint)->prInfoPT = (*hPrint)->prInfo;
 (*hPrint)->prXInfo.iRowBytes = 0;
 (*hPrint)->prXInfo.iBandV = 0;
 (*hPrint)->prXInfo.iBandH = 0;
 (*hPrint)->prXInfo.iDevBytes = 0;
 (*hPrint)->prXInfo.iBands = 0;
 (*hPrint)->prXInfo.bPatScale = 0;
 (*hPrint)->prXInfo.bULThick = 0;
 (*hPrint)->prXInfo.bULOffset = 0;
 (*hPrint)->prXInfo.bULShadow = 0;
 (*hPrint)->prXInfo.scan = scanLR;
 (*hPrint)->prXInfo.bXInfoX = 0;
 (*hPrint)->prJob.iFstPage = 1;
 (*hPrint)->prJob.iLstPage = iPrPgMax;
 (*hPrint)->prJob.iCopies = 1;
 (*hPrint)->prJob.bJDocLoop = bDraftLoop;
 (*hPrint)->prJob.fFromUsr = TRUE;
 (*hPrint)->prJob.pIdleProc = nil;
 (*hPrint)->prJob.pFileName = nil;
 (*hPrint)->prJob.iFileVol = 0;
 (*hPrint)->prJob.bFileVers = 0;
 (*hPrint)->prJob.bJobX = 0;
 (*hPrint)->printX[0] =
 (*hPrint)->printX[1] =
 (*hPrint)->printX[2] =
 (*hPrint)->printX[3] =
 (*hPrint)->printX[4] =
 (*hPrint)->printX[5] =
 (*hPrint)->printX[6] =
 (*hPrint)->printX[7] =
 (*hPrint)->printX[8] =
 (*hPrint)->printX[9] =
 (*hPrint)->printX[10] =
 (*hPrint)->printX[11] =
 (*hPrint)->printX[12] =
 (*hPrint)->printX[13] =
 (*hPrint)->printX[14] =
 (*hPrint)->printX[15] =
 (*hPrint)->printX[16] =
 (*hPrint)->printX[17] =
 (*hPrint)->printX[18] = 0;
}



/*
 * Utils.c
 *
 * Utility program to format the code resources used with the Daisy printing 
manager.  If you run it twice, it does nothing the second time.
 */
#include“prglobals.h”
#define PAD 24L
typedef struct{
 unsigned int  flags;
 unsigned int  delay;
 unsigned int  emask;
 unsigned int  menu;
}driver,*Pdriver,**Hdriver;
void mkDefault();
main()
{
 setpackflags();
 setdriverflags();
 createPDEF(“\psrc:Printer:DaisyPDEF0”,0);
 createPDEF(“\psrc:Printer:DaisyPDEF4”,4);
 createPDEF(“\psrc:Printer:DaisyPDEF5”,5);
 createPREC();
}
/*
 * This is a utility function to strip off the header bytes LightSpeedC 
puts on Code Resources it creates.  I put an illegal instruction right 
before the code I want.
 */
createPDEF(filename,idno)
char *filename;
int idno;
{
long    thesize;
unsigned int   **thehandle,**newhandle;
unsigned int*theword,*newone;
intthefile;
intresult;
 thefile = OpenResFile(filename);
 thehandle = (unsigned int **) GetResource(‘pdef’,idno);
 if (thehandle == 0L) return;
 else{
 thesize = SizeResource(thehandle) + PAD;
 asm {
 move.l thehandle,a0
 _HLock
 }
 theword = *thehandle;
 while(*theword++ != ILLEGAL){
 thesize -=2;
 }
 asm{
 move.l thesize,d0
 _NewHandle
 move.l a0,newhandle
 }
 if (newhandle == 0L) return;
 else{
 asm{
 move.l newhandle,a0
 _HLock
 move.l (a0),newone
 move.l theword,a0
 move.l newone,a1
 move.l thesize,d0
 _BlockMove
 move.w d0,result
 }
 if(result)return;
 AddResource(newhandle,’PDEF’,idno,”\pStripped PDEF”);
 WriteResource(newhandle);
 RmveResource(thehandle);
 UpdateResFile(thefile);
 CloseResFile(thefile);
 }
 }
}
setdriverflags()
{
Hdriver Xprint;
 OpenResFile(“\psrc:Printer:DaisyDRVR”);
 Xprint = (Hdriver)(GetResource(‘DRVR’,2));
 (*Xprint)->flags = dCtlEnable | dStatEnable;
 (*Xprint)->delay = 0x0000;
 (*Xprint)->emask = 0x0000;
 (*Xprint)->menu = 0x0000;
 ChangedResource(Xprint);
 WriteResource(Xprint);
}
/* This function sets the version number and flags for the PACK resource 
used to interface with the Chooser. */
setpackflags()
{
long  **pack;
 OpenResFile(“\psrc:Printer:DaisyPACK”);
 pack = (long **)(GetResource(‘PACK’,-4096));
 if (pack != 0L){
 *((*pack)+2) = 0xF0000001;
 *((*pack)+3) = 0x0400E000;
 ChangedResource(pack);
 WriteResource(pack);
 }
}
/* Create a default printer settings resource based on the same code 
used in our printer resource file. */

createPREC()
{
THPrint thehandle;
 CreateResFile(“\psrc:Printer:DaisyPREC0”);
 OpenResFile(“\psrc:Printer:DaisyPREC0”);
 SetResLoad(TRUE);
 RmveResource(GetResource(‘PREC’,0));
 asm{
 move.l #((long)sizeof(TPrint)),d0
 NewHandle
 move.l a0,thehandle
 }
 mkDefault(thehandle);
 AddResource(thehandle,’PREC’,0,”\pPrint defaults”);
 WriteResource(thehandle);
}

#include “mkDefault.c”

* Daisy.r file puts it all together
*
bin:sys:Daisy
PRERDasY
*Include dialogs
INCLUDE src:Printer:dialogs.rsrc
Type PDEF = GNRL
Draft Printing Code,0 (48);; locked, Purgeable
.R
src:Printer:DaisyPDEF0 PDEF 0
*Type PDEF = GNRL
*Spool Printing Code,1 (48) ;; locked, Purgeable
*.R
*src:Printer:DaisyPDEF1 PDEF 1
*
Type PDEF = GNRL
Dialog Code,4 (48) ;; locked, Purgeable
.R
src:Printer:DaisyPDEF4 PDEF 4
Type PDEF = GNRL
PrPicFile stub,5
.R
src:Printer:DaisyPDEF5 PDEF 5
Type DRVR = GNRL
.XPrint,-8192 (32) ;; attributes -> Purgeable
.R
src:Printer:DaisyDRVR DRVR 2
Type PACK = GNRL
Daisy Config,-4096 (32) ;; attributes -> Purgeable
.R
src:Printer:DaisyPACK PACK -4096
Type PREC = GNRL
,0
.R
src:Printer:DaisyPREC0 PREC 0
Type PREC = GNRL
,1
.R
src:Printer:DaisyPREC0 PREC 0
Type PREC = GNRL
,-8192
.R
src:Printer:DaisyDRVR DATA -16320
Type STR 
,-8191
Print File
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »

Price Scanner via MacPrices.net

Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more

Jobs Board

Sublease Associate Optometrist- *Apple* Val...
Sublease Associate Optometrist- Apple Valley, CA- Target Optical Date: Apr 20, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92307 **Requisition Read more
*Apple* Systems Administrator - JAMF - Syste...
Title: Apple Systems Administrator - JAMF ALTA is supporting a direct hire opportunity. This position is 100% Onsite for initial 3-6 months and then remote 1-2 Read more
Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
Medical Assistant - Orthopedics *Apple* Hil...
Medical Assistant - Orthopedics Apple Hill York Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.