|
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
- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine