Dot Printer
Volume Number: 5
Issue Number: 10
Column Tag: Advanced Mac'ing
Dot Matrix Printer Driver
By Earle Horton, Hanover, NH
A Dot-Matrix Graphics Printer Driver
This article describes a Macintosh printer driver for use with a Tandy DMP-110 dot-matrix printer. The printer resource file is written to be device-dependent, but it can serve as a skeleton for creating printer files for other devices. The routines to translate QuickDraw BitMaps to DMP-110 graphics codes are localized, so that one would only have to rewrite a few routines to create a driver for another dot-matrix printer. The printing code is in a combination of assembler and C, and may be compiled with either MPW C and Asm, v 2.0.2 or Aztec cc and as, v 3.6c. All files necessary for compilation with either system are included, but the Aztec-specific files assume that you have the MPW Shell, MPW Make, and Rez.
This driver implements the Print Manager high level printing calls, so that any Macintosh application which uses them can print on this printer. I attempt to emulate as closely as possible the ImageWriter driver in high-resolution (Best) mode, subject to the physical limitations of the printer. I provide portrait mode printing only, with a choice of scaling factors. Anyone who has taken a college math course will recognize the spirit of the following sentence: "I leave Landscape Mode printing as an exercise for the reader." The driver also implements two low-level procedures to dump BitMaps to the printer, so that command-shift-4 will work.
The printer file is missing some features which can be found in Apple printer files. It does not implement the driver's low-level BitMap proc or, indeed, any of the low level printing calls. Users of most applications will not notice this lack, since few applications use low level printing. It does not participate in text positioning and measuring or "line layout" as do Apple printer drivers. This means that although the printer file works well with screen fonts like Geneva and New York, LaserWriter fonts like Times and Courier sometimes give it problems. This is a feature which should be added, but it might take lots of code to do it right.
The code used in the printer file does not access low memory globals, except as these are used in library routines. It does not use any development-system-specific tricks to obtain "global" variables for the driver or for any of the code resources used in the driver. It does not use hard-coded numbers for the ioRefNum of the serial port or of the printer driver, but rather opens drivers by name. Any access to the Print Manager, e.g. to set or detect a printer error condition, is done through glue routines or through the PrGlue trap. In view of these precautions, the code should be fully capable of being used with other development systems, and should be compatible with future releases of Apple System Software, providing Apple does not change the interface to printer files too much.
The Printer File:
This section describes the resources you must create in order to implement a driver of this type. The structure of a printer resource file was described in my previous article (MacTutor Volume 3, Number 11 and 12), so I will give the resource list here, arranged roughly by function, and only describe the contents briefly. (You can get a list like this using MPW Rezdet with the "-l" command line option.)
DMP-110 Resource List
'PDEF' (0, Purgeable, Locked) # Code resources
'PDEF' (4, "Dialogs", Purgeable, Locked)
'PACK' (-4096, "Chooser Device", Purgeable, Locked)
'DRVR' (-8192, ".XPrint", Purgeable, Locked)
'HEXA' (-8192, "Printer Settings") # Port settings
'ICN#' (128, "DMP-110") # Bundle
'FREF' (128)
'Dmp1' (0)
'BNDL' (128)
'STR ' (-4092, "Right Button")# For device package
'STR ' (-4093, "Left Button")
'STR ' (-4091, "List label")
'STR#' (-4080, "Baud Rates")
'DITL' (-8191, "Job dialog tempplate") # Style and Job dialogs
'DITL' (-8192, "Style Dialog Template")
'DLOG' (-8191, "Job Dialog")
'DLOG' (-8192, "Style Dialog")
'DLOG' (-8190, "Next Page Box") # Miscellaneous
'DITL' (-8190, "Next Page Template") # dialogs and alerts
'ALRT' (-4079, "Sys41Alert")
'DITL' (-4079, "Sys41 Items")
'ALRT' (-4078, "ATalk is on!")
'DITL' (-4078, "ATalk Items")
'ICON' (-4080, "DMP-110")
'PREC' (0, "Default Scale") # Print Records
'PREC' (1, "Last Used Print Record")
'PREC' (2, "Shrink To Fit")
'PREC' (3, "Exact BitMap")
'PREC' (4, "Compatible")
Four code resources are included. The printer driver ('DRVR' -8192) and the dialog code ('PDEF' 4) are necessary to any printer resource file. At least one type of printing code ('PDEF' 0, here) is also necessary. The Chooser device package, ('PACK' -4096) is necessary if we wish to have adjustable settings which are not logically part of the standard Style and Job dialogs. A 'HEXA' resource is included to save global settings; it is quite small.
The Chooser device package uses both buttons, and displays a string list of baud rate names in the Chooser's list box.
Four unique Print Record resources ('PREC') are used here, for four different printing styles. The same paper style is used in all four of them, but different scaling methods are used to obtain different printing effects. 'PREC' number 1 is used to store the last-used Print Record.
The rest of the resources are used in the dialogs and alerts, and the Bundle-associated information is used by the Chooser to display our printer file. Informative alerts and dialogs are provided for sheet feeding and for problem situations. An ICON resource containing the printer file icon is used to assure user identification of exactly who is putting up an alert. Alerts which might be displayed by the Chooser device package are given resource numbers which are in the legal range for the device package to use. Other Alerts and Dialog resources are given resource numbers belonging to 'PDEF' #0.
Cast of Characters: The Files
This section shows the source files used to build the printer resource file, and the icon used by my printer file.
The files with the 'Manx' icon ("Z") are used in creating the Aztec version of the printer file. The files with the MPW icon are for use with MPW C and Asm. The "Anonymous" files are for use with both systems. All are 'TEXT' files, with the exception of the printer file. Intermediate object, code and resource files are not shown.
Different source files exist for Aztec and for MPW for a number of reasons. Assembler syntax is different, so each system has its own "Glue" source file. Compiler and linker options, as well as mechanics of making the project, differ. Thus the two Makefiles. (Both use MPW Make, however.) The "?Final.r" file is different between the two systems because the Aztec linker refuses to create a non-'CODE' resource with ID zero, and it is more convenient to change this ID using the resource compiler. Finally, the file "AZHeaders.c" is used to create a precompiled header file for the Aztec C compiler. MPW C v 2.0.2 has no such feature.
Common files include two C language header files, a resource definition file, and four C language source files. The bulk of the project code is in C; the assembler files provide necessary headers for code resources, as discussed in the 1987 article.
This project uses the Pascal interface to all ToolBox routines, as introduced in MPW C, version 2, and as described in Appendix H of the MPW C manual. An esthetic problem with this approach is that it requires the programmer to type in certain routine names in UPPER-CASE, e.g. GETNAMEDRESOURCE() for GetNamedResource(). The header file "compat.h" uses preprocessor macros to hide this ugliness. When using Aztec C, including "-D_INLINE" on the C compiler command line and using "sys2:lib:azc.lib" accomplishes the same thing. All ToolBox and Operating System calls used in this printer driver use parameters as described in Inside Macintosh as a result. Points are passed by value, unless they are listed as "VAR" parameters in IM. Strings are always Pascal strings. The MPW Pascal style interface does not appear to include all the List Manager calls, but this is taken care of in the C source files rather than in the header file. Perhaps MPW 3.0 high-level languages will have a uniform programming interface for all IM calls.
The C code as supplied can use either "PrintTraps.h" or "Printing.h" to define Print Manager calls which are used. For compatibility with systems older than 4.1, define BACKWARD_COMPATIBLE in "dmp-110.h" so that "Printing.h" gets used. Use of the calls in "PrintTraps.h" results in smaller code. Use of "Printing.h" assures compatibility with older systems. For a driver for your own use, then, the newer header file is appropriate if you are running all your Macs with System 4.1 or newer. For distribution, the older header file is probably better.
The header file "DMP-110.h" is the current version of "Daisy.h" from the 1987 article. It defines constants and typedefs which are shared between the various code resources. It gets smaller each time I work on it as the code moves closer to the point where the various code resources can be independent of each other.
The main resource definition file, "DMP-110.rsrc.r," is Rez source for all the non-code resources used in the printer file. If you are planning on using Aztec C or another system to build a printer file based on this one, and don't have MPW Shell, then you have to convert this file, or get the source disk and decompile the resources in the printer file.
This article includes Aztec listings, but all files in the folder picture will be included in the source disk.
The table entitled "DMP-110 Files Listing" shows all the files which exist in the project folder after a successful "buildprogram dmp-110" using the MPW language tools.
DMP-110 Files Listing
Name Crtr Type
-------------------- ---- ----
AZFinal.r Manx TEXT
AZGlue.asm Manx TEXT
AZHeaders.c Manx TEXT
AZMakefile Manx TEXT
compat.h ???? TEXT
DMP-110 Dmp1 PRER
DMP-110.h ???? TEXT
DMP-110.rsrc RSED rsrc
DMP-110.rsrc.r ???? TEXT
Makefile MPS TEXT
MPWFinal.r MPS TEXT
MPWGlue.a MPS TEXT
MPWGlue.a.o MPS OBJ
MPWMakefile MPS TEXT
PACK RSED rsrc
PACK.c ???? TEXT
pack.c.o MPS OBJ
PDEF0 RSED rsrc
PDEF0.c ???? TEXT
PDEF0.c.o MPS OBJ
PDEF4 RSED rsrc
pdef4.c ???? TEXT
PDEF4.c.o MPS OBJ
PDriver RSED rsrc
PDriver.c ???? TEXT
PDriver.c.o MPS OBJ
Messing Around in Assembler: The Glue
The specially assembled headers for the printing code resources are concentrated in one source file, and are now much easier to deal with. The MPW code is assembled in modules, using the PROC?ENDP directive pairs. All modules are kept in one object file. When the glue is linked with each C object file, the linker option "-m <module>" is used to determine the start address of the code resource. You have to name the glue file first on the linker command line, and have the entry point be the first module in the glue file which is picked up by the linker, in order to have the header appear at the top of the linked code resource. The structure of the assembler headers was discussed in the 1987 article, and is the same here.
The Aztec glue is a little different, since the Aztec linker does not have a "-m" switch, and is not smart enough to pluck out the correct startup routine. Conditional assembly takes care of this. If you compile with Aztec, you just get four ".o" glue files instead of one. In order to prevent a conflict of entry points, the Aztec Makefile creates a special version of the runtime library with the default entry point removed.
The assembler glue defines some read-only constant data using "dc.b" and "dc.w" directives. This is because the MPW C compiler will not define string and array constants with PC-relative addresses. Since you have to have the glue anyway, why not make use of it? This is a convenient place to store device-dependent things like escape codes. Some C compilers do allow definition of string constants within source files which are to be linked into stand-alone code resources, but the trend appears to be to move away from this. Use of such also reduces portability.
The Aztec C libraries appear to be missing a glue routine for IsMPPOpen(). I have supplied assembler glue for this.
The Style Dialog: Image Scaling Strategies
This printer file includes a number of named 'PREC' resources. A list in the Style Dialog allows the user to choose between them. Only one paper size is currently allowed, but a number of scaling factors are supplied. Each style is appropriate for a particular kind of application program or printing effect. This is a convenient dialog to use during development, since the dialog code doesn't have to know a lot about the printer's capabilities, and you don't have to recompile to try out a different 'PREC'. The printing code figures out at run-time how big the paper is, and how to scale the image in the Printing Port before sending it to the printer. If you design a printer driver which allows a number of different options which are independent of each other, then a more elaborate dialog box might be appropriate.
When one of the list cells is selected and the dialog is confirmed, the dialog code copies the appropriate fields from the 'PREC' resource whose name matches the selected cell. The dialog code also records the resource number of the chosen 'PREC' in one of the private fields of the Print Record which it returns to the application, and uses this information to choose the style to select when putting up a new Style Dialog. This means that the last-chosen style for a document is "remembered" if the application saves the Print Record.
Most of the Print Records supplied with this printer file specify Picture-scaling to enlarge the printed image before sending it to the printer. In order to understand the effect of scaling on the appearance of the printed output, it is necessary to understand the differences which exist between the capabilities of applications to cope with scaling.
Paint programs print images strictly as BitMaps. Whatever you have on the screen is mirrored, bit for bit, in the printing GrafPort when using one of these programs. These usually cannot scale images themselves, nor do they produce images which can be scaled attractively. HyperCard is a perfect example.
Font-oriented programs, such as word processors, provide the ability to adjust the spacing between characters when printing justified text. This may cause problems when we try to print an image at a different resolution from that in which it has been drawn originally.
For scaling to be successful, a larger size font must exist, or the text will be printed in a scaled font, which will not be attractive. If we scale an image containing justified text by a non-integral factor, then quantization errors in text positioning cause justification to fail. This is a problem with the DMP-110, since there is no way to divide 72 dpi (screen) evenly into 120 dpi (paper). A way around this is to provide a Printing Port resolution of 60 dpi, and scale by 2, but this means the apparent size differs from screen to paper, and results are not really WYSIWYG.
Another problem arises when we scale by an integral amount, and the sizes of characters in the font family are not linear. For example, the capital 'A' in Times 12 has a width of 8, but the capital 'A' in Times 24 has a width of 17. If the character sizes are not linear, then justification fails when the Picture is enlarged, even if by an integral amount. Screen fonts like Geneva always print with justification preserved if the output Picture is scaled by an integral amount, because character sizes are linear. LaserWriter fonts, on the other hand, seldom print correctly under these conditions, because character sizes are not linear. The printer file needs to exchange some information with the Font Manager in these cases, so that non-linear fonts will be positioned correctly. Since I don't do this, my printer file will not print LaserWriter fonts so that the right ends of justified lines are properly lined up.
The most "advanced" printing application type is a program which handles images as a collection of objects. The size of an object is determined using non-screen units such as inches or centimeters, and the program can handle practically any printer resolution you throw at it. Since these programs do not store images as collections of bits, but rather as drawing instructions, they are (should be) able to draw an object to the correct scale no matter what the printer resolution or paper size. The only real limitation here is that, again, scaling of text is limited by the collection of installed fonts. Examples are MacDraw and similar object-oriented drawing programs, CAD programs, and VersaTerm PRO when printing TekPrint files.
The Print Records provided by this dialog provide dot-addressable graphics for BitMap and drawing programs, a "Compatible" or near WYSIWYG mode, and a scaling factor which preserves text justification while allowing use of screen fonts, but not all three at once. With another printer and a more ImageWriter-like resolution, you may do better. Remember that the numeric values stored in my 'PREC's will not produce attractive results with your printer, unless it has the same resolution as mine. The comments in the resource definition file show what I try to achieve with each Print Record.
A certain amount of experimentation is necessary to determine what the Style Dialog should put in the Print Records, in order to achieve any special effects which your printer is capable of. I recommend using a simple interface such as that above while you are experimenting with printing styles, and then move to a more elaborate, customized dialog when you have determined what kind of results you get from your printer and your Macintosh.
The Device Interface: Talking to the Chooser
The file type for this printer file is 'PRER', so that we can use a Chooser Device Package interface to solicit configuration information from the user. The Chooser's list is used to put up a list of baud rates, and the two Chooser buttons are used to select which serial port to use. Because this printer file is written to be device-dependent, there are no further options which may be set from the device package. Device-dependent parameters like escape codes are not changed by the user.
When the user clicks one of the buttons or list items, the device package would like to communicate the new settings to any currently open printer driver which belongs to us. This is implemented here simply by changing the 'HEXA' resource and writing it out to the resource file. Since this printer file implements high-level printing jobs and screen dumps only, each printing request is treated as a separate print job, and the new settings will be picked up before any more printing is done. If you decide to implement low-level calls, then you have to figure out a way to communicate settings changes from the device package to the driver. This is not easy.
The device package checks for a system version of 4.1 or newer, and puts up an error Alert if the device is selected, the driver was compiled to use "PrintTraps.h," and the system is too old. If AppleTalk is active and the printer port button is clicked, then the device package informs the user that this is an impossible choice. (There is no way to dim the left button, except by emptying the Chooser's list.)
The Driver:
This driver is similar in structure to the driver described in my previous article, but it does have some significant enhancements. The driver does not use any development-system-specific tricks to maintain private storage. Each active routine in the driver performs a single function, then cleans up after itself when done. Support for low-level printing functions is removed.
The driver only does screen dumps. The screen dump routines use essentially the same algorithms that are used by the high-level 'PDEF' code, so I won't discuss them in any detail. They will, however, have to be changed if your printer accepts different graphics codes from the ones mine uses. The hardest task is to find the Rect which encloses the top window, since the portRect doesn't include the structRgn of a window. If anyone has a better method of finding this Rect, I would be interested in hearing from you.
The Open, Close, and Status routines simply return noErr. The Control routine opens the chosen serial port, dumps the screen or top window if called for, closes the serial port, and logically should return an error code if an error occurred during printing. If the control routine does return an error code, however, the 'FKEY' which calls it for a screen dump just tosses the error code in the byte bucket, assumes that the driver was not open, opens it, and calls the control routine again. For this reason, this driver control routine always returns noErr. The driver has no global storage, doesn't do anything with it's device control entry, and is in general quite friendly to other parts of the System.
Coping with Errors: Making the Best of a Bad Time
This driver does much more complete error checking than the driver in my previous article did.
My driver is a 'PRER' device, so it gets to use the Chooser Device Package interface for such things as baud rate selection. This also means it does not get the "automatic" serial port selection which the Chooser does for 'PRES' devices like the ImageWriter. A common user error is to turn on AppleTalk when the driver has been configured to use the printer port. When printer driver is opened it finds AppleTalk on, but its configuration resource tells it to use the printer port. The Alert box above tells the user that this has happened, and the driver closes itself. Other open errors are possible, but much less common. These are handled by simply closing the driver, and returning an error code.
Aside from errors which may occur during the driver's open routine, there are three types of error which I look for in the 'PDEF'. First, the user may type command-'.' at any time during printing. The routine pointed to by the prJob.pIdleProc in the print record is called often during high-level printing, and sets PrSetError() to other than noErr when this happens. (The default routine which I supply now uses iIOAbortErr instead of iPrAbort for this purpose.) The printing code allocates temporary output buffers to use when translating BitMaps, and sets PrSetError() to memFullErr if NewPtr() ever returns nil. Finally, errors may occur during a write attempt to the serial port. Serial port errors are rare, but should be anticipated.
Another possible error is that free memory might run out when the application is drawing into the Printing Port, and the system is filling a PicHandle with drawing commands. The only thing I do about this is hope that it doesn't happen!
The procedure for handling aborts in printing 'PDEF's and in the driver is discussed below.
The top-level routine calls setjmp() before sending any characters to the printer, and stores the jmp_buf[] array and IO Parameter block in a data structure which is passed down the calling sequence. All serial port output is done through the routine asyncwrite(), which takes a pointer to the data structure and a pointer to an idle routine as parameters. Asyncwrite() issues an asynchronouswrite request to the serial driver, and repeatedly calls the idle routine until the request has been completed. (The ioResult field of the parameter block used to issue the write request will remain positive until the write request has been completed.) If a user abort (command-'.') occurs, then the idle routine calls PrSetError() with iIOAbortErr. (The default idle routine I include does this, but an application-supplied routine may arrange for printing to be halted in another way.) After the idle routine returns, asyncwrite() checks PrError() to see if an abort or error has been logged.
If asyncwrite() detects that an abort or error has occurred during the write, it first calls PBKillIO() to kill any pending serial port output, then calls longjmp() to get back to the top level printing routine. The top level of the control routine does any necessary cleanup, and then calls PrSetERror(iIOAbortErr).
When the write request is complete, asyncwrite() checks the ioResult field of the IO Parameter block for the presence of an error code, and longjmp()s back to the main level if it finds one.
Since a printer is a slow device, it is important to detect errors quickly, particularly user abort errors. A user abort may mean a mechanical problem with the printer, so we want to detect it and kill any pending output at the earliest possible moment. This method provides that ability, but it does have one problem with the DMP-110 printer. High resolution graphics are sent to the printer by first sending an escape sequence telling the printer to expect a number of two-byte codes between 1 and 959, then sending the codes. If the printing code is sending a sequence of graphics codes, all of which are non-zero, then the codes will be sent all at once. This can happen if the image contains a horizontal line, for instance. If an abort is sensed in the middle of a long sequence of graphics codes, then the printer will be left hanging, expecting perhaps several hundred more graphics codes. Since all characters are valid graphics codes in high resolution mode, there is no way to get the printer out of this state, short of shutting it off and turning it on again. This may cause the user some unpleasantness by leaving the printer in an undefined state, but perhaps less than if the driver tries to print to the end of the line, while the ribbon or paper is jammed. If your target printer has a means of resetting itself with a serial port command at this time, you would want to send the reset command before the control routine returns.
A possible source of problems under MultiFinder is that the printer driver is a shared resource, and so it is highly unlikely that the printer driver's close routine will ever get called when an application exits. This means that the serial port might get left open, and the user can't use it for something else. For this reason, the driver doesn't maintain any storage, and closes the serial port before it returns. Each screen dump or high-level printing job is handled separately, and everything is cleaned up afterwards. This means that if the user changes printers, or wants to use a terminal program on the same serial port after she is done printing, then she won't have any problems doing so.
Finally, there is one more source of errors, if the programmer decides to use "PrintTraps.h" instead of "Printing.h." This is best explained by the the Alert box:
My copy of the printer file uses "PrintTraps.h" because (a) it is strictly for my personal use, and (b) I use the newest system file I can get at all times. If you want to write a printer file for commercial use, then "Printing.h" is highly recommended. Using "Printing.h" adds about 2k to the size of the complete printer file. If you plan to write a printer file for a color device which uses Color QuickDraw, or need any features in System 4.1 for any other reason, then you might as well go ahead and use "PrintTraps.h."
Background Printing: Working with MultiFinder
The default idle procedure used when high-level printing is active calls WaitNextEvent() if the trap is implemented, and SystemTask() and GetNextEvent() otherwise. This allows a user to put a printing application into the background under MultiFinder, and allows desk accessories to continue running under UniFinder. Combined with the asynchronous method of writing to the serial port, this gives the user use of the machine when the serial driver is waiting to send characters. In the screen dump above, the Finder is printing the directory of the "src" folder to the DMP-110 printer, and ResEdit is running in the foreground. Performance of ResEdit is just fine under these conditions. This is not the scheme that Apple has chosen to use for background printing, but pending documentation of Apple's scheme it is an acceptable alternative.
One problem with this scheme is that most applications will put up a dialog box when printing similar to the Finder's, above. MultiFinder doesn't like to swap out an application when the front window is a dBoxProc type of window. Changing the procID of the Finder's printing dialog box to documentProc allows the user to freely swap the Finder in an out of the foreground while printing, and the Finder doesn't know a thing about it! Setting the "Can Background" bit in the application's 'SIZE' resource is also necessary to an older application to be given background time if you swap it out while printing.
Printing speed seems to be about the same under these conditions as it is with the printing application in the foreground or operating without MultiFinder. With asynchronous writes, and the limiting factor being the speed of the print head, there is usually a lot of free time available when printing.
The driver's procedure for dumping the screen does not call WaitNextEvent(), for the simple reason that allowing the user to move windows around would really muck up the output!
BitMap to Dot-Matrix: Nuts and Bolts
This section shows how I transform a QuickDraw BitMap to a series of graphics codes which my printer can understand. It is necessarily device-dependent. Your output format may vary, depending on the characteristics of your printer, but your input will be the same if you are talking to a black-and-white printer. Writing a printer file for a color printer is "left as an exercise for the reader."
The DMP-110 accepts high-resolution printing codes in the following format. A two-byte escape code is used to signal high-resolution graphics. This is followed by another two-byte code which tells it how many columns to print. Each 16-dot column is specified by a two-byte code. In order to print n columns of graphical data we send the two-byte graphics code, two-bytes to specify n columns, and then n*2 bytes of graphical data. There are 959 possible 16-dot columns on one line, for a printable width of just under eight inches.
The first byte specifies the top eight dots, and the second specifies the bottom eight dots. The low-order bit in the first byte is on if the top dot in the first column is to be printed, off otherwise. The high-order bit in the first byte is responsible for the eighth dot in the first column. The second byte in the two-byte sequence accounts for the ninth through sixteenth dots in the first column. This is continued until 2*n bytes are sent, with the last byte specifying the bottom eight dots in the last dot-column. There is also a four-byte sequence for positioning the print head at any of the possible 959 printing positions.
Because of the way the 68000 series processors store numeric quantities in RAM, the first or top dot corresponds to the eighth bit in the first byte sent, which is the least significant bit. This is very important to remember because the Toolbox Utilities routines which are used to move bits around access them in the opposite order. In order to copy a bit from a BitMap into a dot-position in the first or any other column, we have to take into account the logical bit-reversal which occurs during this process. A sixteen pixel deep Rect in the source BitMap is copied to an output buffer for the DMP-110 in the following fashion.
A double loop loops over the dot-columns to be printed, up to 959 in number. Two inner loops fill the top and bottom bytes with bits from the source BitMap. A QuickDraw BitMap consists of bytes which are laid on their sides, so that the first pixel in the first row is specified by the first bit in the data pointed to by the baseAddr field of the BitMap. This bit is mapped to the least significant bit in the first byte in the output buffer. In the scheme used by the Toolbox Utility routines, the first bit in the source BitMap is mapped to the eighth bit in the output buffer. The seventh bit in the first output byte comes from the first bit in the second row of the source BitMap. The first inner loop fills the first output byte with the first bits from each of the first eight rows in the source BitMap, and the second inner loop uses the first bits from the ninth through sixteenth rows of the BitMap to fill the second output byte. The outer loop continues until all of the dot-columns in one 16-dot high printing row have been filled.
Landscape style printing should be easier to implement. If the BitMap is scanned in the correct direction then bits within bytes will not have to be moved around, but only whole bytes.
A number of schemes may be used for sending the output buffer to the printer at this point. The entire row of graphics codes might be sent at one time, simplifying the computation. If, however, the output page will contain many white areas, it is advantageous to reposition the print head during printing so as to skip over white areas. The DMP-110 requires four bytes to position the head, and two bytes to print a single blank column. Accounting cost in terms of number of bytes sent through the serial port, if we skip over three blank columns we win, if we skip over two columns we break even, and if we skip over one column, we lose. How to optimize the printing process is harder to figure out for a whole row, but it is probably correct to assume that white columns will be grouped together, and that some improvement is to be expected by skipping over any blank dot-column. The printing head is then positioned before the next non-blank printing area. More improvement might be expected by skipping over only blank dot-columns where they are grouped, but at the expense of having to write more code.
A BitMap is printed in this scheme by first breaking it up into areas which are sixteen pixels deep. Each such area is converted and sent to the printer, followed by a carriage return and a high-resolution graphics linefeed. By looping over the whole BitMap, an exact image of the BitMap is made on paper.
This general scheme will work for any dot-matrix printer which accepts byte codes to specify a dot-column, if it is first modified to use the device-dependent protocol needed by the printer. Changes to this source code to work with another printer should involve changing escape codes and reworking the BitMap translation routines only.
First, look in the printer manual to determine how many vertical dots are printed in each dot-column. This determines how many rows of the source BitMap may be printed at once. Second, find out how the dots in a column are encoded into bytes to be sent to the printer. This will most probably be top-dot-first using Intel or VAX ordering. Third, map the bits in the BitMap to a suitable output buffer. Fourth, and this is optional but recommended, determine whether optimization of output byte codes to take advantage of repositioning the print head is possible. Fifth, send the printing codes to the printer along with the proper device-dependent escape codes and a graphics line feed. Finally, if the printer is capable of using form feed codes to separate pages, send the form feed at the end of the page. Otherwise, advance the paper using more line feeds.
BitMap to Band: Capturing the Image
This section details how to trick an application program into drawing a picture in our printing GrafPort, how to "play back" the picture in bands using DrawPicture(), and how to send the bands on to the BitMap translation routines. In order to write a printer file for another device, the size of the BitMap, the size of the GrafPort's portRect, and the number of bands will have to be tailored to your printer. The general principles remain the same, and I believe that the method I use here is a good general purpose method for capturing QuickDraw images to be sent in high-resolution mode to any dot-addressable device.
My code does not take advantage of a printer's native fonts, since all information is printed in graphics mode. It relies on QuickDraw and on Macintosh screen fonts for all text printing. How to combine graphics and use of a printer's native fonts is left as "an exercise for the reader" or perhaps a future article.
The first routine to be called in a printing job is PrOpenDoc(). This routine sets up the fields of a Printing GrafPort (TPrPort) to match those in the Print Record passed to it, allocates necessary storage, and initializes variables used in the printing job. The lGParam[1-4] fields of the TPrPort are used to hold all printing variables particular to this printing job, and dynamic storage is allocated in a tree structure branching from pointers stored in these fields, which are given preprocessor names for readability. This routine allocates a total of about 3k of storage. If the storage cannot be found, then it calls PrSetError(memFullErr), and leaves things in a state where PrCloseDoc can clean up properly. The application will call PrCloseDoc if there is an error logged after this routine returns.
Use of distinct blocks of storage to hold the printing variables complicates the process of allocating and disposing of them, but allows for the use of an output BitMap whose size is determined at run time.
The dimensions of the TPrPort are taken from the prInfo.rPage field of the Print Record, and the dimensions of the port's BitMap are taken from the prXInfo field. The bounds Rect of the BitMap is sized so as to be able to break up the portRect into several "bands" used in printing. These must agree with the physical capabilities of the printer. The serial port is opened for this printing job, and a pointer to the TPrPort is returned to the caller.
The PrOpenPage() routine is called before every page to be printed. It sets the port to the Printing Port, and opens a new picture. All subsequent drawing in this port is saved in the picture, whose handle is saved in one of the lGParam fields of the Printing Port.
The PrClosePage() routine does all the work of translating and sending the saved picture to the printer. It first calls ClosePicture to finish the picture definition. Then the portRect of the Printing Port is resized according to the information in the prInfoPT field of the Print Record, so as to reflect the final page size. This information is taken from the Print Record, and not from the code, to allow for easier adaptation to other printers. Note that you don't have to print the picture at this point, you can do anything you want to with it. You could for instance save the images printed by an application in a picture file or a paint file.
Printing is accomplished as follows, for each band in the printing page:
The Printing Port's BitMap is first aligned with the band to be printed, using the MovePortTo() Toolbox call. The Printing Port's portRect is erased. DrawPicture() is called to draw the application's picture in the expanded portRect, thus correctly filling the bits in the BitMap for the band to be printed. The routine dumpbits() is then called to convert and send the band to the printer.
This process is repeated until the entire picture has been printed, and then KillPicture() disposes of the picture Handle.
Since this is the routine which communicates with the serial port, and it also calls DrawPicture() several times, this is the routine wherein the printing code spends the most time. The prJob.pIdleProc routine should be called repeatedly whenever the printing code is waiting for the serial driver to finish a write request. This is done in the routine asyncwrite(), which also checks serial port errors. If this routine ever senses an abort attempt or a serial port error, then it longjmp()s back to the top of PrClosePage, which calls PrSetError() and returns.
When the last page has been printed, then the application calls PrCloseDoc, which closes the serial port and the Printing Port, reclaims all dynamic storage that was successfully allocated, and exits.
Putting It All Together: Compiling and Stuff
Compiling the sources is perfectly straightforward, if you have the MPW Shell and either MPW C and MPW Asm, or Aztec cc and Aztec as. Copy the files from the source disk into a new folder, copy either "MPWMakefile" or "AZMakefile" to "Makefile," and type "BuildProgram install." This builds the driver, and installs a copy of it in the active System Folder. If you are using Aztec C, the script generated by BuildProgram will ask you to find an original copy of "sys2:lib:azc.lib." This is copied to "{clib}tools.lib" and modified for use with this project, by deleting the module containing crt0.o. This step is necessary so that the linker will use my assembler glue as the entry point, and not crt0. Also make sure that when using Aztec cc, your {INCLUDE} environment variable points to the directory containing the MPW-compatible header files, and not the older Aztec header files.
If you do not have the MPW Shell, and want to build the printer file using Aztec Make, then you will have to modify AZMakefile to use Aztec Make syntax. Since I purchased the "cheap" Aztec C package, I did not get Aztec Make with it. If you also bought the "cheap" Aztec package, but do not have MPW Shell, then you will have to write a script to compile and link the files, or (ugh!) type in the commands by hand.
Also, MPW users might want to go through their "Printing.h" and "PrintTraps.h" files and replace every single instance of the keyword "int" with "short." This has nothing to do with this project, but it will allow you to call PrGeneral from other programs. Presumably, this problem has been fixed in MPW C v 3.0.
Results: What Does it Look Like?
It is difficult to include samples of printed output, since MacTutor probably uses a page setup program, and would have to scan output I sent them and then paste it back into the final article before printing. We can only hope.
The most serious flaw with the choice of printer is that the resolution is not ImageWriter-compatible. This is a hardware limitation of the device I am using, and may not affect your efforts.
The main shortcoming of this printer file is that it does not participate in line layout at all, and thus cannot reproduce justified text with LaserWriter fonts if the output image is scaled. I haven't begun to investigate what is required to do this properly, and so I don't know how much work is involved in implementing this feature. Images containing linear screen fonts like Geneva, New York, and Symbol always seem to come out right if the scale factor is an integer.
Bit-mapped images do not produce the same size printed output as would the ImageWriter or the LaserWriter in ImageWriter-compatible (default) mode. Paint images which are scaled by an integral factor come out bigger or smaller than they look on the screen as a result, but all the bits are there, and in place. You will not have this problem if your printer is capable of 72 dpi, or a multiple thereof.
Word processor programs produce acceptable output if a screen font exists in the correct size for QuickDraw to use when DrawPicture() is called. For example, if a 9-point font is used, the image is doubled in size before printing, and an 18-point font can be found whose character sizes are twice those of the 9-point font, then text is positioned properly on the output, and justification works properly. More work is necessary to make the printer file work properly in all cases with LaserWriter fonts and others with non-linear character sizes.
Drawing programs like MacDraw work the best with this printer file, since these do the work of scaling by themselves. With a drawing program, one uses a Print Record which calls for no scaling on the part of the printing code, and the drawing program can then place and size objects to best advantage. These programs look in the prStl field of the Print Record to determine the paper size, and thus the correct size of the printed object. Such programs are capable of producing identically-sized output with my printer and my driver as they do with Apple printers and Apple drivers.
Although this printer file has some shortcomings, it does allow printing of Macintosh graphics on my DMP-110, which had been gathering dust since I stopped using my Radio Shack Color Computer. Results are in most cases attractive, and it suffices for most of the draft graphics printouts I need. The missing features are left for the subject of a future article, or perhaps an exercise for the reader.
Listing: MPW MakeFile
# file is part of DMP-110 printer driver for the Macintosh
# series of computers.
# Earle R. Horton Wednesday, November 30, 1988
# Makefile for DMP-110 v1.0.
# This is the Makefile for use with MPW C, v 2.0.2.
CFLAGS = -g
.c.o .c
C {CFLAGS} {default}.c -o {default}.c.o
.a.o .a
Asm {default}.a -o {default}.a.o
DMP-110 PACK PDEF0 PDEF4 PDriver DMP-110.rsrc mpwfinal.r
Rez -o DMP-110 -c 'Dmp1' -t 'PRER' mpwfinal.r
SetFile DMP-110 -a B
DMP-110.rsrc DMP-110.rsrc.r makefile
rez -o DMP-110.rsrc -t 'rsrc' -c RSED DMP-110.rsrc.r
PACK MPWGlue.a.o pack.c.o
link MPWGlue.a.o pack.c.o
-m PACKENTRY -d
{libraries}interface.o -rt PACK=-4096 -sn Main="Chooser Device"
-ss 100000 -o PACK -t 'rsrc' -c RSED
PDriver MPWGlue.a.o PDriver.c.o
link -m DriverEntry -d MPWGlue.a.o PDriver.c.o {clibraries}cinterface.o
{libraries}interface.o {clibraries}CRunTime.o {clibraries}StdCLib.o
-rt DRVR=-8192 -sn Main=.XPrint
-ss 100000 -o PDriver -t 'rsrc' -c RSED
PDEF0 MPWGlue.a.o PDEF0.c.o
link -m PRINTENTRY -d MPWGlue.a.o PDEF0.c.o {clibraries}cinterface.o
{libraries}interface.o {clibraries}CRunTime.o
{clibraries}StdCLib.o -sn Main="Draft Printing Code"
-rt PDEF=0 -ss 100000 -o PDEF0 -t 'rsrc' -c RSED
PDEF4 MPWGlue.a.o PDEF4.c.o
link -m DIALOGSENTRY -d MPWGlue.a.o PDEF4.c.o {clibraries}cinterface.o
{libraries}interface.o -sn Main=PrDialogs
-rt PDEF=4 -ss 100000 -o PDEF4 -t 'rsrc' -c RSED
install DMP-110
duplicate -y DMP-110 "{systemfolder}DMP-110"
clean
delete -i "{systemfolder}testDMP-110"
'files -m 300 -t rsrc ;files -m 300 -t PRER;files -m 300 -t 'OBJ
''
PDEF0.c.o DMP-110.h
PDEF4.c.o DMP-110.h
PDriver.c.o DMP-110.h
pack.c.o DMP-110.h
Listing: compat.h
/* FILE is intended for use with MPW C 2.0 Interface files
* which generate InLine code for routines that call the
* ToolBox with points by value & strings as Pascal strings.
* It should be included AFTER any Macintosh #include files.
* See Appendix H of the MPW C 2.0 manual for details.
* file allows use of MPW C 2.0 standardized ToolBox calls
* using spelling from Inside Macinstosh, and not all upper
* case, as is used in MPW C 2.0 header files. Please note
* that use of this means that you no longer have access
* to the C-language versions of the ToolBox calls.
* It is rumored that this file will no longer be necessary
* with MPW C 3.0. (?)
* If you like to pass C strings to ToolBox, do NOT include
* this FILE. */
#ifdef MPU68000 /* Aztec C, v 3.6c. */
/* -D_INLINE on cc's command line is all that's required. */
#else
#define NewControl NEWCONTROL
#define SetCTitle SETCTITLE
#define GetCTitle GETCTITLE
#define DragControl DRAGCONTROL
#define TestControl TESTCONTROL
#define TrackControl TRACKCONTROL
#define FindControl FINDCONTROL
#define OpenDeskAcc OPENDESKACC
#define FindDItem FINDDITEM
#define NewDialog NEWDIALOG
#define ParamText PARAMTEXT
#define GetIText GETITEXT
#define SetIText SETITEXT
#define NewCDialog NEWCDIALOG
#define GetVol GETVOL
#define SetVol SETVOL
#define UnmountVol UNMOUNTVOL
#define Eject EJECT
#define FlushVol FLUSHVOL
#define Create CREATE
#define FSDelete FSDELETE
#define FSOpen FSOPEN
#define OpenRF OPENRF
#define Rename RENAME
#define GetFInfo GETFINFO
#define SetFInfo SETFINFO
#define SetFLock SETFLOCK
#define RstFLock RSTFLOCK
#define GetFontName GETFONTNAME
#define GetFNum GETFNUM
#define LCellSize LCELLSIZE
#define LClick LCLICK
#define LNew LNEW
#define NewMenu NEWMENU
#define AppendMenu APPENDMENU
#define SetItem SETITEM
#define GetItem GETITEM
#define InsMenuItem INSMENUITEM
#define MenuSelect MENUSELECT
#define EqualString EQUALSTRING
#define RelString RELSTRING
#define UprString UPRSTRING
#define DIZero DIZERO
#define NumToString NUMTOSTRING
#define StringToNum STRINGTONUM
#define SFPutFile SFPUTFILE
#define SFPPutFile SFPPUTFILE
#define SFGetFile SFGETFILE
#define SFPGetFile SFPGETFILE
#define IUDateString IUDATESTRING
#define IUDatePString IUDATEPSTRING
#define IUTimeString IUTIMESTRING
#define IUTimePString IUTIMEPSTRING
#define IUCompString IUCOMPSTRING
#define IUEqualString IUEQUALSTRING
#define DIBadMount DIBADMOUNT
#define DrawString DRAWSTRING
#define StringWidth STRINGWIDTH
#define StuffHex STUFFHEX
#define AddPt ADDPT
#define SubPt SUBPT
#define EqualPt EQUALPT
#define PtInRect PTINRECT
#define Pt2Rect PT2RECT
#define PtToAngle PTTOANGLE
#define PtInRgn PTINRGN
#define StdText STDTEXT
#define CreateResFile CREATERESFILE
#define OpenResFile OPENRESFILE
#define OpenRFPerm OPENRFPERM
#define GetNamedResource GETNAMEDRESOURCE
#define Get1NamedResource GET1NAMEDRESOURCE
#define GetResInfo GETRESINFO
#define SetResInfo SETRESINFO
#define AddResource ADDRESOURCE
#define GetAppParms GETAPPPARMS
#define OpenDriver OPENDRIVER
#define TEGetOffset TEGETOFFSET
#define TEGetPoint TEGETPOINT
#define TEClick TECLICK
#define NewString NEWSTRING
#define SetString SETSTRING
#define GetIndString GETINDSTRING
#define DeltaPoint DELTAPOINT
#define ShieldCursor SHIELDCURSOR
#define NewWindow NEWWINDOW
#define SetWTitle SETWTITLE
#define GetWTitle GETWTITLE
#define NewCWindow NEWCWINDOW
#define GrowWindow GROWWINDOW
#define DragWindow DRAGWINDOW
#define TrackGoAway TRACKGOAWAY
#define FindWindow FINDWINDOW
#define PinRect PINRECT
#define DragGrayRgn DRAGGRAYRGN
#define TrackBox TRACKBOX
#endif
Listing: DMP-110.h
/* Earle R. Horton.
* Wednesday, November 30, 1988
* All rights reserved.*/
#ifndef __DMP__
#define __DMP__
/* Define BACKWARD_COMPATIBLE to use printing glue,
* rather than _PrGlue trap. Penalty is about 2k size in
* final printer resource file.*/
#ifdef BACKWARD_COMPATIBLE
#include <Printing.h>
#else
#include <Printtraps.h>
#endif
#include <setjmp.h>
#include <Devices.h>
#include <AppleTalk.h>
#include <Files.h>
#include <Serial.h>
#define PrintErr (*(short *)(0x0944)) /* Not used if PrGlue trap found.
*/
#define SHEETDIALOG (-8190)
#define DONEITEM 1
#define STOPITEM 2
#define RES1ID (-4080)
#define RES2ID (-8192)
#define SYSNEEDED (0x0410)
#define ATALKALERT (-4078)
#define SYSVERSALERT (-4079)
#define iPrRelease 3
#define GRAFREZZ 120
#define NORMALREZ 80
#define COMPATREZ 72
#define VERSION 3
#define iFMgrCtl 8
#define iPrEvtCtl 6
#define lPrLFEighth 0x0003FFFE
#ifndef FALSE
#define FALSE 0
#define TRUE 1
#endif
#define iPrPgMax 9999
#define iPrPgFst 1
typedef struct {
struct QElem *qLink;
short qType;
short ioTrap;
Ptr ioCmdAddr;
ProcPtr ioCompletion;
OSErr ioResult;
char *ioNamePtr;
short ioVRefNum;
short ioCRefNum;
short csCode;
long lParam1;
long lParam2;
long lParam3;
} PrParam,*PPrParam;
typedef struct{
short dummy[5];
} pconfig,*Pcfg,**Pfg;
#define pport ((*settings)->dummy[0])
#define pbaud ((*settings)->dummy[1])
#define XonXoff ((*settings)->dummy[2])
#define MAGIC 'Dmp1'
#define PORTOPEN 'OPEN'
#define dOpened 5
typedef struct{
ParamBlockRec iopb; /*writing to serial driver. */
jmp_buf abortbuf;
Ptr obuf;
}Dstorage,*DPstorage,**DHstorage;
#endif
Listing: MPWGlue.a
;; Glue for Printer Driver code resource headers MPW C.
;; header for each type of resource is placed in a separate
;; module using Asm PROC directive. The "-m" option to the
;; linker makes it the entry point to linked code resource.
;; Earle R. Horton. All rights reserved.
;; Tuesday, December 13, 1988
;; Some of Glue routines contain data. This is read-only!
CASE ON
SEG 'Main'
INCLUDE 'SysEqu.a'
myDrvrFlags EQU ((1<<dCtlEnable) + (1<<dStatEnable) + (1<<dNeedGoodBye))
<< 8 ; Flag byte.
;; The header for the 'DRVR' resource.
DriverEntry PROC EXPORT ;; 'DRVR' starts here.
IMPORT myPrOpen ; open routine
IMPORT myPrPrime ; prime
IMPORT myPrControl ; control
IMPORT myPrStatus ; status
IMPORT myPrClose ; close
CODE
DC.W myDrvrFlags ; control and status enable only
DC.W 0 ; doesn't need time
DC.W 0 ; no events
DC.W 0 ; no menu
DC.W myPrOpenCall-DriverEntry ; Offsets to driver routines.
DC.W myPrPrimeCall-DriverEntry
DC.W myPrControlCall-DriverEntry
DC.W myPrStatusCall-DriverEntry
DC.W myPrCloseCall-DriverEntry
STRING PASCAL
DC.B '.Print' ; Driver name as Pascal string
DC.W 0 ; Zero for word-align.
dummyheader ; Make it look like a procedure for
LINK A6,#0 ; MacsBug
myPrOpenCall
PEA myPrOpen ;; SP -> desired driver routine.
BRA.S CallDriver ;; Jump interface to call real routine.
myPrPrimeCall
PEA myPrPrime ;; Times 5.
BRA.S CallDriver
myPrControlCall
PEA myPrControl
BRA.S CallDriver
myPrStatusCall
PEA myPrStatus
BRA.S CallDriver
myPrCloseCall
PEA myPrClose
CallDriver
MOVE.L A1,-(SP) ;; Push ptr to dev control entry.
MOVE.L A0,-(SP) ;; Push pointer to parameter block.
MOVEA.L 8(SP),A0 ;; Get routine address.
JSR (A0) ;; Call driver routine.
;; Result is in D0.
MOVE.L (SP)+,A0 ;; Restore registers.
MOVE.L (SP)+,A1 ;; C programs leave stack alone.
ADDQ.W #$4,A7 ;; Fix up stack pointer.
BTST #$01,$0006(A0) ;; Immediate call?
BNE.S StdReturn ;; Yes, regular return.
MOVE.L JIODone,-(sp) ;; No, return via IODone
StdReturn
RTS
label ;; This is for MacsBug.
UNLK A6
RTS
STRING ASIS
DC.B 'DMP-110 '
ENDP
;; The header for the Chooser Device Package.
CASE OFF
PackEntry PROC EXPORT
IMPORT device
BRA.S devicejump
deviceID
DC.W 0
Packname
DC.L $5041434B ; 'PACK'
IDnumber
DC.W $F000 ; -4096
Version
DC.W 1
Flags
DC.L $0C00E000
devicejump
JMP device
ENDP
;; The header for the Job and Style Dialogs code.
CASE OFF
DialogsEntry PROC EXPORT
import MyPrintDefault
import MyPrStlDialog
import MyPrJobDialog
import MyPrStlInit
import MyPrJobInit
import MyPrDlgMain
import MyPrValidate
import MyPrJobMerge
bra.w MyPrintDefault
bra.w MyPrStlDialog
bra.w MyPrJobDialog
bra.w MyPrStlInit
bra.w MyPrJobInit
bra.w MyPrDlgMain
bra.w MyPrValidate
bra.w MyPrJobMerge
ENDP
;; Routines to call Pascal procs which are known by address
;; and not by name. (ProcPtr glue).
CASE OFF
CallDlgInit PROC EXPORT
Export CallItemProc
CallItemProc
MOVE.L (SP)+,A0 ; Pop return address.
MOVE.l (SP)+,A1 ; Pop actual function address.
MOVE.L A0,-(SP) ; Push return address.
JMP (A1) ; Call the Pascal Function
ENDP
;; The header for the printing code.
CASE OFF ; If main code is in Pascal.
PrintEntry PROC EXPORT
IMPORT myPrOpenDoc
IMPORT myPrCloseDoc
IMPORT myPrOpenPage
IMPORT myPrClosePage
bra.w myPrOpenDoc
bra.w myPrCloseDoc
bra.w myPrOpenPage
bra.w myPrClosePage
ENDP
CASE ON
serialdata PROC EXPORT
; This module consists of read-only data.
baud300 EQU 380
baud600 EQU 189
baud1200 EQU 94
baud1800 EQU 62
baud2400 EQU 46
baud3600 EQU 30
baud4800 EQU 22
baud7200 EQU 14
baud9600 EQU 10
baud19200 EQU 4
baud57600 EQU 0
EXPORT AOutName,BOutName,BaudRates,prhireslf
; Handy storage place for constant strings & data which can
; be copied by the Driver routines.
STRING PASCAL
AOutName
DC.B '.AOut'
BOutName
DC.B '.BOut'
STRING ASIS
prhireslf
dc.b 3,26,27,'G'
ALIGN 2
BaudRates
DC.W baud300
DC.W baud600
DC.W baud1200
DC.W baud1800
DC.W baud2400
DC.W baud3600
DC.W baud4800
DC.W baud7200
DC.W baud9600
DC.W baud19200
DC.W baud57600
ENDP
END
Listing: Pack.c
/* Chooser Device Pack code. Described in Device Manager
* chapter of Inside Macintosh IV. */
/* Earle R. Horton.
* Wednesday, November 30, 1988
* All rights reserved. */
#include <Resources.h>
#include <ToolUtils.h>
#include <OSUtils.h>
#include <Dialogs.h>
#include <Lists.h>
#define PACK
#include "dmp-110.h"
#include "compat.h"
#define __SEG__ Main
/* The Pascal Interface to MPW C 2.0.2 does not fully include the List
Manager. */
#ifdef macintosh
pascal void LGetCell(Ptr,short *,Cell,ListHandle);
pascal void LSetCell(Ptr,short,Cell,ListHandle);
pascal void LSetSelect(Boolean,Cell,ListHandle);
#endif
pascal OSErr device(message,caller,objname,zonename,p1,p2)
short message,caller;
StringPtr objname,zonename;
long p1,p2;
{
Pfg settings;
#ifndef BACKWARD_COMPATIBLE
SysEnvRec World;
(void)SysEnvirons(1,&World);
if(World.systemVersion < SYSNEEDED){
(void)StopAlert(SYSVERSALERT,nil);
}
else
#endif
switch(message){
case buttonMsg:
prsetup(p2);
break;
case fillListMsg:
filllist(p1);
break;
case selectMsg:
settings = (Pfg)(GetResource('HEXA',RES2ID));
pbaud = p2;
break;
}
return (noErr);
}
/* Fill the list passed to us with the names of baud rates
* contained in our 'STR#' resource. */
filllist(list)
ListHandle list;
{
Pfg settings;
Cell cell;
short i;
char thestring[256];
settings = (Pfg)(GetResource('HEXA',RES2ID));
(void)LAddRow(11,0,list);
for(i=0;i<11;){
SetPt(&cell,0,i++);
GetIndString(thestring,RES1ID,i);
LSetCell(&thestring[1],(short)thestring[0],cell,list);
}
SetPt(&cell,0,pbaud);
LSetSelect(0xFFFF,cell,list);
LAutoScroll(list);
}
/* prsetup() - Configure serial port.*/
prsetup(button)
long button;
{
Pfg settings;
if ((settings = (Pfg)(GetResource('HEXA',RES2ID))) == nil)
return;
if((button & 0xFF) == 1){
if(IsMPPOpen()){ /* Conflict! */
(void)StopAlert(ATALKALERT,nil);
return;}
pport = 1;
}else{
pport = 0;}
ChangedResource(settings);
WriteResource(settings);
}
Listing: pdef0.c
/* Draft mode, text-only printing code for the Macintosh.*/
/* file is part of DMP-110 printer driver for the Macintosh
* series of computers.*/
/* Earle R. Horton.
* Wednesday, November 30, 1988
* All rights reserved. */
#include <Windows.h>
#include <Events.h>
#include <Dialogs.h>
#include <Fonts.h>
#include <Memory.h>
#include <Resources.h>
#include <ToolUtils.h>
#include <Errors.h>
#include <OSUtils.h>
#include <Desk.h>
#include "dmp-110.h"
#include "compat.h"
#define __SEG__ Main
/* Use our own names for these.*/
#define storage lGParam1
#define pagenum lGParam2
#define PREC lGParam3
#define picture lGParam4
/* Useful constants */
#define SERRESET 8
#define SERSHAKE 10
#define XONCR ((char)17)
#define XOFFCR ((char)19)
#define RESFILEID (-8192)
OSErr SPOpen();
#define WNETrapNumber 0x60
#define UnImpTrapNumber 0x9f
/* Abort printing if command '.' pressed. */
checkabort()
{
EventRecord myevent;
Boolean result;
Boolean WNEisThere = NGetTrapAddress(WNETrapNumber,1) !=
NGetTrapAddress(UnImpTrapNumber,1);
if(WNEisThere){
result = WaitNextEvent(everyEvent,&myevent,0L,0L);
}else{
SystemTask();
result = GetNextEvent(keyDownMask|autoKeyMask, &myevent);
}
if(result){
if(LoWord(myevent.message & charCodeMask) == '.' &&
(myevent.modifiers & cmdKey) ){
PrSetError(iIOAbortErr); /* iPrAbort? */
}
}
}
/* This function returns a pointer to a specialized GrafPort
* (a TPrPort) customized for printing.
* Based on passed Print Record, prepare a GrafPort for use
* by application in printing. store information of use to
* us in lGParam[n] fields of the TPrPort. We use a BitMap
* whose baseAddr contains pointer to a buffer whose size is
* taken from prXInfo field of print record. We define the
* portRect to correspond to prInfo.rPage field of the print
* record. Later, will scale application's drawing commands
* to fit into prInfoPT.rPage Rect, and print Picture which
* the application has created. */
pascal TPPrPort myPrOpenDoc(hPrint,pPrPort,pIOBuf)
THPrint hPrint;
TPPrPort pPrPort;
Ptr pIOBuf;
{
TPPrPort thisport;
Handle us;
THPrint savePrint;
BitMap mybits;
THPrint ourPrint;
DCtlHandle d;
DPstorage s;
SysEnvRec World;
short refnum;
/* Allocate storage, abort if no space for TPrPort.
* First stage aborts if not enough for a TPrPort, setting
* PrError() to memFullErr and returning nil. */
if(pPrPort == nil){
if((thisport = (TPPrPort)
NewPtr((Size)sizeof(TPrPort))) == nil){
PurgeMem(maxSize);
thisport = (TPPrPort)NewPtr((Size)sizeof(TPrPort));
if(thisport == nil){
PrSetError(memFullErr);
return nil;
}
}
thisport->fOurPtr = TRUE; /* TRUE: printing code. */
} else {
thisport = pPrPort;
thisport->fOurPtr = FALSE; /* FALSE: application. */
}
thisport->fOurBits = TRUE;
/* If we get here, there is enough space for least TPrPort.
* next block of code attempts to allocate dynamic storage
* we will need during printing. If it fails, it sets
* thisport->storage to nil, & sets PrError() to memFullErr.
* application should detect the print error condition, and
* call PrCloseDoc. Dynamic storage which was not allocated
* successfully is disposed of by PrCloseDoc. Storage
* is allocated in several blocks rather than single big
* block in order to facilitate changing size of the output
* buffer or band. */
thisport->storage = (long)(s = (DPstorage)NewPtr((Size)sizeof(Dstorage)));
if(s != nil){
memset(s,0,sizeof(Dstorage));
s->obuf = NewPtr((Size)2000);
}
ourPrint = hPrint;
if(HandToHand(&ourPrint) == noErr){
thisport->PREC = (long)ourPrint;
}else{
thisport->PREC = nil;}
thisport->gPort.portBits.baseAddr = mybits.baseAddr = NewPtr((Size)
((*hPrint)->prXInfo.iRowBytes*(*hPrint)->prXInfo.iBandV));
if( (thisport->storage == nil) ||
(s->obuf == nil) || (thisport->PREC == nil) ||
(mybits.baseAddr == nil) ){
PrSetError(memFullErr);
}else{
/* If we get here, then memory worries are over, except of
* course if we run out while defining a pict. Don't know
* what happens then. TPrPort is ready to be initialized,
* and serial port to be opened. Only possible fatal error
* remaining is a serial port error.*/
OpenPort(thisport);
SetStdProcs(&thisport->gProcs); /* Fill out gProcs for this
port. */
thisport->gPort.grafProcs = &thisport->gProcs;
SetPort(thisport);
SysEnvirons(1,&World);
PrSetError(SPOpen(s,&World));
/* Save our copy of Print Record Handle, then open port. */
thisport->PREC = (long)ourPrint;
/* Define the port's BitMap as our printing band.*/
mybits.bounds.top = 0;
mybits.bounds.left = 0;
mybits.bounds.bottom = (*hPrint)->prXInfo.iBandV;
mybits.bounds.right = (*hPrint)->prXInfo.iBandH+1;
mybits.rowBytes = (*hPrint)->prXInfo.iRowBytes;
SetPortBits(&mybits);
MovePortTo((*hPrint)->prInfo.rPage.left,(*hPrint)->prInfo.rPage.top);
PortSize((*hPrint)->prInfo.rPage.right-(*hPrint)->prInfo.rPage.left,
(*hPrint)->prInfo.rPage.bottom-(*hPrint)->prInfo.rPage.top);
RectRgn(thisport->gPort.visRgn,&thisport->gPort.portRect);
RectRgn(thisport->gPort.clipRgn,&thisport->gPort.portRect);
thisport->pagenum = 1;
thisport->storage = (long)s;
if((*ourPrint)->prJob.pIdleProc == nil)
(*ourPrint)->prJob.pIdleProc = (ProcPtr)checkabort;
/* Attempt to save Print Record in 'PREC' 1 in printer file.
* No big deal if failure, since it is not essential to
* printing. */
savePrint = (THPrint)GetResource('PREC',1);
if(savePrint != nil){
LoadResource(savePrint);
**savePrint = **ourPrint;
ChangedResource(savePrint);
UpdateResFile(HomeResFile(savePrint));
}
}
return thisport;
}
/* Called at completion of print job, whether successful or
* not. Clean up any dynamic storage which requires it, then
* set print error to noErr. */
pascal void myPrCloseDoc(pPrPort)
TPPrPort pPrPort;
{
DPstorage s;
if( (pPrPort != nil) &&
((s = (DPstorage)pPrPort->storage) != nil) &&
(s->obuf != nil) &&(pPrPort->PREC != nil) &&
(pPrPort->gPort.portBits.baseAddr != nil) ) {
/* Assume successful open. */
ClosePort(pPrPort);
PBClose(&s->iopb,FALSE);
}
if(pPrPort != nil){ /* Dispose storage which needs it.*/
if(s != nil){
if(s->obuf != nil){
DisposPtr(s->obuf);
}
DisposPtr(s);
}
if(pPrPort->PREC != nil){
DisposHandle(pPrPort->PREC);}
if(pPrPort->gPort.portBits.baseAddr != nil){
DisposPtr(pPrPort->gPort.portBits.baseAddr);
}
if(pPrPort->fOurPtr){
DisposPtr(pPrPort);}
}
PrSetError(noErr); /* No bad feelings! */
}
/* routine opens a new page. Check for valid TPrPort, then
* just start a new picture definition. */
pascal void myPrOpenPage(pPrPort,pPageFrame)
TPPrPort pPrPort;
TPRect pPageFrame;
{
Rect *scalerect;
if(pPageFrame != nil){
scalerect = pPageFrame;
}else{
scalerect = &pPrPort->gPort.portRect; }
SetPort(pPrPort);
/* app. may run out of memory while the picture is being
* saved. Don't know how to prevent or even detect this.*/
pPrPort->picture = (long)OpenPicture(scalerect);
}
/* This is the routine which does the actual printing.
* Close picture for the page. Use DrawPicture() to draw
* repeatedly, while moving BitMap down the page to capture
* an image for each band.
* Send each band to the output conversion routines. */
pascal void myPrClosePage(pPrPort)
TPPrPort pPrPort;
{
PicHandle thepic;
short i,iocount,vband,nbands,rows,nleft;
Rect savepr,scaleRect,savebm;
long width,height;
THPrint ourPrint;
char formfeed;
DPstorage s;
ourPrint = (THPrint)pPrPort->PREC; /* Set up vars. */
SetPort(pPrPort);
ClosePicture();
thepic = (PicHandle)pPrPort->picture;
vband = (*ourPrint)->prXInfo.iBandV;
nbands = (*ourPrint)->prXInfo.iBands;
s = (DPstorage)pPrPort->storage;
if(PrError() != noErr){
} else if(setjmp(s->abortbuf) != 0){ /* Pops back here on abort
or error. */
PrSetError(iIOAbortErr);}
else if(pPrPort->pagenum < (*ourPrint)->prJob.iFstPage){/* Check
page no. */
pPrPort->pagenum++; }
else if(pPrPort->pagenum++ > (*ourPrint)->prJob.iLstPage){
PrSetError(iIOAbortErr);
} else {
savepr = pPrPort->gPort.portRect;
savebm = pPrPort->gPort.portBits.bounds;
/* Enlarge portRect of Printing Port. */
OffsetRect(&pPrPort->gPort.portBits.bounds,-savebm.left,-savebm.top);
scaleRect = (*ourPrint)->prInfoPT.rPage;
OffsetRect(&scaleRect,-scaleRect.left,-scaleRect.top);
pPrPort->gPort.portRect = scaleRect;
RectRgn(pPrPort->gPort.visRgn,&scaleRect);
RectRgn(pPrPort->gPort.clipRgn,&scaleRect);
rows = scaleRect.bottom - scaleRect.top;
if((*ourPrint)->prStl.feed != feedCut || waitnextpage()){
for(i=0;i<rows;i+=vband){/* Loop over bands. */
MovePortTo(scaleRect.left,-i); /* to next.*/
EraseRect(&scaleRect);
DrawPicture(thepic,&scaleRect);
/* Draw in the band. */
dumpbits(pPrPort);/* Print it. */
}
formfeed = 12;/* formfeed. Device Dependent. */
s->iopb.ioParam.ioReqCount = 1;
s->iopb.ioParam.ioBuffer = &formfeed;
asyncwrite(s,(*((THPrint)pPrPort->PREC))->prJob.pIdleProc);
/* Restore portRect so Application can draw in it.*/
pPrPort->gPort.portBits.bounds = savebm;
pPrPort->gPort.portRect = savepr;
RectRgn(pPrPort->gPort.visRgn,&savepr);
RectRgn(pPrPort->gPort.clipRgn,&savepr);
}else{
PrSetError(iIOAbortErr); /* iPrAbort? */
}
}
KillPicture(thepic);
}
dumpbits(pPrPort) /* Dump printing band to serial port. */
TPPrPort pPrPort;
{
DPstorage s;
int i,rows;
BitMap *bits;
ProcPtr idle;
s = (DPstorage)pPrPort->storage;
bits = &pPrPort->gPort.portBits;
rows = bits->bounds.bottom-bits->bounds.top;
idle = (*((THPrint)pPrPort->PREC))->prJob.pIdleProc;
for (i=0;i<rows;i+=16){
bitmap_to_hires(s,bits,i,rows-i,idle);
if(PrError() != noErr)return;
}
}
/* bitmap_to_hires()
* function translates a QuickDraw BitMap to codes which may
* be sent to a Tandy DMP-110 dot-matrix printer in
* high-resolution graphics mode. Graphics codes for this
* printer in hi-res mode include all eight bit characters.
* There are two characters for each column of 16 dots on
* paper. The top dot (1) corresponds to bit
* zero of the first byte sent. The bottom dot (16)
* corresponds to bit eight of second byte. There are 960
* columns, 1920 bytes, of graphics data to be sent for one
* line of graphics output. Note that the ToolBox bit
* manipulation routines use lower-to-upper bit order.
* No smarts in routine, the whole 1918 bytes are sent for
* each line.*/
bitmap_to_hires(s,b,row,nleft,idle)
DPstorage s;
BitMap *b; /* BitMap to print. */
int row; /* Starting row on this pass. */
int nleft; /* Max number of rows to print. */
ProcPtr idle;
{
short *obytes,*inbytes;
int column,lastcol,nbits,dot;
obytes = (short *)s->obuf;
for(dot=960;dot-- >0;){ obytes[dot] = 0;}
lastcol = b->bounds.right - b->bounds.left;
lastcol = (lastcol > 959) ? 959 : lastcol;
inbytes = (short *)(b->baseAddr + b->rowBytes*row);
nbits = b->rowBytes*8;
for(column=0;column<lastcol;column++){
(*idle)();
if(PrError() != noErr)return;
for(dot=0;dot<8 && dot<nleft;dot++){
if(BitTst(inbytes,(long)(column+dot*nbits))){
BitSet(obytes,(long)(7-dot));}
}
for(dot=8;dot<16 && dot < nleft;dot++){
if(BitTst(inbytes,(long)(column+dot*nbits))){
BitSet(obytes,(long)(23-dot));}
}
obytes++;
}
output_hires_data(s,(long)(lastcol),idle);
/* Send to printer. */
}
/* output_hires_data() - Send a stream of high resolution
* graphics data to the Tandy DMP-110. The codes for
* transferring the graphics, and for high-resolution paper
* advance, are hard-coded into this routine. Skips blank
* graphics codes, and repositions the printer head. The
* overhead is 8 bytes for repositioning the head and
* restarting high resolution graphics. If there are four
* blank columns, then, we break even. If more, we
* win. If there are less four blank columns, we lose.*/
output_hires_data(s,columns,idle)
DPstorage s;
long columns;
ProcPtr idle;
{
unsigned char codes[10];
short *p;
short pos,ncodes,start;
short *buf = (short *)s->obuf;
for(pos=0,ncodes=0,p=buf,start=0;pos<960;pos++){
if(buf[pos] == 0){
if(ncodes != 0){
sendcodes(s,p,ncodes,start,idle);
if(PrError() != noErr)return;
ncodes = 0;
}
}else{
if(ncodes == 0){
p = &buf[pos];
start = pos;
}
ncodes++;
}
}
if(ncodes != 0){
sendcodes(s,p,ncodes,start,idle);
}
codes[0] = 26; /* DMP-110 carriage return. */
codes[1] = 27; /* High res line feed. */
codes[2] = 'G';
s->iopb.ioParam.ioReqCount = 3L;
s->iopb.ioParam.ioBuffer = (Ptr)codes;
asyncwrite(s,idle);
}
sendcodes(s,buf,n,pos,idle)
DPstorage s;
char *buf;
int n,pos;
ProcPtr idle;
{
unsigned char codes[10];
codes[0] = 27;
codes[1] = 16;
codes[2] = (pos>>8)&3;
codes[3] = pos & 0xFF;
codes[4] = 27;
codes[5] = 73;
codes[6] = (n>>8)&3;
codes[7] = n&0xFF;
s->iopb.ioParam.ioReqCount = 8L;
s->iopb.ioParam.ioBuffer = (Ptr)codes;
asyncwrite(s,idle);
s->iopb.ioParam.ioReqCount = (long)(2*n);
s->iopb.ioParam.ioBuffer = buf;
asyncwrite(s,idle);
}
/* Modal dialog box: "Insert next sheet." */
waitnextpage()
{
DialogPtr sheetdialog;
WindowPtr tempport;
short itemhit,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;
}
/* Open serial drvr & configure it. Quit if ioResult field
* of parameter block ever becomes other than noErr. */
OSErr SPOpen(s,World)
register DPstorage s;
SysEnvRec *World;
{
register ParmBlkPtr pb;
int serconfig;
Pfg settings;
OSErr error;
extern char AOutName[],BOutName[];
extern short BaudRates[];
error = noErr;
settings = (Pfg)(GetResource('HEXA',RESFILEID));
if(settings == nil){ /* Where's our stuff? */
return ResError();}
LoadResource(settings);
HNoPurge(settings);
pb = &s->iopb;
switch (pport){ /* get the correct port */
case 0: /* modem port */
pb->ioParam.ioNamePtr = (StringPtr)AOutName;
break;
case 1: /* printer port */
if(IsMPPOpen()){
(void)StopAlert(ATALKALERT,nil);
return portInUse;}
pb->ioParam.ioNamePtr = (StringPtr)BOutName;
break;
}
PBOpen(pb,FALSE);
if (pb->ioParam.ioResult != noErr){
return(pb->ioParam.ioResult);}
pb->ioParam.ioNamePtr = nil;
/* Set up io parameter block for writing to serial driver.
* a control call resets the baud rate.
* Another sets flow control. */
((CntrlParam *)pb)->csCode = SERRESET;
serconfig = data8 + noParity + stop20;
serconfig += BaudRates[pbaud];
((CntrlParam*)pb)->csParam[0] = serconfig;
PBControl(pb,FALSE);
if (pb->ioParam.ioResult != noErr) return(pb->ioParam.ioResult);
#define shake ((SerShk *)&((CntrlParam*)pb)->csParam[0])
shake->errs = FALSE;
shake->evts = FALSE;
shake->fDTR = FALSE;
shake->fInX = FALSE;
if(XonXoff && (World->machineType >= envMachUnknown)){
shake->fXOn = TRUE;
shake->fCTS = FALSE;
shake->xOn = XONCR;
shake->xOff = XOFFCR;
} else {
shake->fXOn = FALSE;
shake->fCTS = TRUE;
}
((CntrlParam *)pb)->csCode = SERSHAKE;
PBControl(pb,FALSE);
if (pb->ioParam.ioResult != noErr) return(pb->ioParam.ioResult);
pb->ioParam.ioPosMode = 0;
pb->ioParam.ioPosOffset = 0;
ReleaseResource(settings);
return(noErr);
}
/* Routine to write asynchronously to serial port, run an
* idle proc.idle routine may call PrSetError(),
* so we check PrError() for abort each time. Kill pending
* serial port IO if abort detected.*/
asyncwrite(s,idle)
DPstorage s;
ProcPtr idle;
{
IOParam *p = &(s->iopb.ioParam);
PBWrite(p,TRUE); /* Issue ASYNC write. */
do{
(*idle)(); /* Idle til done. */
if(PrError() != noErr){ /* Check for abort. */
if(p->ioResult > 0){ /* More chars? */
PBKillIO(p,FALSE); /* Stop output. */
}
longjmp(s->abortbuf,1); /* Get out. */
}
}while(p->ioResult > 0); /* Check for complete. */
if(p->ioResult < 0){ /* Serial port Error! */
longjmp(s->abortbuf,1);
}
}
Listing: pdef4.c
/* Code to implement Printing Dialogs. Described in rather
* complete fashion in Technical Note #95. */
/* This file is part of the DMP-110 printer driver for the
* Macintosh series of computers. */
/* Earle R. Horton.
* Wednesday, November 30, 1988
* All rights reserved. */
#include <Windows.h>
#include <Events.h>
#include <Dialogs.h>
#include <Controls.h>
#include <Lists.h>
#include <Fonts.h>
#include <Memory.h>
#include <Resources.h>
#include <ToolUtils.h>
#include <Packages.h>
#include <Menus.h>
#include "dmp-110.h"
#include "compat.h"
/* The Pascal Interface to MPW C 2.0.2 does not fully include the List
Manager. */
#ifdef macintosh
pascal void LGetCell(Ptr,short *,Cell,ListHandle);
pascal void LSetCell(Ptr,short,Cell,ListHandle);
pascal void LSetSelect(Boolean,Cell,ListHandle);
#endif
#ifndef iDevDaisy
#define bDevDaisy 2
#define iDevDaisy (bDevDaisy << 8)
#endif
#define STYLEDIALOG (0xE000)
#define JOBDIALOG (0xE001)
#define XTRA 24 /* Six extra longs for our use. */
#define NORMALPRINT 0
#define DLGSIZE ((long)(sizeof(TPrDlg) + XTRA))
#define extra(tp,i) (* ((long *)((char *)(tp) + sizeof(TPrDlg)) + (i)))
#define TPSIZE ((long)(sizeof(TPrint)))
/* Items we handle in the job and style dialogs. */
#define CANCELITEM 2
#define STYLETITLE 4
#define STYLELIST 5
#define ALLBUTTON 5
#define RANGEBUTTON 6
#define FROMNUM 7
#define TONUM 9
#define COPIES 11
#define FANBUTTON 13
#define SHEETBUTTON 14
#ifdef MPU68000 /* Aztec does not yet do prototypes. */
pascal void CallItemProc();
pascal TPPrDlg CallDlgInit();
#else
pascal void CallItemProc(tp,itemhit,RealAddr) /* Glue. */
TPPrDlg tp;
short itemhit;
ProcPtr RealAddr;
extern;
pascal TPPrDlg CallDlgInit(hPrint,RealAddr) /* Glue. */
THPrint hPrint;
ProcPtr RealAddr;
extern;
#endif
/* Validate/update a print record. */
pascal Boolean MyPrValidate(hPrint)
THPrint hPrint;
{
THPrint thedefault;
if(Valid(hPrint)) return FALSE;
else{
thedefault = (THPrint)(GetResource('PREC',NORMALPRINT));
LoadResource(thedefault);
**hPrint = **thedefault;
return TRUE;
}
}
/* Printing dialog supervisor function.
* Ref: Macintosh Technical Note 95. */
pascal Boolean MyPrDlgMain(hPrint,pDlgInit)
THPrint hPrint;
ProcPtr pDlgInit;
{
TPPrDlg tp;
WindowPtr tempport;
short donetype,itemhit;
Handle doneitem;
Rect donebox;
ProcPtr itemproc;
tp = CallDlgInit(hPrint,pDlgInit);
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);
PenSize(1,1);
while(!(tp->fDone)){
ModalDialog(tp->pFltrProc,&itemhit);
CallItemProc(tp,itemhit,itemproc); }
SetPort(tempport);
CloseDialog(tp);
DisposPtr(tp);
if(tp->fDoIt) (void)MyPrValidate(hPrint);
return(tp->fDoIt);
}
/* Default item filter for print dialogs.*/
pascal Boolean MyFilter(tp,theEvent,itemhit)
TPPrDlg tp;
EventRecord *theEvent;
short *itemhit;
{
char c;
if(theEvent->what == keyDown){
c = (theEvent->message & charCodeMask);
if( (c == 13) || (c == 3)){ /* Return or Enter. */
*itemhit = 1;
return TRUE;
}
}
return FALSE;
}
/* Style Dialog Filter. Give it to List Manager if it is
* in list of 'PREC's. Signal done if a double-click in a
* cell. Otherwise, handle via the normal filter.*/
pascal Boolean MyStlFilter(tp,theEvent,itemhit)
TPPrDlg tp;
EventRecord *theEvent;
short *itemhit;
{
if(*itemhit == STYLELIST){
GlobalToLocal(&theEvent->where);
(void)LClick(theEvent->where,theEvent->modifiers,(ListHandle)extra(tp,0));
LocalToGlobal(&theEvent->where);
return FALSE;
}else{
return MyFilter(tp,theEvent,itemhit);
}
}
/* This function fills a print record with defaults. The
* default values are stored in Printer resource file, in
* PREC 0. This is easy. */
pascal void MyPrintDefault(hPrint)
THPrint hPrint;
{
THPrint thedefault;
thedefault = (THPrint)(GetResource('PREC',NORMALPRINT));
LoadResource(thedefault);
**hPrint = **thedefault; /* What the hey. */
}
/* 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 "OK" button is hit, then we have to fill in the new
* style in the user print record. */
pascal void HandleStyleItems(tp,itemhit)
TPPrDlg tp;
short itemhit;
{
short thenum,type;
Handle item;
Rect box,bigbox;
MenuHandle PRECMenu;
long restype;
short resid;
short nitems,i;
THPrint rPrint;
char thestring[256];
Point theCell;
switch(itemhit){
case DONEITEM:
SetPt(&theCell,0,0);
LGetSelect(0xFFFF,&theCell,(ListHandle)extra(tp,0));
i = 255;
LGetCell(&thestring[1],&i,theCell,(ListHandle)extra(tp,0));
thestring[0] = i;
rPrint = (THPrint)GetNamedResource('PREC',thestring);
if(rPrint == nil){
rPrint = (THPrint)GetResource('PREC',0);
}
LoadResource(rPrint);
(*(tp->hPrintUsr))->prInfo = (*rPrint)->prInfo;
(*(tp->hPrintUsr))->prInfoPT = (*rPrint)->prInfoPT;
(*(tp->hPrintUsr))->rPaper = (*rPrint)->rPaper;
(*(tp->hPrintUsr))->prStl = (*rPrint)->prStl;
(*(tp->hPrintUsr))->prXInfo = (*rPrint)->prXInfo;
(*(tp->hPrintUsr))->printX[0] = (*rPrint)->printX[0];
LDispose((ListHandle)extra(tp,0));
tp->fDone = TRUE;
tp->fDoIt = TRUE;
break;
case CANCELITEM:
LDispose((ListHandle)extra(tp,0));
tp->fDone = TRUE;
tp->fDoIt = FALSE;
break;
default:
break;
}
}
/* Symbolic constants used to make LNew() more readable. */
#define Drawn 0xFFFF
#define noGrow 0x0000
#define noHScroll 0x0000
#define vScroll 0xFFFF
#include <strings.h>
/* User item for putting up list of available Print Recs. */
pascal void PRECuserItem(tp,theitem)
TPPrDlg tp;
short theitem;
{
short type,i,j,nitems,id;
Handle item;
Rect listbox,dataBounds;
Point cellSize,theCell;
char thestring[256];
THPrint rPrint;
long restype;
ListHandle PRECList;
/* First time called, create the list. */
if(extra(tp,0) == nil){
GetDItem(tp,theitem,&type,&item,&listbox);
listbox.right -=15;
SetRect(&dataBounds,0,0,1,0);
SetPt(&cellSize,0,0);
PRECList = LNew(&listbox,&dataBounds,cellSize,0,(WindowPtr)tp,
Drawn,noGrow,noHScroll,vScroll);
(*PRECList)->selFlags = lDoHAutoscroll + lOnlyOne;
extra(tp,0) = (long)PRECList;
/* Read in the resources, put the names in the list. */
nitems = CountResources('PREC');
(void)LAddRow(nitems,0,PRECList);
for(i=0,j=0;i++<nitems;){
rPrint = (THPrint)GetIndResource('PREC',i);
GetResInfo(rPrint,&id,&restype,thestring);
SetPt(&theCell,0,j++);
LSetCell(&thestring[1],(short)thestring[0],theCell, PRECList);
if(((*tp->hPrintUsr))->printX[0] == id){
LSetSelect(0xFFFF,theCell,PRECList);
LAutoScroll(PRECList);
}
}
}
/* After the first time, just frame item's Rect, and call
* LUpdate() to draw the list. */
GetDItem(tp,theitem,&type,&item,&listbox);
InsetRect(&listbox,-1,-1);
FrameRect(&listbox);
LUpdate(tp->Dlg.window.port.visRgn,(ListHandle)extra(tp,0));
}
/* The style dialog initializer. Get the dialog from the
* resource file, calculate Rect for Print Record list. */
pascal TPPrDlg MyPrStlInit(hPrint)
THPrint hPrint;
{
TPPrDlg tp;
short type;
Handle item;
Rect box,listbox;
tp = (TPPrDlg)NewPtr(DLGSIZE);
(GetNewDialog(STYLEDIALOG,tp,(WindowPtr)-1));
GetDItem(tp,STYLETITLE,&type,&item,&box);
listbox.left = box.right+20;
listbox.top = tp->Dlg.window.port.portRect.top+10;
listbox.bottom = tp->Dlg.window.port.portRect.bottom-10;
listbox.right = listbox.left + 200;
GetDItem(tp,STYLELIST,&type,&item,&box);
SetDItem(tp,STYLELIST,type,PRECuserItem,&listbox);
extra(tp,0) = nil;
tp->pFltrProc = (ProcPtr)MyStlFilter;
tp->pItemProc = (ProcPtr)HandleStyleItems;
tp->hPrintUsr = hPrint;
return(tp);
}
pascal Boolean MyPrStlDialog(hPrint) /* Conduct printer style dialog.
*/
THPrint hPrint;
{
return(MyPrDlgMain(hPrint,MyPrStlInit));
}
/* Record user choice of paper feed, page numbers, and
* number of copies. */
pascal void HandleJobItems(tp,itemhit)
TPPrDlg tp;
short itemhit;
{
short thenum;
long thelong;
Handle numitem;
Rect numbox;
char title[256];
switch(itemhit){
case DONEITEM:
tp->fDone = TRUE;
tp->fDoIt = TRUE;
if(buttonset(tp,ALLBUTTON)){
(*(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 = (short)thelong;
if(buttonset(tp,SHEETBUTTON)){
(*(tp->hPrintUsr))->prStl.feed = feedCut;
}
else (*(tp->hPrintUsr))->prStl.feed = feedFanfold;
break;
case CANCELITEM:
tp->fDone = TRUE;
break;
case SHEETBUTTON:
pushradiobutton(tp,SHEETBUTTON,FANBUTTON,SHEETBUTTON);
(*(tp->hPrintUsr))->prStl.feed = feedCut;
break;
case FANBUTTON:
pushradiobutton(tp,FANBUTTON,FANBUTTON,SHEETBUTTON);
(*(tp->hPrintUsr))->prStl.feed = feedFanfold;
break;
case ALLBUTTON:
pushradiobutton(tp,ALLBUTTON,ALLBUTTON,RANGEBUTTON);
break;
case RANGEBUTTON:
pushradiobutton(tp,RANGEBUTTON,ALLBUTTON,RANGEBUTTON);
break;
default:
break;
}
}
/* The job dialog initializer. Make sure all the buttons
* come up reflecting the Print Record contents. */
pascal TPPrDlg MyPrJobInit(hPrint)
THPrint hPrint;
{
TPPrDlg tp;
short thenum;
Handle theitem;
Rect thebox;
char title[255];
tp = (TPPrDlg)NewPtr(DLGSIZE);
(GetNewDialog(JOBDIALOG,tp,(WindowPtr)-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 /*= 1*/);
NumToString((long)thenum,title);
SetIText(theitem,title);
tp->pFltrProc = (ProcPtr)MyFilter;
tp->pItemProc = (ProcPtr)HandleJobItems;
tp->hPrintUsr = hPrint;
return(tp);
}
pascal Boolean MyPrJobDialog(hPrint) /* Conduct printer job dialog.
*/
THPrint hPrint;
{
return(MyPrDlgMain(hPrint,MyPrJobInit));
}
/* Copy a job subrecord. Update the destination record's
* printer information, band information, and paper rect,
* based on information in the job subrecord. */
pascal void MyPrJobMerge(hPrintSrc,hPrintDst)
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;
}
/* Handy test for radio button down. */
int buttonset(d,num)
DialogPtr d;
short num;
{
short type;
Handle item;
Rect box;
GetDItem(d,num,&type,&item,&box);
if(GetCtlValue(item) == 1){
return true;
}else{
return false;
}
}
pushradiobutton(thedialog,itemhit,first,last)
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);
}
}
/* This function answers the question: Can we possibly use
* this print record? Try to be liberal here. Don't
* allow a Print Record which requires a larger buffer than
* our printing routines can handle. */
int Valid(hPrint)
THPrint hPrint;
{
if (((*hPrint)->iPrVersion != VERSION) || ((*hPrint)->prInfo.iDev !=
0) || ((*hPrint)->prJob.bJDocLoop != bDraftLoop))
return FALSE;
if((*hPrint)->prXInfo.iBandV != 64 ||
(*hPrint)->prXInfo.iBandH != 959 ||
(*hPrint)->prXInfo.iRowBytes != 120
)
return FALSE;
return TRUE;
}
Listing: PDriver.C
/* This is the driver. A description of the driver can be
* found in Device Manager chapter of Inside Macintosh, & a
* description of THIS driver can be found in Print Manager
* chapter.*/
/* file is part of the DMP-110 printer driver for the Macintosh series
of computers.*/
/* Earle R. Horton.
* Wednesday, November 30, 1988
* All rights reserved. */
#define DRIVER
#include <Windows.h>
#include <Events.h>
#include <Dialogs.h>
#include <Fonts.h>
#include <Memory.h>
#include <Resources.h>
#include <ToolUtils.h>
#include <Errors.h>
#include <Desk.h>
#include "dmp-110.h"
#include "compat.h"
#define __SEG__ Main
#define lPrEvtAll 0x00FFFFFD
#define lPrEvtTop 0x00FEFFFD
#define dNeedGoodBye 4
int checkabort();
/* Useful constants */
#define SERRESET 8
#define SERSHAKE 10
#define XONCR ((char)17)
#define XOFFCR ((char)19)
#define RESFILEID (-8192)
extern char AOutName[],BOutName[];
extern short BaudRates[];
extern char prlfstr[];
extern char prinitstr[];
extern char prtopstr[];
extern char preopstr[];
extern char prmargin[];
extern char prhireslf[];
OSErr openprinterfile(),SPOpen();
/* Driver close routine.
* Close serial port. Dispose of our storage.
* Better not close the serial driver if we
* are dealing with 64k ROMs.
* Called by device manager under UniFinder when application
* heap is reinitialized.*/
OSErr myPrClose(p,d) /* Device Close Call */
PrParam *p; /* ==> parameter block */
DCtlPtr d; /* ==> device control entry */
{
return(noErr);
}
#include <strings.h>
/* Printer driver open routine. Open our printer resource
* file, get any information we have stored, allocate a
* non-relocatable block of storage, set up serial port for
* use. Check errors. */
OSErr myPrOpen(p,d)
PrParam *p; /* ==> parameter block */
DCtlPtr d; /* ==> device control entry */
{
extern short DriverEntry;
return (noErr);
}
OSErr myPrPrime(p,d) /* We don't prime. It's for drivers which do */
/* read/write directly. */
PrParam *p; /* ==> parameter block */
DCtlPtr d; /* ==> device control entry */
{
return(noErr);
}
OSErr myPrControl(p,d) /* Control calls. Many defined, few implemented.
*/
PrParam *p; /* ==> parameter block */
DCtlPtr d; /* ==> device control entry */
{
Dstorage storage;
char *buf = nil;
SysEnvRec World;
OSErr error;
ProcPtr idle;
SysEnvirons(1,&World);
PrSetError(noErr);
memset(&storage,0,sizeof(Dstorage));
error = SPOpen(&storage,&World);
if(error != noErr) return noErr;
storage.iopb.ioParam.ioResult = noErr;
if(setjmp(storage.abortbuf) != 0){
PBClose(&storage.iopb);
if(p->csCode == iPrEvtCtl){
DisposPtr(buf);
}
return noErr;
}
/*Device Control Call. p->csCode gives opcode, and we switch on it
to perform low-level Printing calls*/
switch (p->csCode){
case iPrBitsCtl:/* Send a bitmap to the printer. */
break;
case iPrEvtCtl: /* Screen prntng. (cmd-shift 4.)*/
if(*(short*)(&p->lParam1) == 1){/* Top window.*/
buf = NewPtr(2000L);
if(buf != nil){
dumptop(&storage,buf);
DisposPtr(buf);
}
}
else if(*(short*)(&p->lParam1) == 2){
/* Screen. */
buf = NewPtr(2000L);
if(buf != nil){
dumpscreen(&storage,buf);
DisposPtr(buf);
}
}
break;
default:
break;
}
PBClose(&storage.iopb);
return noErr;
}
/* Printer driver status call, used by the Font Manager to
* request a copy of printer's font characterization table.
* Ignored like the control call. */
OSErr myPrStatus(p,d) /* Device Status Call */
PrParam *p; /* ==> parameter block */
DCtlPtr d; /* ==> device control entry */
{
return (noErr);
}
/* Open serial driver and configure it. Quit if ioResult
* field of param block ever becomes other than noErr. */
OSErr SPOpen(s,World)
register DPstorage s;
SysEnvRec *World;
{
register ParmBlkPtr pb;
int serconfig;
Pfg settings;
Handle pfilename;
short pfile;
pfilename = GetResource('STR ',0xE000);
if(pfilename == nil){
return ResError(); }
HLock(pfilename);
pfile = OpenResFile(*pfilename);
if(pfile == -1){
return ResError(); }
DisposHandle(pfilename);
settings = (Pfg)(GetResource('HEXA',RESFILEID));
if(settings == nil){
return ResError(); }
LoadResource(settings);
HNoPurge(settings);
pb = &s->iopb;
switch (pport){ /* get the correct port */
case 0: /* modem port */
pb->ioParam.ioNamePtr = (StringPtr)AOutName;
break;
case 1: /* printer port */
default:
if(IsMPPOpen()){
(void)StopAlert(ATALKALERT,nil);
return portInUse; }
pb->ioParam.ioNamePtr = (StringPtr)BOutName;
break;
}
PBOpen(pb,FALSE);
if (pb->ioParam.ioResult != noErr){
return(pb->ioParam.ioResult); }
pb->ioParam.ioNamePtr = nil;
/* Set up the io parameter block for writing to the serial
* driver. a control call resets the baud rate */
((CntrlParam *)pb)->csCode = SERRESET;
serconfig = data8 + noParity + stop20;
serconfig += BaudRates[pbaud];
((CntrlParam*)pb)->csParam[0] = serconfig;
PBControl(pb,FALSE);
if (pb->ioParam.ioResult != noErr) return(pb->ioParam.ioResult);
#define shake ((SerShk *)&((CntrlParam*)pb)->csParam[0])
shake->errs = FALSE;
shake->evts = FALSE;
shake->fDTR = FALSE;
shake->fInX = FALSE;
if(XonXoff && (World->machineType >= envMachUnknown)){
shake->fXOn = TRUE;
shake->fCTS = FALSE;
shake->xOn = XONCR;
shake->xOff = XOFFCR;
} else {
shake->fXOn = FALSE;
shake->fCTS = TRUE;
}
((CntrlParam *)pb)->csCode = SERSHAKE;
PBControl(pb,FALSE);
if (pb->ioParam.ioResult != noErr) return(pb->ioParam.ioResult);
pb->ioParam.ioPosMode = 0;
pb->ioParam.ioPosOffset = 0;
return(noErr);
}
printstring(s,string) /* Send printer control string to */
/* serial driver. */
register DPstorage s;
unsigned char *string;
{
s->iopb.ioParam.ioBuffer = (Ptr)(string+1);
s->iopb.ioParam.ioReqCount = (long)(string[0]);
asyncwrite(s,checkabort);
}
/* dumptop() - Called to dump the top window to the screen.
* Nothing if top window is a color window.
* Uses PtInRgn() to determine the extent of the window's
* structure region, so we can print entire window frame.
* If you know of a better way, let me know. Question:
* What do we do if we get a round window? */
dumptop(s,obuf)
DPstorage s;
short *obuf;
{
EventRecord myevent;
GrafPtr tmpport,oldport;
int i,rows,width,first_row,last_row,left_offset;
Rect printrect;
GetPort(&oldport);
tmpport = (GrafPtr)FrontWindow();
SetPort(tmpport);
/* Expand portRect to Rect enclosing Window struct region. */
printrect = tmpport->portRect;
LocalToGlobal(&printrect);
LocalToGlobal(&printrect.bottom);
while(PtInRgn(*(Point*)(&printrect),((WindowPeek)tmpport)->strucRgn)){
printrect.left--; }
printrect.left++;
while(PtInRgn(*(Point*)(&printrect),((WindowPeek)tmpport)->strucRgn)){
printrect.top--;}
printrect.top++;
while(PtInRgn(*(Point*)(&printrect.bottom),((WindowPeek)tmpport)->strucRgn)){
printrect.bottom++;}
printrect.bottom--;
while(PtInRgn(*(Point*)(&printrect.bottom),((WindowPeek)tmpport)->strucRgn)){
printrect.right++; }
printrect.right--;
GlobalToLocal(&printrect);
GlobalToLocal(&printrect.bottom);
/* Calculate rows in BitMap which we need to print. Send
* sixteen at a time to the BitMap printing routine.*/
first_row = printrect.top - tmpport->portBits.bounds.top;
last_row = printrect.bottom - tmpport->portBits.bounds.top;
left_offset = printrect.left - tmpport->portBits.bounds.left;
width = printrect.right - printrect.left;
for(i=first_row;i<=last_row;i+=16){
bitmap_to_hires(s,&tmpport->portBits,i,
obuf,width,left_offset,last_row-i+1);
}
SetPort(oldport);
}
/* dumpscreen() - Called to dump the screen to the printer.
* Dumps WMgrPort instead. Funky stuff if screen is not in
* two-color mode.*/
dumpscreen(s,obuf)
DPstorage s;
short *obuf;
{
GrafPtr tmpport;
int i,rows,width;
GetWMgrPort(&tmpport);
rows = tmpport->portBits.bounds.bottom - tmpport->portBits.bounds.top;
width = tmpport->portRect.right - tmpport->portRect.left - 1;
for(i=0;i<=rows;i+=16){
bitmap_to_hires(s,&tmpport->portBits,i,obuf,width,0,rows-i);
}
}
/* bitmap_to_hires()
* function translates a QuickDraw BitMap to codes which may
* be sent to a Tandy DMP-110 dot-matrix printer in
* high-resolution graphics mode. Graphics codes for this
* printer in hi-res mode include all eight bit characters.
* There are two characters for each column of 16 dots on
* paper. The top dot (1) corresponds to bit
* zero of the first byte sent. The bottom dot (16)
* corresponds to bit eight of second byte. There are 960
* columns, 1920 bytes, of graphics data to be sent for one
* line of graphics output. Note that the ToolBox bit
* manipulation routines use lower-to-upper bit order.
* No smarts in this routine, whole 1918 bytes are sent for
* each line. */
bitmap_to_hires(s,b,row,obuf,width,left_offset,nleft)
DPstorage s; /* Global driver storage. */
BitMap *b; /* BitMap to print. */
int row; /* Starting row on this pass. */
short *obuf; /* Serial port output buffer. */
int width; /* Print this many columns. */
int left_offset; /* Start this far from left of b->bounds.left. */
int nleft; /* Max number of rows to print. */
{
short *obytes,*inbytes;
int column,lastcol,nbits,dot;
for(dot=960;dot-- >0;){
obuf[dot] = 0;
}
obytes = obuf;
lastcol = (b->bounds.right > 959) ? 959 : b->bounds.right;
lastcol = (lastcol > width) ? width : lastcol;
inbytes = (short *)(b->baseAddr + b->rowBytes*row);
nbits = b->rowBytes*8;
/* Transform QuickDraw BitMap to Tandy DMP-110 high resolution graphics
using ToolBox bit-manipulation routines. Sixteen rows BitMap column
become two-byte printing code.*/
for(column=left_offset;column<=lastcol+left_offset;column++){
for(dot=0;dot<8 && dot<nleft;dot++){
if(BitTst(inbytes,(long)(column+dot*nbits))){
BitSet(obytes,(long)(7-dot)); }
}
for(dot=8;dot<16 && dot < nleft;dot++){
if(BitTst(inbytes,(long)(column+dot*nbits))){
BitSet(obytes,(long)(23-dot)); }
}
obytes++;
}
output_hires_data(s,obuf,(long)(lastcol)-1); /* Send to printer.
*/
}
/* output_hires_data() - Send a stream of high resolution
* graphics data to the Tandy DMP-110. The codes for
* transferring the graphics, and for high-resolution paper
* advance, are hard-coded into this routine. Skips blank
* graphics codes, and repositions the printer head. The
* overhead is 8 bytes for repositioning the head and
* restarting high resolution graphics. If there are four
* blank columns, then, break even. If there are more, we
* win. If there are less than four blank columns, lose. */
output_hires_data(s,buf,columns)
DPstorage s;
short *buf;
long columns;
{
short *p;
short pos,ncodes,start,max;
max = (columns > 959) ? 959 : columns;
for(pos=0,ncodes=0,p=buf,start=0;pos<=columns;pos++){
if(buf[pos] == 0){
if(ncodes != 0){
sendcodes(s,p,ncodes,start);
ncodes = 0; }
}else{
if(ncodes == 0){
p = &buf[pos];
start = pos; }
ncodes++;
}
}
if(ncodes != 0){
sendcodes(s,p,ncodes,start); }
printstring(s,prhireslf);
}
sendcodes(s,buf,n,pos)
DPstorage s;
char *buf;
int n,pos;
{
unsigned char codes[10];
s->iopb.ioParam.ioBuffer = (Ptr)&codes[0];
codes[0] = 27;
codes[1] = 16;
codes[2] = (pos>>8)&3;
codes[3] = pos & 0xFF;
codes[4] = 27;
codes[5] = 73;
codes[6] = (n>>8)&3;
codes[7] = n&0xFF;
s->iopb.ioParam.ioReqCount = 8;
asyncwrite(s,checkabort); /* Send 8 bytes to printer. */
s->iopb.ioParam.ioBuffer = &buf[0];
s->iopb.ioParam.ioReqCount = 2*n;
asyncwrite(s,checkabort); /* Print the buffer. */
}
/* checkabort() - Returns TRUE if command-'.' detected.
* Otherwise, FALSE. */
checkabort()
{
EventRecord myevent;
if(WaitNextEvent(everyEvent,&myevent,0L/*WaitTime*/,0L)){
if(LoWord(myevent.message & charCodeMask) == '.' &&
(myevent.modifiers & cmdKey) ){
PrSetError(iIOAbortErr);
return TRUE;
}
}
return FALSE;
}
/* Routine to write asynchronously to serial port, and run
* an idle proc while we wait. The idle routine may call
* PrSetError(), so we check PrError() for abort each time.
* Kill pending serial port IO if abort detected. */
asyncwrite(s,idle)
DPstorage s;
ProcPtr idle;
{
IOParam *p = &(s->iopb.ioParam);
PBWrite(p,TRUE); /* Issue ASYNC write. */
do{
(*idle)(); /* Idle til done. */
if(PrError() != noErr){ /* Check for abort. */
if(p->ioResult > 0){ /* More chars? */
PBKillIO(p,FALSE); /* Stop output. */
}
longjmp(s->abortbuf,1); /* Get out. */
}
}while(p->ioResult > 0); /* Check for complete. */
}
Listing: DMP-110.rsrc.r
/* contents of a Macintosh Printer Resource File can be found in the
Print Manager Chapter of Inside Macintosh, if you can get it. */
/* This file is part of DMP-110 printer driver for the Mac
* series of computers. */
#include "Types.r"
/* This file's creator. */
#define Version "DMP-110 v1.0"
type 'Dmp1' {
pstring; /* String */
};
/* Print record resource file copy. */
type 'PREC' {
integer ; /* version */
/* prInfo */
byte;
byte; /* iDev */
integer ; /* iVres */
integer ; /* iHres */
rect ; /* rPage */
rect ; /* rPaper */
/* prStl */
integer ; /* wDev */
integer ; /* iPageV */
integer ; /* iPageH */
byte ; /* bPort */
byte ; /* feed */
/* prInfoPT */
byte;
byte; /* iDev */
integer ; /* iVres */
integer ; /* iHres */
rect ; /* rPage */
/* prXInfo */
integer ; /* iRowBytes */
integer ; /* iBandV */
integer ; /* iBandH */
integer ; /* iDevBytes */
integer ; /* iBands */
byte ; /* bPatScale */
byte ; /* bUlThick */
byte ; /* bUlOffset */
byte ; /* bUlShadow */
byte ; /* scan */
byte ; /* bXInfoX */
/* prJob */
integer ; /* iFstPage */
integer ; /* iLstPage */
integer ; /* iCopies */
byte ; /* bJDocLoop */
byte ; /* fFromUsr */
longint ; /* pIdleProc */
longint ; /* pFileName */
integer ; /* iFileVol */
byte ; /* bFileVers */
byte ; /* bJobX */
array [19]{ /* "Private" */
integer;
};
};
resource 'ICN#' (128, "DMP-110") {
{ /* array: 2 elements */
/* [1] */
$"0000 0000 0000 0000 0000 0000 0000 0000"
$"0000 0000 0000 0000 0000 0000 007F 8000"
$"0040 C000 0040 A000 0040 9000 0040 F800"
$"0040 0800 0040 0800 0040 0800 0040 0800"
$"0040 0800 0040 0800 03FF FFC0 04AA AC20"
$"04D5 55A0 04AA ADA0 027F F840 0200 0040"
$"01FF FF80 0008 0400 0008 0400 0008 0400"
$"1E08 0E00 1FF0 1F00 1E00 1F00 0000 0A",
/* [2] */
$"0000 0000 0000 0000 0000 0000 0000 0000"
$"0000 0000 0000 0000 0000 0000 007F 8000"
$"007F C000 007F E000 007F F000 007F F800"
$"007F F800 007F F800 007F F800 007F F800"
$"007F F800 007F F800 03FF FFC0 07FF FFE0"
$"07FF FFE0 07FF FFE0 03FF FFC0 03FF FFC0"
$"01FF FF80 0008 0400 0008 0400 0008 0400"
$"1E08 0E00 1FF0 1F00 1E00 1F00 0000 0A"
}
};
resource 'FREF' (128) {
'PRER',
0,
""
};
resource 'Dmp1' (0) {
Version
};
resource 'BNDL' (128) {
'Dmp1',
0,
{
'ICN#',
{
0, 128
},
'FREF',
{
0, 128
}
}
};
data 'HEXA' (-8192, "Printer Settings") {
$"0001 0002 0000 "
};
resource 'STR ' (-4092, "Right Button") {
"Modem"
};
resource 'STR ' (-4093, "Left Button") {
"Printer"
};
resource 'STR ' (-4091, "List label") {
"Select Speed and Click on a Port."
};
resource 'STR ' (-8191, "Spool File Name") {
"Print File"
};
resource 'DITL' (-8191,"Job dialog tempplate") {
{
{8, 321, 28, 381},
Button {
enabled,
"OK"
},
{9, 395, 29, 455},
Button {
enabled,
"Cancel"
},
{6, 6, 23, 127},
StaticText {
disabled,
Version
},
{30, 5, 46, 89},
StaticText {
disabled,
"Page Range:"
},
{30, 93, 45, 138},
RadioButton {
enabled,
"All"
},
{30, 140, 46, 200},
RadioButton {
enabled,
"From:"
},
{30, 205, 46, 237},
EditText {
disabled,
""
},
{30, 242, 46, 270},
StaticText {
disabled,
"To:"
},
{30, 272, 46, 304},
EditText {
disabled,
""
},
{55, 5, 71, 89},
StaticText {
disabled,
"Copies:"
},
/* [11] */
{55, 95, 71, 127},
EditText {
disabled,
"1"
},
{55, 145, 71, 200},
StaticText {
disabled,
"Feed:"
},
{55, 205, 70, 303},
RadioButton {
enabled,
"Continuous"
},
{55, 305, 71, 425},
RadioButton {
enabled,
"Sheet Feed"
}
}
};
resource 'DITL' (-8192, "Style Dialog Template") {
{
{44, 390, 64, 450},
Button {
enabled,
"OK"
},
{10, 390, 30, 450},
Button {
enabled,
"Cancel"
},
{10, 4, 30, 124},
StaticText {
disabled,
"DMP-110 v1.0"
},
{37, 66, 53, 120},
StaticText {
disabled,
"Style:"
},
{0, 0, 0, 0},
UserItem {
enabled
}
}
};
resource 'DITL' (-8190, "Next Page Template") {
{
{30, 60, 46, 140},
Button {
enabled,
"Done"
},
{30, 160, 46, 240},
Button {
enabled,
"Stop"
},
{8, 8, 24, 120},
StaticText {
enabled,
Version
},
{8, 120, 24, 265},
StaticText {
enabled,
"Insert Next Sheet"
}
}
};
resource 'DLOG' (-8191,"Job Dialog") {
{52, 16, 136, 492},
dBoxProc,
invisible,
noGoAway,
0x1,
-8191,
"Job"
};
resource 'ICON' (-4080, "DMP-110") {
$"0000 0000 0000 0000 0000 0000 0000 0000"
$"0000 0000 0000 0000 0000 0000 007F 8000"
$"0040 C000 0040 A000 0040 9000 0040 F800"
$"0040 0800 0040 0800 0040 0800 0040 0800"
$"0040 0800 0040 0800 03FF FFC0 04AA AC20"
$"04D5 55A0 04AA ADA0 027F F840 0200 0040"
$"01FF FF80 0008 0400 0008 0400 0008 0400"
$"1E08 0E00 1FF0 1F00 1E00 1F00 0000 0A" };
resource 'DLOG' (-8192,"Style Dialog") {
{30, 20, 113, 488},
dBoxProc,
invisible,
noGoAway,
0x1,
-8192,
"Stl" };
resource 'DLOG' (-8190, "Next Page Box") {
{48, 51, 100, 330},
dBoxProc,
-1,
noGoAway,
0x0,
-8190,
"Next Page"};
/* Print Record provides 60 dpi in the printing portRect as
* seen by application, and scales the image up to 120 dpi.
* It is useful scaling up BitMaps to double image size, or
* word processing applications where text justification is
* needed. */
resource 'PREC' (0,"Default Scale") {
3,
0,
0,
60,
60,
{0, 0, 630, 480},
{-15, -15, 645, 495},
512,
1320,
1020,
0,
1,
0,
0,
120,
120,
{0, 0, 1260, 960},
/* TPrXInfo */
120, /* rowBytes */
64, /* vertical dots */
959, /* horizontal dots */
7680, /* size of bit image */
20, /* bands per page */
0, /* bPatScale */
1, /* bUlThick */
1, /* bUlOffset */
1, /* bUlShadow */
0, /* scanTB */
0, /* unused */
1,
9999,
1,
0,
1,
0,0,
0,0,
0,
{ 0,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }
};
/* The printing code puts a copy of the last-used
* Print Record here. */
resource 'PREC' (1,"Last Used Print Record") {
3,
0,
0,
80,
80,
{0, 0, 840, 640},
{-20, -20, 860, 660},
512,
1320,
1020,
0,
1,
0,
0,
120,
120,
{0, 0, 1260, 960},
/* TPrXInfo */
120, /* rowBytes */
64, /* vertical dots */
959, /* horizontal dots */
7680, /* size of bit image */
20, /* bands per page */
0, /* bPatScale */
1, /* bUlThick */
1, /* bUlOffset */
1, /* bUlShadow */
0, /* scanTB */
0, /* unused */
1,
9999,
1,
0,
1,
0,0,
0,0,
0,
{ 4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
/* Print Record causes drawing program to reduce an object-
* oriented drawing by 33%, allows us to fit a larger image
* on a single letter-sized sheet of paper. If application
* does not check prStl.iPageV and prStl.iPageH fields, then
* results are exactly same as produced by "Exact BitMap."*/
resource 'PREC' (2,"Shrink To Fit") {
3,
0,
0,
80,
80,
{0, 0, 1280, 960},
{-20, -30, 1300, 990},
512,
1980,
1530,
0,
1,
0,
0,
80,
80,
{0, 0, 1280, 960},
/* TPrXInfo */
120, /* rowBytes */
64, /* vertical dots */
959, /* horizontal dots */
7680, /* size of bit image */
20, /* bands per page */
0, /* bPatScale */
1, /* bUlThick */
1, /* bUlOffset */
1, /* bUlShadow */
0, /* scanTB */
0, /* unused */
1,
9999,
1,
0,
1,
0,0,
0,0,
0,
{
2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
}
};
/* This Print Record reflects the actual size of the printed
* image in dots. Theres a one-to-one correspondence between
* screen pixels and printed dots. Best to use for painting
* programs, but printed image will be smaller than what is
* seen on the screen. */
resource 'PREC' (3,"Exact BitMap") {
3,
0,
0,
120,
120,
{0, 0, 1280, 960},
{-20, -30, 1300, 990},
512,
1320,
1020,
0,
1,
0,
0,
120,
120,
{0, 0, 1280, 960},
/* TPrXInfo */
120, /* rowBytes */
64, /* vertical dots */
959, /* horizontal dots */
7680, /* size of bit image */
20, /* bands per page */
0, /* bPatScale */
1, /* bUlThick */
1, /* bUlOffset */
1, /* bUlShadow */
0, /* scanTB */
0, /* unused */
1,
9999,
1,
0,
1,
0,0,
0,0,
0,
{
3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
}
};
/* Print Record gives a printing port resolution of 80 dpi,
* which is close to ImageWriter res & therefore close
* to what most Macintosh applications expect to see. The
* problem with this 'PREC' is that final image is scaled
* by a non-integral factor, & so text justification in word
* processing programs fails by a few bits width.*/
resource 'PREC' (4,"Compatible") {
3,
0,
0,
80,
80,
{0, 0, 840, 640},
{-20, -20, 860, 660},
512,
1320,
1020,
0,
1,
0,
0,
120,
120,
{0, 0, 1260, 960},
/* TPrXInfo */
120, /* rowBytes */
64, /* vertical dots */
959, /* horizontal dots */
7680, /* size of bit image */
20, /* bands per page */
0, /* bPatScale */
1, /* bUlThick */
1, /* bUlOffset */
1, /* bUlShadow */
0, /* scanTB */
0, /* unused */
1,
9999,
1,
0,
1,
0,0,
0,0,
0,
{
4,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
}
};
resource 'STR#' (-4080, "Baud Rates") {
{
"300",
"600",
"1200",
"1800",
"2400",
"3600",
"4800",
"7200",
"9600",
"19200",
"57600"
}
};
resource 'DITL' (-4078, "ATalk Items") {
{
{139, 121, 159, 181},
Button {
enabled,
"OK"
},
{54, 11, 125, 294},
StaticText {
enabled,
"This printer cannot use the printer port now "
"because AppleTalk is active. Please either "
"turn off AppleTalk, use the modem port "
"instead, or choose another printer."
},
{9, 235, 41, 267},
Icon {
enabled,
-4080
}
}
};
resource 'DITL' (-4079, "Sys41 Items") {
{
{90, 137, 110, 197},
Button {
enabled,
"OK"
},
{56, 21, 76, 308},
StaticText {
enabled,
"This Printer requires System 4.1 or newer!"
},
{11, 274, 43, 306},
Icon {
enabled,
-4080 }
}
};
resource 'ALRT' (-4079, "Sys41Alert") {
{40, 36, 164, 370},
-4079,
{
OK, visible, sound1,
OK, visible, sound1,
OK, visible, sound1,
OK, visible, sound1
}
};
resource 'ALRT' (-4078, "ATalk is on!") {
{48, 38, 216, 342},
-4078,
{
OK, visible, sound1,
OK, visible, sound1,
OK, visible, sound1,
OK, visible, sound1 }
};
Listing: MPWFinal.r
/* This is the "Stick-it-all-together" Rez file. */
/* file is part of the DMP-110 printer driver for the Mac
* series of computers. */
INCLUDE "PACK";
INCLUDE "PDEF0";
INCLUDE "PDEF4";
INCLUDE "PDriver";
INCLUDE "dmp-110.rsrc";