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 printers 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 dont 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 dont 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 dont 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 dont 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 applications 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 users 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, Im 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 dont 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. Its a good thing for me I dont 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 applications 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 dont need low memory globals, however, since I can share storage with my driver. The function DrvrStorage returns a pointer to the printer drivers private storage, and I access the driver variables from within my printing code. Programmers advice: dont 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 drivers 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 dont know. Presumably, either our printing code or applications will benefit from having available a copy of the last-used Print Record. I certainly dont 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. Its 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 ports 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 ports 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 ports 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 applications 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 hasnt 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 lines. 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 drivers global storage, where I have stashed information about the current print jobs 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 dont know whether this is the standard way to stop printing at this point, but it seems to work.) Then I cause the users 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 drivers control routine with a csCode = iPrIOCtl, lParam1 = the pointer to the base of the current lines text array, and lParam2 = the number of bytes to print. End of line is handled by calling the drivers 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 drivers 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. Dont 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 didnt 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 dont 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 users 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 drivers 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 its 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 cant 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 drivers private
* storage. We dont 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);; Its 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 wont 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 users
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 records 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 users 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){ /* Were 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 dont get
the hint, and attempt to spool print even when the print record says
otherwise.
*/
#includeprglobals.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 dont 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. Its 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.
*/
#includeprglobals.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