TweetFollow Us on Twitter

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";
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.