Printer Resource 2
Volume Number: 3
Issue Number: 12
Column Tag: Advanced Mac'ing
Printer Resource File, Part II 
By Earle Horton, Dartmouth College, Hanover, NH
Part 3: The dialogs, or Communicating with an application
The routines which handle the Style (“Page Setup”) and Job (“Print”)
dialogs are contained in a resource of type ‘PDEF’, ID 4. These routines communicate
user choices to the application via a data structure known as the “Print Record”,
which is always accessed by its Handle. The dialog routines also provide to the
application the ability to modify the dialogs, and insert its own event filters and item
handlers. Because of this, the structure of the dialog handling routines is quite rigid.
In order to understand the necessity for this way of doing things, it will be necessary
to obtain a copy of Macintosh Technical Note #95: “How to add items to the print
dialogs.” This Technical Note provides an excellent description of the workings of the
print dialog code, and it is not my intention to summarize it here. It would also be wise
to print out the header file which defines the Print Record data structure for your
development system. This part of the Printer Resource File is quite possibly more
complicated and harder to understand than even the printing code.
Fig. 2 Our Driver has a set-up option!
The dialog handling code starts off with the following header:
bra.w PrintDefault
bra.w PrStlDialog
bra.w PrJobDialog
bra.w PrStlInit
bra.w PrJobInit
bra.w PrDlgMain
bra.w PrValidate
bra.w PrJobMerge
This code provides several levels of support for applications, and we must be
careful to allow each application to use it in the way it is accustomed to. Each level of
support implies certain information which is supplied by the Printer Resource File,
and other information which the application wants to fill in itself. If an application
calls PrintDefault, then it wants us to fill in all the fields of the Print Record passed to
PrintDefault with suitable default values for the printer. (Note to C programmers
who cut their teeth on UNIX: Print Records are accessed by the Handle; get used to
accessing objects by the Handle RIGHT NOW.) An application may call PrintDefault,
and no other routines in this overlay. For this reason, the default print record,
contained in ‘PREC’ 0, should provide a reasonable page format, and all the fields
should have values which imply behavior which the user expects from, say, the
ImageWriter driver. My default Print Record is similar to that used by the
ImageWriter, but I have set all margins to the edge of the page. Study the Print Record
data structure, and those portions of Inside Macintosh which tell application
programmers how to use it.
Assume that the Print Record is contained in a data structure named “uPrint”.
uPrint.iPrVersion is equal to 3, the current version of the Printing Manager.
uPrint.prInfo.iDev contains -3 in its high-order byte, and a device-specific byte in
its low-order byte. I use the low-order byte to keep track of whether the character
pitch is 10, 12, or 15 characters per inch. The ImageWriter and LaserWriter
drivers use this field for resolution information. My driver will be called by the font
manager with this byte, and my text drawing code will use the value to determine
where to place text in its output buffer. uPrint.prInfo.iVRes and uPrint.prInfo.iHRes
provide the printer’s vertical and horizontal resolution, in units of pixels per inch. I
provide values here which are close to those used by the ImageWriter and the standard
Macintosh screen, thus insuring that font choices made by the Font Manager will be
similar to what my printing code expects. uPrint.prInfo.rPage is the “Page
Rectangle” of the specialized GrafPort used by applications in printing, in units of
pixels per inch. uPrint.rPaper is the “Paper Rectangle” and encloses the Page
Rectangle.
If you want to provide applications with maximum flexibility in choosing
margins, then set uPrint.prInfo.rPage equal to uPrint.rPaper. Remember that the
lengths of the sides of uPrint.rPaper, divided by the appropriate resolution, should
equal the physical paper size, so that the application knows how big the paper really
is. The conventional way to do this is to inset uPrint.prInfo.rPage inside
uPrint.rPaper, thus providing default margins. In this case, the origin of the
coordinate system is the top left corner of uPrint.prInfo.rPage, and the top left corner
of uPrint.rPaper has negative coordinates. An application may attempt to print outside
of uPrint.prInfo.rPage, but never outside of uPrint.rPaper. If you set up these fields
with margins, you should probably print in the margins when requested, and if
physically possible. Attempts to print outside the page rectangle should, of course, be
ignored.
The prStl sub-record contains four types of information. Why these four should
be grouped here, I don’t know. uPrint.prStl.wDev is private, you may put whatever
you want here. The next two fields, uPrint.prStl.iPage[VH], give the physical
dimensions of the paper, in units of 1/120 inch. Make sure that these agree with the
page rectangle, or something strange may happen. uPrint.prStl.bPort contains which
serial port to use. I do not use this field, but rather obtain this information from my
Chooser device interface. The next field is uPrint.prStl.TFeed, and indicates the paper
field mechanism. That this is in the style sub-record makes little sense, since the
information is set from the Job dialog. It may be in here for historical reasons, and it
is certainly too late to change it now.
uPrint.prInfoPT is a copy of uPrint.prInfo; structure assignment is a convenient
way to copy one onto the other. uPrint.prXInfo contains information used in spool
printing, and you can either put in reasonable values or zeroes here if you don’t do
spool printing (this example does not do spool printing). uPrint.prJob contains
information particular to a particular print job. uPrint.prJob.iFstPage and
uPrint.prJob.iLstPage give the first and last pages to print. The printing code is
responsible for keeping track of which pages get printed in draft printing, and not the
application. uPrint.prJob.iCopies is the responsibility of the application; if the user
wants multiple copies of the page, then the application must print them.
uPrint.prJob.bJDocLoop is always bDraftLoop, since we only support draft printing.
uPrint.prJob.pIdleProc is a ProcPtr to a background procedure to run during printing.
The default value is ‘nil’, and the application may put its own ProcPtr in here.
Usually this procedure just checks for a user abort, although it can do anything except
use the Print Manager (thankfully, we don’t have to make the Print Manager
reentrant!).
The rest of the fields of the Print Record are either for spool printing or are
private. If you need to store extra information here, then use the uPrint.printX array.
The only private field I have found it necessary to use is uPrint.prInfo.iDev, although
you may need to use more.
Fig. 3 Our Set-Up dialog sets baud rate, control chars
When an application calls PrValidate, it has a completely filled in Print Record
obtained from either PrintDefault, the dialog handling routines, or perhaps fabricated
by the application itself. Check the Print Record here to see that the resolution is
correct, and verify that the page size can be handled by the printing code. If the Print
Record cannot be used, use PrintDefault to convert it to the default values, and return
TRUE. Otherwise, do nothing and return FALSE.
PrJobMerge is used when it is desired to copy fields of from Print Record to
another, such as when printing multiple files. Copy the job sub-record and update the
printer information, band information, and paper rectangle in the destination print
record. These instructions are from Inside Macintosh and are admittedly somewhat
vague, since I don’t really know what “update” means here. I just copy the stuff which
requires updating from the source Print Record.
Read Technical Note #95 at this point to see how the dialog routines work. There
is not much room for orginality in either PrDlgMain, PrStlDialog, or PrJobDialog.
The dialog initialization routines fetch the appropriate dialog template from the
Printer Resource File, usually with GetNewDialog, and set the initial values of radio
buttons, check boxes, and the like. The dialogs are not actually drawn until PrDlgMain.
Keep in mind that the application may be modifying the dialogs after your dialog init
routine, then passing the pointer to the modified dialog to PrDlgMain. This means that
your dialog event filter and item handler may be called after routines supplied by the
application. The Print Record associated with the TPrDlg dialog object is not modified
unless the “Done” or “Ok” button is hit (item number 1 in the dialog). The fDoIt and
fDone fields signal to PrDlgMain whether the Print Record should be validated or not,
and whether the cancel or finished button has been hit.
My style dialog contains three radio buttons for printer pitch, and two check
boxes for portrait and landscape mode. The item handler sets the appropriate control
values when one of these is hit, and records the user choices only if the Ok button is
hit. It gets information about which values to put in the print record from the control
settings. This code only supports 8 1/2" by 11" paper, in one of two orientations. If
you want to provide more paper sizes, and perhaps controls to set the margins, then
provide more dialog items and a place for the item handler to get the other settings.
Several paper sizes could be provided merely by having several Print Records stored
in the Printer Resource file as ‘PREC’ resources. Remember to fill in only the fields
having to do with paper size, orientation, and page size when doing the style dialog. The
device field can also be set here, since it is a private field. After the item handler fills
in these fields, the application’s item handler, if used, gets a shot at the dialog object
and the Print Record, then PrDlgMain cleans things up and disposes of the dialog
object.
The job dialog is handled in identical fashion to the style dialog. The item handler
fills in the job record from the user’s choices. The job dialog, in comparison to the
style dialog, is pretty well standardized. Provide a mechanism whereby the user can
specify multiple copies and a page range, as well as sheet or fanfold feed. After filling
in the appropriate fields in either the style or job dialogs, set the value of the fDone
and if necessary the fDoIt fields of the Print Dialog object, and return. PrDlgMain will
validate the Print Record if fDoIt is set, and return the value of fDoIt when done. This
is how the application knows whether the Print Record has been selected by the user
and, in the case of the job dialog, whether to proceed with printing.
The standard print dialogs are not a good place from which to obtain information
which is not part of the regular ImageWriter or LaserWriter dialogs, since the
application is not required to use them. If you need information which is particular to
only your printer driver, then use the Chooser device interface instead.
Fig. 4 Our Print Setup dialog box
Part 4: The high-level printing code
This is the part everybody has been waiting for, I’m sure of it! How to translate
QuickDraw drawing commands into the printed word (or even pretty pictures). The
code found in the ‘PDEF’ resources 0, 1, 2, and 3 does exactly this. Each one of these
resources starts with an offset table to the four externally callable routines.
bra.w PrOpenDoc
bra.w PrCloseDoc
bra.w PrOpenPage
bra.w PrClosePage
Applications use these routines to print a document in the following manner:
a) Call PrOpenDoc to get a printing GrafPort to draw in.
b) For each page in the document:
i) Call PrOpenPage to reinitialize the port.
ii) Draw in the printing GrafPort, using ordinary QuickDraw commands.
iii) Call PrClosePage to signal that the page is done.
c) Call PrCloseDoc to dispose of the printing GrafPort.
PrOpenDoc is just like any of the routines which create a new GrafPort for the
application to draw in: NewWindow, for example. The port is customized for printing,
however, by means of ProcPtrs which point to our drawing routines. In addition, we
get four long integers to play with as we see fit. My printing code works in the
following fashion: Attempts to draw text in the printing port results in the text being
stored in a big rectangular array of characters. When a page is completed, the
application signals this to the printing code, and the text is sent to the printer, all at
once. I do this because it is the easiest way for me to do it, it avoids keeping track of
the print head, and it allows me to emulate a reverse line feed. Think about it: an
application may print some text, then attempt to move up the paper, rather than down.
If I keep all the text in an array, then I don’t have to figure out how to reverse the
direction of paper travel.
The way I do things in this code depends to some extent on the demands imposed by
this method of printing. This method is fine for text, but I can think of one
disadvantage when you (or I) want to do graphics. Memory on a Macintosh is finite.
We cannot expect to print in high resolution on a dot matrix printer by first buffering
up a bitmap of the whole page, e specially when the Macintosh gets a real multi-process
capability operating system. In order to handle graphics properly, one has to develop
the routines to do either draft mode printing or spooling. It’s a good thing for me I
don’t have to come up with them for this article! Either true draft mode or spool
printing will have some similarities to the printing method I employ here, but both
will require more bookkeeping. To summarize, my printing code is a good start, and is
good for text, but can only go so far.
Fig. 5 Print Dialog
Central to printing is a data structure known as a TPrPort. This is merely a
GrafPort, with a QDProcs structure, four longs, and a pair of Booleans added. A
TPrPort is accessed by pointer, or TPPrPort. An application obtains a TPrPort to
draw in by calling our PrOpenDoc routine. It must pass to PrOpenDoc a valid Print
Record, and may pass us one or two storage pointers. If we get a pointer to storage to
use for the printing port, then we fill out the TPrPort using the application’s storage,
and flag that we did so in the fOurPtr field. Otherwise, we obtain a pointer to the
required storage, and begin to initialize the printing port. This is a fairly complicated
process, and I only handle text! I also obtain storage for my output buffer, and store
the pointer in the lGParam4 private field of the printing port. The process is given,
step by step, below.
First, obtain from NewPtr sufficient storage for the text output buffer and the
printing port. If storage is not available, stuff the constant iMemFullErr into the low
memory global PrintErr and return ‘nil’. A side note may be appropriate here.
PrintErr is the integer located at 0x0944. According to some early documentation I
have, PrintErr is only part of “PrintVars: 10 print code variables [16 bytes]” stored
starting at this location. Presumably, if you are writing a printer driver, then you
own all 16 bytes. I am reluctant to use any of these, however, until I find out how
Apple uses them. PrintErr is the only variable in PrintVars I can find documentation
for. Accordingly, the only use I make of PrintVars is to check PrintErr for user abort,
and to stuff it with an error code if I have to. I don’t need low memory globals,
however, since I can share storage with my driver. The function DrvrStorage returns
a pointer to the printer driver’s private storage, and I access the driver variables
from within my printing code. Programmer’s advice: don’t use low-memory globals
unless you are given no other choice, and you really know what they are for!
After obtaining a pointer to the driver’s private storage, stash some useful
information there for later use. A copy of the entire Print Record should be enough,
but I also take this opportunity to calculate the number of lines per page, and set a page
number counter to 1. Initialize here any information you want to save that is asociated
with a particular printing job. At this point, we are supposed to save a copy of the
current print record in ‘PREC’ #1 in our resource file. Why, I don’t know.
Presumably, either our printing code or applications will benefit from having
available a copy of the last-used Print Record. I certainly don’t use it.
Now we open and configure the printing port. The printing port is opened like
any other GrafPort, just call OpenPort with the pointer to it. Now we customize it for
printing. Whenever possible, use ToolBox calls to manipulate the fields of the printing
port, rather than storing into them directly. It’s easier, and is the recommended
method. The TPrPort contains a sub- record, gProc, of type QDProcs:
QDPtr textProc, /* Draw text. */
QDPtr lineProc, /* Draw a line. */
QDPtr rectProc, /* Draw a rectangle. */
QDPtr rRectProc, /* Draw rounded-corner rect.*/
QDPtr ovalProc, /* Draw an oval. */
QDPtr arcProc, /* Draw an arc. */
QDPtr polyProc, /* Draw a polygon. */
QDPtr rgnProc, /* Draw a region. */
QDPtr bitsProc, /* Bit-transfer procedure. */
QDPtr commentProc, /* Handle picture comments. */
QDPtr txMeasProc, /* Return the width of text. */
QDPtr getPicProc, /* Get info from QD pict. */
QDPtr putPicProc /* Stash info in picture. */
All drawing routines which affect a GrafPort are funnelled through one of these
thirteen routines! First, call SetStdProcs, to fill in the gProcs field of the TPrPort
with the default QuickDraw primitives. Then, install ProcPtrs to point to the
QuickDraw calls you implement. (If we are only drawing text, we must use
SetStdProcs to fill in the ProcPtrs we do not supply, if only to keep track of the Pen
location for us.) I now fill in the textProc and txMeasProc fields of gProcs with
pointers to my text drawing and text measuring routines. Next, the grafProcs field of
the printing port’s GrafPort is made to point to the gProcs field of the printing port.
The device field of the GrafPort is filled in with the iDev field of the Print Record.
This will be used later by our driver when it is called by the Font Manager.
Set the portBits.bounds rectangle of the printing port to an empty rectangle, so
QuickDraw routines will not try to set bits in it. (If you are doing graphics printing,
it is up to you how the port’s bitmap is handled.) Call SetPort to make the printing
port the current port. Set the portRect to be equal to the page rect from the printing
record, using PortSize. Using MovePortTo, translate the printing port’s coordinate
system so that the upper left corner of the paper is the global origin, if you desire to
allow printing on the entire page. We will use LocalToGlobal later to find the correct
location to place text. Do this even if you consider the page rectangle to be equal to the
paper rectangle, since an application may create its own Print Record. The
application’s page rectangle and paper rectangle might be anything which we allow in
PrValidate, and are not limited to values which we supply in the PDEF 4 routines.
Issue a reset command to the printer driver. Initialize whatever variables you
need to maintain throughout a printing job. If you use a buffer, clear it out. Save a
copy of the Print Record in resource ‘PREC’ #1, in case anybody is still interested. If
the application hasn’t installed a pointer to an idle procedure, install one of your own.
(The default idle procedure checks for command-’.’ and aborts printing if it finds
one.) Return the pointer to the printing port to the application when all tasks are
complete.
The QuickDraw primitives which I replace in the printing port are StdText and
StdTextMeas. All QuickDraw text drawing routines are glue to call StdText. Most, but
not all, routines which determine the width of text call StdTextMeas. These routines
work in my printing port as follows:
When MyStdText is called, it first calls GetPen to get the current QuickDraw Pen
location. The device field of the printing port is used to find the width of a character.
Since I only support mono-spaced fonts, I have but three values for the width of a
character. I call LocalToGlobal to translate the pen location from local (page) to global
(paper) coordinates. Characters which are to be printed are stored in a big array of
lines, and the global pen location is used to determine the starting array index of where
to put them. Once the proper line and offset is found, a call to BlockMove stashes the
text for later printing. Finally, a call to Move is used to update the pen location by the
proper horizontal offset. Note: I have “drawn” text, therefore I have moved the
QuickDraw pen. The total horizontal offset depends upon the character pitch, as
determined in the page setup dialog.
When MyStdTextMeas is called, I am being asked “How wide is this chunk of
text?” I am passed a pointer to a text buffer, an integer telling how many characters
will be drawn, a pointer to a FontInfo record, and two scaling Points. The idea here is
that I now get an opportunity to modify the font information if my printer cannot
supply exactly what is asked for. I change to font information to reflect a constant size
font, and modify the two scaling points according to what is in the GrafPort device field.
Numer.h over denom.h gives the horizontal scaling, and numer.v over denom.v gives
the vertical scaling. I return the width times the number of characters. The width
depends upon the character pitch, as in MyStdText. If your printer has a proportional
font, then you will have to be considerably more sophisticated here, and in your
StdText routine, too.
You may install as many QuickDraw primitives in the gProcs field of your
printing GrafPort as your printer, and your programming abilities, can handle. If you
want to attempt graphics printing on a dot matrix printer, then most of the routines
you install will be involved with translating some QuickDraw command into a bitmap,
presumably as high as your print head and as wide as your paper. Then you will have
to store the bitmap somewhere, or print it directly. The standard ImageWriter driver
provides two methods of doing this. In draft mode printing, bitmaps are printed
immediately. In spool printing, all QuickDraw calls between PrOpenPage and
PrClosePage are stored in a QuickDraw picture, which is kept in a special file for this
purpose. A number of pages are buffered up in the Print File, then the routine
PrPicFile is called to print them out. There is nothing mysterious about this, but the
translation process from QuickDraw calls to dot-matrix printer commands may mean
some work!
I recommend starting with the simplest method of graphics printing, then
working up. If you want to implement graphics printing on your printer, then make
the “command-shift-4” routine in the driver work first. Get a copy of the QuickDraw
manual and a copy of the Printer manual, go to a quiet place, and work out the
tranlsation algorithm from screenBits to paper. The feeling of accomplishment you get
will be a tremendous boost!
PrOpenPage is called to clear out the printing port and make it ready for the next
page. All variables associated with your printing port should be returned to the initial
state. Anything appropriate for the printer at top of page should be done here.
PrOpenPage also makes the printing port the current port, although not all
applications need this feature.
PrClosePage is called when the application is finished with printing the current
page in the document. My method of printing waits until PrClosePage is called, then
prints out the whole page at once. This method is appropriate for text-only printing,
but may not be for other methods, e specially if memory is tight. Between PrOpenPage
and PrClosePage, I store text in a dynamically allocated array of structures called
“line”s. I use an integer to flag whether the line actually has text in it, and a
character array to hold the text. If I handle up to 66 lines on a page, and 164
characters in a line, then it takes my code 11 kilobytes to hold the array of lines. If
we wish to handle multiple styles (italic, underline, etc.) then we could modify the
line structure to hold style information as well without increasing the size of the
array of lines very much. For graphics printing, this method is probably not
appropriate because of the large amount of memory that would be needed to hold
bitmaps.
Here is the algorithm used in MyPrClosePage to actually print the text. I obtain a
pointer to the driver’s global storage, where I have stashed information about the
current print job’s parameters. From the prJob.iFstPage and prJob.iLstPage fields, I
determine whether the current page is within the range of lines which get printed. If
the current page is before the first page to be printed, MyPrClosePage returns without
doing anything except incrementing the page counter. If current page is after the last
page, I stuff an error code in PrintErr. (I don’t know whether this is the standard way
to stop printing at this point, but it seems to work.) Then I cause the user’s
“end-of-file” string to be sent to the printer. After checking the page range, I begin
the process of printing out the lines in the output buffer.
Actual printing is implemented by calling the driver’s control routine with a
csCode = iPrIOCtl, lParam1 = the pointer to the base of the current line’s text array,
and lParam2 = the number of bytes to print. End of line is handled by calling the
driver’s control routine with the appropriate code for end of line. For each line in the
buffer:
Call the pIdleProc procedure of the current print record.
If PrintErr is equal to iPrAbort then return.
Else
Check the flag to see if the line has text in it.
Find out how many characters (index of last non-space
character +1)
Call the driver to print the text.
If at end of page, then
Call the driver to do a form feed.
Else
Call the driver to advance the paper.
Calling the driver’s control routine to print the text is only one method. It is
also possible to send the text to the serial driver directly from the printing code, or
use any other method you can think of here.
PrCloseDoc is used to dispose of a printing port, and to signal to the printing code
that the current print job is finished. Any storage which has been allocated for
printing is disposed of here, and the port is closed. The TPrPort must be closed prior
to disposing of the port, or the memory allocated by the visRgn and clipRgn will not be
reclaimed. If the printing port storage was allocated by the printing code, dispose of it
here. Don’t dispose of storage which belongs to the application!
Part 5: PrPicFile
The PDEF 5 overlay contains the routine PrPicFile. The file “PDEF5.c” merely
shows the proper header to use, and the parameters passed to this routine. When spool
printing, the printing code stores all drawing commands passed to it in a disk file. The
routine PrPicFile is called by the application after the print job has been stashed away
in the picture file. PrPicFile then prints the file. Recommended procedure is to swap
as much of the application out of RAM as possible, then call PrPicFile. This means that
if you choose to implement spool printing, then you can use lots and lots of RAM when
you are actually printing. You may need lots and lots of RAM to translate a QuickDraw
picture to a nice looking dot matrix output page.
I am glad I didn’t have to implement spool printing for the purposes of this
article, but I certainly wish everyone the best of luck.
Part 6: Installation, or Talking to the user
The Chooser desk accessory provides a standardized interface for new device
drivers to obtain configuration information from the user. This means there is no need
to provide a configuration program in order to be able to handle multiple device types.
When a device icon is selected in the Chooser dialog, the Chooser looks in the device
resource file for a resource of type ‘PACK’, ID -4096. This resource has the
“standard” header for stand alone code resources as implemented by LightspeedC and as
documented in the Device Manager chapter of Inside Macintosh Volume 4. That chapter
is recommended reading for what follows.
The format of the ‘PACK’ resource header is as follows:
Offset (hex) Word
0 short branch to offset 0x10
2 Device ID (word)
4 ‘PACK’ (long word)
8 0xF000 (-4096)
A Version word
C Flags (long word)
10 Start of code
LightspeedC formats this resource header correctly, but provides no way to set
the version and flags. A separate utility program is provided to show how to set these.
My resource has a version number of 1, and uses the Chooser right button. I do not use
the List Manager here, but provide a string for the Chooser to label the list when
choosing my device file. When my icon is selected in the Chooser, this string appears
as a label to tell the user that more configuration options are possible. Setting the
flags to the proper value tells the Chooser that I will use the right button, and ‘STR ‘
number -4092 gives it a button title to use: “Setup”. The Chooser communicates
with my ‘PACK’ resource as if it were the following Pascal function:
FUNCTION Device(message,caller: INTEGER; objName,zoneName:
StringPtr; p1,p2: LONGINT) : OSErr;
I declare the function in C as:
pascal OSErr main(message,caller,objname, zonename,p1,p2)
int message,caller;
StringPtr objname,zonename;
long p1,p2;
The only field which is of interest to me here is the integer message, and only
when that is equal to the constant buttonMsg (19). When called with the button
message, the ‘PACK’ resource puts up a dialog box, and gives the user a set of
configuration options. The baud rate can be set here, as can flow control. I provide
five editText items for the user to enter printer control strings. Most, but not all,
printers will produce a carriage return and then a line feed when they receive the
string: “\r\n” or “^M^J” or a decimal 13 followed by a decimal 10. Using the second
notation, I let the user edit these strings so that his printer can be used. For example,
with my Tandy DMP-110, one uses “^M” for end-of-line, “^L” for end-of-page, and
there are several choices for the string for initializing the printer. I also provide at
this time strings for top-of-page and end-of-job, although I usually don’t use either
with my printers.
Another way to handle this would be to provide several configuration files, with
the same creator type as your Printer Resource File. The Chooser maintains a list box
for use by device packages; you have probably seen it in use if you have chosen a
LaserWriter or used AppleShare. The list box could be used to select between
configuration files in the same manner that the LaserWriter driver selects between
devices on the AppleTalk network. Details of how to do this can be found in IM4.
When my device package is called, I get the user’s choices via ModalDialog, and
store the results in the printer resource file if the “Save” button is hit. The Printer
Resource File is always the last-opened resource file when the package is called by the
Chooser, so my configuration resources are always right at hand. I use an array of
integers to store configuration information, and a string list to store the printer
control strings. When changing the string list, make sure to resize the Handle to the
resource in case the user has entered longer or shorter strings than were there before.
There is one problem here. When the device package is chosen, it may be because
the user wants to change some of my configurations, and not to change printers at all.
My driver will not load a new set of settings, however, until its open routine is called.
This may cause some delay in realizing the effects of changing the settings from the
Chooser, particularly if an application calls PrOpen only once, which many older
applications do.
Part 7: Nuts and bolts, or Putting it all together
The Daisy printer Resource File requires six LightspeedC project files, seven
resource files in addition to the Printer File, and ten source files to create. Because of
the environment used by LightspeedC, there is no Makefile, so you will have to follow
instructions here in order to get it right. (This task will be much easier with a
“batch” oriented development system.)
Files used to create the Daisy Printer Resource File: (All files are contained on
disk “src” and in folder “Printer”, available on the source code disk for this issue of
MacTutor. See the MacTutor Mail Order Store page for details on obtaining this disk.)
See Table 1.
Note: You must obtain the SysEnvirons glue and header file for your development
system, also.
LightspeedC is an excellent choice for a development system, but there are things
which it will not do. It will not allow you to produce the correct headers for the PDEF
resources, for instance, because it uses a standard code resource header. I get around
this limitation by hacking the code resources after LightspeedC is finished with them.
Prior to the offset table at the beginning of each of these resources, I put an illegal
instruction (0x4afc). A utility program I wrote reads in the code resource, strips off
everything up to and including the illegal instruction, and writes out the modified code
resource to its file. Doing things this way imposes certain restrictions on what you
put in the code. Specifically, you cannot have anything in the PDEF code which will
cause LightspeedC to insert code in front of your main() function. Things I know of
which will cause this to happen are:
Linking libraries before the main code.
Switch statements.
The PDEF resources must not have switch statements when using LightspeedC.
Any libraries used in them must have names which are lexically greater than your
printing code. Name the printing code source file “AAAwhatever.c” to accomplish this,
or perhaps name the library file “ZZZlib.lib”. These restrictions of course do not
apply to other development systems, which may, however, impose other ones.
The PACK resource and the DRVR both require flags to be set in their headers.
The PACK resource must have flags to tell the Chooser what its capabilities are, and the
DRVR must respond to the correct subset of driver calls. My utility program sets the
flags correctly for the PACK resource and the DRVR.
What follows is a detailed description of how to make the printer resource file
using LightspeedC and RMaker. Too bad there is no “make” utility for LightspeedC!
Instructions for other development systems may be more or less similar to these.
Change to suit your disk/machine/development system configuration.
File name Type Creator What
LightspeedC “Project” files:
DRVR_proj PROJ KAHL Driver project
PACK_proj PROJ KAHL Chooser interface project
PDEF0__proj PROJ KAHL Draft printing code project
PDEF4_proj PROJ KAHL Dialog code project
PDEF5_proj PROJ KAHL PrPicFile project
UTILS_proj PROJ KAHL Utility programs project
resource files:
DaisyDRVR ???? ???? ‘DRVR’ -8192, ‘DATA’ -16320
DaisyPACK ???? ???? ‘PACK’ -4096
DaisyPDEF0 ???? ???? ‘pdef’ 0 -> ‘PDEF’ 0
DaisyPDEF4 ???? ???? ‘pdef’ 4 -> ‘PDEF’ 4
DaisyPDEF5 ???? ???? ‘pdef’ 5 -> ‘PDEF’ 5
DaisyPREC0 ???? ???? ‘PREC’ 0
dialogs.rsrc ???? ???? resources created by RMaker
Daisy ???? ???? The final actual printer resource
file!
text files:
Daisy.r TEXT KAHL RMaker source to put it all together
dialogs.r TEXT KAHL RMaker source to resources created
by RMaker
mkDefault.c TEXT KAHL C code to create default Print Record
PACK.c TEXT KAHL Chooser device interface code
PDEF0.c TEXT KAHL Draft printing code
PDEF4.c TEXT KAHL Dialog code
PDEF5.c TEXT KAHL PrPicFile stub code
prglobals.h TEXT KAHL Daisy header file, #included in all
sources
Utils.c TEXT KAHL Utility program to reformat code
headers
XPrint.c TEXT KAHL Driver code
Application files:
Code Fixer [TOKEN:25455]mpiled Utils project
Fig. 6 All the files required to build a printer resource file
Obtain a 512ke or better Mac. Create a system disk named “bin”, with
LightspeedC and a folder of “#include” files on it. Call the system folder “sys”.
Create another disk named “src” with a folder named “Printer” on it. Put the printer
driver sources in this folder. Boot from “bin” and put “src” in the other disk drive.
Make a project for the driver. Set the project type to “Device Driver”, number
2, name “.XPrint”. Use the “Add” item under the “Source” menu to add “XPrint.c”
and “Environs.lib”. When you choose the “Build Device Driver” menu item,
LightspeedC will create a file which contains ‘DRVR’ #2 and ‘DATA’ #-16320. The
RMaker command file converts the driver to ‘DRVR’ #-8192, named “.XPrint”
before installing it in the Printer Resource File. The ‘DATA’ resource is converted to
‘PREC’ #-8192. (The driver file created by LightspeedC is to be placed in the folder
“src:Printer” if you wish to use the RMaker command file and utility program
without modification.) The driver file is named “DaisyDRVR”.
Make a project for the Chooser device interface. Project type is “code
resource”, type ‘PACK’, ID -4096. Add the “PACK.c” source to it and create the code
resource, placing it in the file “DaisyPACK”.
Make a project for each of the ‘PDEF’ resources. Set the project type to “code
resource”, type ‘pdef’, ID number from the source file name. (The utility program
converts ‘pdef’ to ‘PDEF’ when it reformats the code.) Create the three resource files
shown. Example: PDEF0.c is used in PDEF0_proj to create DaisyPDEF0, containing
‘pdef’ 0. Later, the utility program reformats the header and converts it to ‘PDEF’ 0.
And so on. (Please, Father, buy me a hard disk and MPW C.)
Create an empty resource file in the “Printer” folder named DaisyPREC0. You
can use the LightspeedC editor to create an empty text file for this purpose, since
LightspeedC text files have resource forks. Just create the text file, and set the tabs in
it, so LightspeedC creates an ‘ETAB’ resource. Alternately, use RedEdit. The utility
program puts the default Print Record in this file, but does not have the ability to
create the file by itself.
Create a LightspeedC project file for the utility program, “Utils.c”. Type is
Application, name is whatever you like. Add the source file “Utils.c” and select
“Run” from the “Project” menu. There is no need to create the application, unless
you want to use it under MultiFinder. When you run Utils.c, it will set all the flags
properly in the ‘DRVR’ and ‘PACK’ resources, reformat the ‘PDEF’ code resources, and
create a default Print Record, ‘PREC’ 0.
Run RMaker. Compile “ dialogs.r” to create the dialogs, strings, string list, and
private resource types. RMaker will create “src:Printer: dialogs.rsrc” at this point.
Compile “Daisy.r”. RMaker gathers together all the resources for the Printer
Resource File, and creates “bin:sys:Daisy”. Edit the RMaker source files if your
pathnames differ or if you want to change any of the dialogs or other resources. Place
Daisy in your system folder.
Run the Chooser desk accessory to install the driver in the system file, and to set
any options which appear in your Chooser interface dialog. Chooser may not want to
fetch the new ‘DRVR’ resource from your Printer Resource File if Daisy is your only
Printer Resource File. (Chooser is meant for the end user, who usually does not
compile new printer drivers several times a day.) It may be necessary to have two
Printer Resource Files during the development stages. When you create a new Printer
Driver, first install the extra Printer File, then re-install Daisy. Do this just to
make sure the Chooser clears out the old copy of the driver for you. Do not attempt to
use RMaker to install the new driver in the system file (I tried this once). The
Printer Resource File is installed on other system disks by copying it into the system
folder, just as you would install an ImageWriter or a LaserWriter printer driver.
Part 8: What else, how to find out more.
Much of the information I needed to make my Printer Resource File work was
buried in the depths of the Printer Manager Chapter of the Promotional Edition of
Inside Macintosh. This is the copy that looks like a Manhattan phone book. The Chooser
device interface information is found in the Device Manager Chapter of Inside
Macintosh 4. In order to use the Chooser interface in a more advanced fashion, it will
be necessary to learn how to use the List Manager, also in Inside Macintosh 4. The
secrets of the Print dialogs were revealed in Macintosh Technical Note #95. There is a
new low-level printer call, PrGeneral, whose calling sequence is given in Technical
Note #128. This routine is found in ‘PDEF’ #7, I believe, and the resource
apparently has a standard header (like the ‘PACK’ -4096 resource).
As you may know, Macintosh Technical Support is pretty sparse on documentation
for writing a Printer Resource File. The following features of the printer interface
were incorrectly or incompletely documented in my Macintosh documentation. More
may be found.
° The driver’s Font Manager control call is passed a pointer to an FMInput, and not
an FMOutput, record.
° Some applications require PrOpenPage to do a SetPort to the Printing Port, while
most do not. My PrOpenPage documentation does not mention this.
° The numer and denom parameters to StdText are Points, and not integers.
(Perhaps it is time to retire the “PhoneBook”.)
° PrintVars: What are they, and how may we use them?
Nevertheless, Apple has released all (or most of) the information that is
necessary to define the specifications for a Printer Resource File, and they have told
application developers “This is how the sucker is called.” Without commenting on the
prettiness (or lack thereof) of the Printer Manager interface, one can say that this
interface will not change (much) in the near future, unless Apple is ready to break
every application which prints on the Macintosh. For good or ill, then, if you want to
write a Printer Resource File, you have to do it (mostly) this way.
Daisy was compiled on a 512ke with 1 Meg of RAM installed and an external
800k disk drive. The LightspeedC compiler version 2.11, System 4.1, Finders 5.5
and 6.0b3, Chooser 3.1, and some version of RMaker were used to produce it. The
WriteNow word processor was used to test it. The following applications were known
to be able to print with it as of September 28, 1987:
DarTerminal 3.2 (Dartmouth AppleTalk emulator)
Finder 5.5, 6.0b3 (Print catalog)
MockWrite 4.3 desk accessory
LightspeedC 2.11
MacTerminal 2.2
MacWrite 4.5,4.6
MDS Edit 2.0 d1
Microsoft Word 1.05
Microsoft Word 3.01
Pretty Print
Teach Text
WriteNow 1.00
This is a real bonus for me, since I was only trying to get something to work with
WriteNow! The structure of the Printer Resource File is complicated, the interface is
hard to understand, and the number of example programs is now exactly one, but the
Macintosh Printer Manager fulfills its primary objectives: It is Device Independent,
and works with all (properly written) Macintosh applications.
I would like to thank David Oster, who insisted that it would be possible to write
one of these things with the existing documentation. David, you were right!
Control Key Codes
Control characters are sent to the printer by entering “^M” in the dialog box.
This would send a “control-M” to the printer, which is an ascii 13 or a carriage
return. Here are the control characters on the Mac II extended keyboard. The first
column gives the control-key sequence as you would type it, the second column gives
the decimal ascii character generated, the third column gives the ascii code name, and
the last column gives an alternative control-key sequence on the extended keyboard.
Use this table to figure out the printer control strings needed to make the printer
operate properly. There does not appear to be any printable character that would
generate ascii codes 0, 30 and 31 when used with the control key on this keyboard.
This is a serious problem when using this driver with the imagewriter II, since some
of it’s programming requires the NULL character and there is no way to generate this.
The apple (command) key in combination with the tilde (`) key does produce ascii 0,
but this is not a printable combination so can’t be used in our set-up dialog. Normally
control-@ produces ascii 0 on normal keyboards, but on the Apple keyboard it does
not! Note that all the function keys return the same ascii value! The option, apple and
shift keys do not generate a code. A simple Basic program generated this table, as
shown below:
{1}
REM This program finds control codes
CLS
PRINT “enter your control character...”
key:
key$=INKEY$
IF key$=”” THEN GOTO key
PRINT “The ascii code is “;ASC(key$)
GOTO key
CONTROL- ASCII NAME OTHER KEY
A 1 SOH HOME
B 2 STX
C 3 ETX ENTER
D 4 EOT END
E 5 ENQ HELP
F 6 ACK
G 7 BEL
H 8 BS DELETE
I 9 HT TAB
J 10 LF
K 11 VT PAGE UP
L 12 FF PAGE DOWN
M 13 CR RETURN
N 14 SO
O 15 SI
P 16 DLE F1-F15
Q 17 DC1
R 18 DC2
S 19 DC3
T 20 DC4
U 21 NAK
V 22 SYN
W 23 ETB
X 24 CAN
Y 25 EM
Z 26 SUB
[ 27 ESC ESCAPE
\ 28 FS L.ARROW
] 29 GS R.ARROW
30 RS U.ARROW
31 US D.ARROW
SPACE 32 SPACE
{2}
/*
* LS C source for PDEF 0, to implement draft mode printing
* on a serial device.
* Earle R. Horton, September 19, 1987.
* All rights reserved.
*/
#include “prglobals.h”
#include
pascal TPPrPort myPrOpenDoc();
pascal void myPrCloseDoc();
pascal void myPrOpenPage();
pascal void myPrClosePage();
pascal void myStdText();
pascal int myStdTextMeas();
void myClearPage();
Ptr allocate();
void free();
void bcopy();
DPstorage DrvrStorage();
void checkabort();
main(){
asm{
dc.w ILLEGAL ;; So I can find it...
jmp myPrOpenDoc
jmp myPrCloseDoc
jmp myPrOpenPage
jmp myPrClosePage
}
}
/*
This function is supposed to return a pointer to a specialized
GrafPort (a TPrPort) customized for printing. Due to the paucity of
documentation on how to go about this, I do not know whether I am
going about this in exactly the right way, but I sure hope so. I set
portBits.bounds for the port to the empty Rect {0,0,0,0} and then
install the standard QuickDraw routines as GrafProcs. In place of
StdText, I put my own StdText routine. Hopefully, QuickDraw will keep
track of the correct pen location for me, and call my routine
whenever it is necessary to draw text. I just put the text in a big
buffer for now, and then print it out when I get called to close the
current page. This takes up some memory, but solves the problem of
what to do when the application wants a reverse line feed.
Other tasks: save a copy of the user print record for later use in
formatting the output page; save a copy of the user print record in
the printer resource file
*/
pascal TPPrPort myPrOpenDoc(hPrint,pPrPort,pIOBuf)
THPrint hPrint;
TPPrPort pPrPort;
Ptr pIOBuf;
{
TPPrPort thisport;
pline thepage;
Handle us;
register DPstorage dsp;
THPrint savePrint;
PrParam *pb;
us = (Handle)(GetResource(‘PDEF’,0));
asm{
move.l us,a0
}
/* Assign storage for printing port and page buffer. */
if((thepage = (pline)allocate((long)(NROWS*sizeof(line)))) == nil){
PrintErr = iMemFullErr;
return nil;
}
else if(pPrPort == nil){
if((thisport = (TPPrPort)
allocate((long)sizeof(TPrPort))) == nil){
free(thepage);
PrintErr = iMemFullErr;
return(nil);
}
thisport->fOurPtr = TRUE;
}
else {
thisport = pPrPort;
thisport->fOurPtr = FALSE;
}
/* Copy print record into private storage area. */
dsp = DrvrStorage();
pb = &dsp->prpb;
dsp->Print = **hPrint;
thisport->lGParam4 = (long)thepage;
dsp->Print.prJob.bJDocLoop = bDraftLoop;
OpenPort(thisport);
/* Fill out gProcs for this port. */
SetStdProcs(&thisport->gProcs);
thisport->gProcs.textProc = (QDPtr)myStdText;
thisport->gProcs.txMeasProc = (QDPtr)myStdTextMeas;
thisport->gPort.grafProcs = &thisport->gProcs;
/*
* Set up the port Rect in the proper coordinates, relative to the
page
* and to the paper.
*/
thisport->gPort.device = dsp->Print.prInfo.iDev;
thisport->gPort.portRect = dsp->Print.prInfo.rPage;
thisport->gPort.portBits.bounds.top =
thisport->gPort.portBits.bounds.left =
thisport->gPort.portBits.bounds.bottom =
thisport->gPort.portBits.bounds.right = 0;
SetPort(thisport);
/* Offset the page (portRect) relative to the paper. */
PortSize(dsp->Print.prInfo.rPage.right,dsp->Print.prInfo.rPage.
bottom);
MovePortTo(- dsp->Print.rPaper.left, - dsp->Print.rPaper.top);
GrafDevice(dsp->Print.prInfo.iDev);
myClearPage(thepage);
pb->csCode = iPrDevCtl; /* Init the printer. */
pb->lParam1 = lPrReset; /* Driver code does work. */
asm{
move.l pb,a0
}
dsp->pagenum = 1;
if(dsp->Print.prJob.pIdleProc == nil)
dsp->Print.prJob.pIdleProc = (ProcPtr)checkabort;
savePrint = (THPrint)GetResource(‘PREC’,1);
if(savePrint != nil){
LoadResource(savePrint);
**savePrint = dsp->Print;
ChangedResource(savePrint);
ReleaseResource(savePrint);
} /* Determine proper margins. */
dsp->nlines = (dsp->Print.rPaper.bottom -
dsp->Print.rPaper.top)/CHARHEIGHT;
return(thisport);
}
pascal void myPrCloseDoc(pPrPort)
TPPrPort pPrPort;
{
pline thepage;
Pfg settings;
free(pPrPort->lGParam4);
ClosePort(pPrPort);
if(pPrPort->fOurPtr) free(pPrPort);
}
/*
* This routine opens a new page. Actually, all it does is clear out
the array of lines in preparation for more fun with QuickDraw. I
suppose it could also send a reset command to the driver...
*/
pascal void myPrOpenPage(pPrPort,pPageFrame)
TPPrPort pPrPort;
TPRect pPageFrame;
{
register DPstorage dsp;
dsp = DrvrStorage();
if(pPageFrame != nil)
*pPageFrame = dsp->Print.prInfo.rPage;
SetPort(pPrPort);
myClearPage(pPrPort->lGParam4);
}
/*
* This is the routine which does the actual printing. QuickDraw
calls which have called our StdText substitute routine have filled up
a buffer with lines of text. Now, we just get the buffer and print
it.
*/
pascal void myPrClosePage(pPrPort)
TPPrPort pPrPort;
{
register DPstorage dsp;
register pline theline;
register int i,iocount;
PrParam *pb;
dsp = DrvrStorage();
pb = &dsp->prpb;
if(dsp->pagenum < dsp->Print.prJob.iFstPage){
dsp->pagenum++;
return;
}
if(dsp->pagenum++ > dsp->Print.prJob.iLstPage){
PrintErr = iPrAbort;
if (dsp->preofstr[0] != ‘\0’){
pb->csCode = iPrIOCtl;
pb->lParam1 = (long)(&dsp->preofstr[1]);
pb->lParam2 = (long)dsp->preofstr[0];
asm{
move.l pb,a0
}
}
return;
}
if(dsp->Print.prStl.feed != feedCut || waitnextpage()){
theline = (pline)pPrPort->lGParam4;
if (dsp->prtopstr[0] != ‘\0’){
pb->csCode = iPrIOCtl;
pb->lParam1 = (long)(&dsp->prtopstr[1]);
pb->lParam2 = (long)dsp->prtopstr[0];
asm{
move.l pb,a0
}
}
for(i=0;dsp->nlines - i;i++){
(* dsp->Print.prJob.pIdleProc)();
if(PrintErr == iPrAbort)return;
if((theline+i)->dirty == DIRTY){
iocount = WIDTH;
while( (theline+i)->text[--iocount] == ‘ ‘){}
++iocount;
pb->csCode = iPrIOCtl;
pb->lParam1 =
(long)(&(theline+i)->text[0]);
pb->lParam2 = (long)iocount;
asm{
move.l pb,a0
}
}
pb->csCode = iPrDevCtl;
if(i < dsp->nlines - 1)
pb->lParam1 = lPrLineFeed;
else pb->lParam1 = lPrPageEnd;
asm{
move.l pb,a0
}
}
}
else PrintErr = iPrAbort;
}
/*
* All text drawing calls in the TPrPort get sent here. Find the
current pen location and translate it to row and column of the page
buffer, squirt the text into the buffer.
*/
pascal void myStdText(byteCount,textBuf,numer,denom)
int byteCount;
QDPtr textBuf;
Point numer,denom;
{
Point thepoint;
TPPrPort tp;
pline thepage;
int width;
int x,y;
GetPort(&tp);
if(tp->gPort.device == IDEV10) width = 7;
else if(tp->gPort.device == IDEV15) width = 5;
else width = 6;
thepage = (pline)tp->lGParam4;
GetPen(&thepoint);
/* (Local is page, Global is paper.) */
LocalToGlobal(&thepoint);
x = thepoint.h/width;
y = thepoint.v/CHARHEIGHT;
bcopy(textBuf,&((thepage+y)->text[x]),byteCount);
(thepage+y)->dirty = DIRTY;
Move(width*byteCount,0);
}
pascal int myStdTextMeas(byteCount,textBuf,numer,denom,info)
int byteCount;
QDPtr textBuf;
Point *numer,*denom;
FontInfo *info;
{
TPPrPort tp;
GetPort(&tp);
if(tp->gPort.device == IDEV10)info->widMax = 7;
else if(tp->gPort.device == IDEV15)info->widMax = 5;
else info->widMax = 6;
info->ascent = 9;
info->descent = 2;
info->leading = 0;
numer->v = denom->v = 1;
denom->h = 6;
numer->h = info->widMax;
return (info->widMax * byteCount);
}
void myClearPage(theline)
pline theline;
{
int count;
unsigned char *ch;
count = NROWS;
clear:
theline->dirty = ~DIRTY;
ch = &theline->text[0];
asm{
move.l ch,a0
move.w #((WIDTH/4)-1),d0
move.l #0x20202020,d1
loop:
move.l d1,(a0)+
dbra d0,@loop
}
if(--count){
theline++;
goto clear;
}
}
Ptr allocate(size)
long size;
{
asm{
move.l size,d0
move.l a0,d0 ;; LightspeedC returns function
} /* value in d0. */
}
void free(ptr)
Ptr ptr;
{
asm{
move.l ptr,a0
}
}
/*
* A UNIXism. Want to make something of it?
*/
void bcopy(src,dst,count)
unsigned char *src,*dst;
int count;
{
asm{
move.l src,a0
move.l dst,a1
clr.l d0
move.w count,d0
}
}
#define UTableBase 284
/*
* This function returns a pointer to the printer driver’s private
* storage. We don’t need to lock the Handle, since it is always
locked
* when the driver is open.
*/
DPstorage DrvrStorage()
{
DCtlHandle ourDCtlEntry;
DHstorage ourdCtlStorage;
asm{
move #2,d0
asl.l #2,d0 ;; d0 = 8L
move.l UTableBase,a0 ;; a0 -> base of unit table
adda d0,a0 ;; a0 -> second entry
move.l (a0),ourDCtlEntry ;; handle to DCtlEntry[2]
}
ourdCtlStorage = (DHstorage)(*ourDCtlEntry)->dCtlStorage;
return(*ourdCtlStorage);
}
/*
* Abort printing if command ‘.’ pressed.
*/
void checkabort()
{
EventRecord my event;
int c;
if (GetNextEvent(keyDownMask, &my event)){
if(LoWord(my event.message & charCodeMask) == ‘.’ &&
(my event.modifiers & cmdKey) ){
PrintErr = iPrAbort;
}
}
}
/*
* Modal dialog box: “Insert next sheet.”
*/
waitnextpage()
{
DialogPtr sheet dialog;
WindowPtr tempport;
int itemhit,donetype;
Handle doneitem;
Rect donebox;
if((sheetdialog = GetNewDialog(SHEETDIALOG, 0L,(WindowPtr) -1)) ==
nil)
return FALSE;
InitCursor();
GetDItem(sheet dialog,DONEITEM,&donetype,&doneitem,&donebox);
GetPort(&tempport);
SetPort(sheet dialog);
PenSize(3,3);
InsetRect(&donebox,-4,-4);
FrameRoundRect(&donebox,16,16);
ModalDialog(0L,&itemhit);
DisposDialog(sheet dialog);
SetPort(tempport);
if(itemhit == STOPITEM) return FALSE;
return TRUE;
}
/*
* PDEF4.c.
*
* Generic daisy/dot matrix text printer driver
* Earle R. Horton August 31, 1987
* All rights reserved.
*
* This module contains the code for validating, creating,
* and modifying print records.
*/
/*
* This module is to be placed into a code resource project using
LightspeedC, version 2.01 or greater. The project is to be made into
PDEF resource number 4. All of the code up to and including the
first illegal instruction is to be stripped off, so that the PDEF
will have the standard format for resources of this type. Switch
statements cannot be used, since LightspeedC compiles them into
separate code which is added to the beginning of the code resource,
before our standard header. I do not know at this point which types
of flow control constructs are safe, but I have determined by
disassembly that the following will produce useable code:
* if, if/else blocks
* gotos
*
* Instructions, or “How I did it.” Create from this module a code
resource of type ‘pdef’ ID 4. Run the program utils.c to make it into
PDEF ID 4 and to strip off the standard header.
*/
#include
#include
#include “prglobals.h”
#define STYLEDIALOG (0xE000)
#define JOBDIALOG (0xE001)
#define XTRA 24 /* Six extra longs for our use. */
#define DLGSIZE ((long)(sizeof(TPrDlg) + XTRA))
#define TPSIZE ((long)(sizeof(TPrint)))
/* Items we handle in the job and style dialogs. */
#define CANCELITEM 2
#define CPI10BUTTON 4
#define CPI12BUTTON 5
#define CPI15BUTTON 6
#define STRAIGHTUPITEM 7
#define SIDEWAYSITEM 8
#define ALLBUTTON 5
#define RANGEBUTTON 6
#define FROMNUM 7
#define TONUM 9
#define COPIES 11
#define FANBUTTON 13
#define SHEETBUTTON 14
pascal void MyPrintDefault(); /* Fill default print record*/
pascal Boolean MyPrStlDialog(); /* printer style dialog. */
pascal Boolean MyPrJobDialog(); /* printer job dialog. */
pascal TPPrDlg MyPrStlInit(); /* Set up style dialog. */
pascal TPPrDlg MyPrJobInit(); /* Set up job dialog. */
pascal Boolean MyPrDlgMain(); /* Print dialog supervisor*/
pascal Boolean MyPrValidate(); /* Validate print record. */
pascal void MyPrJobMerge(); /* Copy a job sub record. */
pascal Boolean MyFilter(); /* Filter dialog events. */
pascal void HandleStyleItems(); /* Handle Style Items. */
pascal void HandleJobItems(); /* Handle Job Items. */
TPPrDlg TPPrDlgallocate();
void pushradio button();
int NumToString(),StringToNum();
Boolean Valid();
void mkDefault();
void free();
main()
{
asm{
dc.w ILLEGAL ;; So I can find it...
jmp MyPrintDefault
jmp MyPrStlDialog
jmp MyPrJobDialog
jmp MyPrStlInit
jmp MyPrJobInit
jmp MyPrDlgMain
jmp MyPrValidate
jmp MyPrJobMerge
}
}
/*
* This function fills a print record with defaults. The default
values are stored in the Printer resource file, in PREC 0. This is
easy. Then we check the fields of the print record for anything
obviously illegal. If the default print record contains stuff that is
bad, then we correct it and update the copy in the printer resource
file. This should never happen, but some wise guy with a copy of
ResEdit and more brains than sense may think he knows more than we
do.
*/
pascal void MyPrintDefault(hPrint)
THPrint hPrint;
{
THPrint thedefault;
thedefault = (THPrint)(GetResource(‘PREC’,0));
if(thedefault == nil){
mkDefault(hPrint);
}
else{
LoadResource(thedefault);
if(MyPrValidate(thedefault)) {
ChangedResource(thedefault);
WriteResource(thedefault);
}
**hPrint = **thedefault; /* What the hell. */
}
}
pascal Boolean MyPrStlDialog(hPrint) /*print style dialog.*/
THPrint hPrint;
{
return(MyPrDlgMain(hPrint,MyPrStlInit));
}
pascal Boolean MyPrJobDialog(hPrint) /* Conduct printer job dialog.
*/
THPrint hPrint;
{
return(MyPrDlgMain(hPrint,MyPrJobInit));
}
/*
* The style dialog initializer.
*/
pascal TPPrDlg MyPrStlInit(hPrint)
THPrint hPrint;
{
TPPrDlg tp;
tp = TPPrDlgallocate();
(GetNewDialog(STYLEDIALOG,tp,-1));
if((*hPrint)->prInfo.iDev == IDEV10)
pushradio button(tp,CPI10BUTTON,CPI10BUTTON,CPI15BUTTON);
else if((*hPrint)->prInfo.iDev == IDEV12)
pushradio button(tp,CPI12BUTTON,CPI10BUTTON,CPI15BUTTON);
else if((*hPrint)->prInfo.iDev == IDEV15)
pushradio button(tp,CPI15BUTTON,CPI10BUTTON,CPI15BUTTON);
pushradio button(tp,STRAIGHTUPITEM,STRAIGHTUPITEM,SIDEWAYSITEM);
tp->pFltrProc = (ProcPtr)MyFilter;
tp->pItemProc = (ProcPtr)HandleStyleItems;
tp->hPrintUsr = hPrint;
return(tp);
}
/*
* The job dialog initializer.
*/
pascal TPPrDlg MyPrJobInit(hPrint)
THPrint hPrint;
{
TPPrDlg tp;
int thenum;
Handle theitem;
Rect thebox;
Str36 title;
tp = TPPrDlgallocate();
(GetNewDialog(JOBDIALOG,tp,-1));
pushradio button(tp,ALLBUTTON,ALLBUTTON,RANGEBUTTON);
if( (*hPrint)->prStl.feed == feedCut)
pushradio button(tp,SHEETBUTTON,FANBUTTON,SHEETBUTTON);
else
pushradio button(tp,FANBUTTON,FANBUTTON,SHEETBUTTON);
GetDItem(tp,FROMNUM,&thenum,&theitem,&thebox);
thenum = (*hPrint)->prJob.iFstPage;
NumToString((long)thenum,title);
SetIText(theitem,title);
GetDItem(tp,TONUM,&thenum,&theitem,&thebox);
thenum = (*hPrint)->prJob.iLstPage;
NumToString((long)thenum,title);
SetIText(theitem,title);
GetDItem(tp,COPIES,&thenum,&theitem,&thebox);
thenum = (*hPrint)->prJob.iCopies;
NumToString((long)thenum,title);
SetIText(theitem,title);
tp->pFltrProc = (ProcPtr)MyFilter;
tp->pItemProc = (ProcPtr)HandleJobItems;
tp->hPrintUsr = hPrint;
return(tp);
}
pascal Boolean MyPrDlgMain(hPrint,pDlgInit)
/* Printing dialog supervisor function. */
/* Reference: Macintosh Technical Note */
/* 95. Good luck! */
THPrint hPrint;
ProcPtr pDlgInit;
{
TPPrDlg tp;
WindowPtr tempport;
int donetype,itemhit;
Handle doneitem;
Rect donebox;
ProcPtr itemproc;
asm{
subq.l #4,a7 ;; Room for function return.
move.l hPrint,-(a7);; Pass handle to print record.
move.l pDlgInit,a0 ;; Get addr. of dialog init routine.
jsr (a0) ;; It’s a “Pascal” routine.
move.l (a7)+,tp ;; Pop return value.
}
itemproc = tp->pItemProc;
tp->fDone = FALSE;
tp->fDoIt = FALSE;
GetPort(&tempport);
SetPort(tp);
ShowWindow(tp);
GetDItem(tp,DONEITEM,&donetype,&doneitem,&donebox);
PenSize(3,3);
InsetRect(&donebox,-4,-4);
FrameRoundRect(&donebox,16,16);
while(!(tp->fDone)){
ModalDialog(tp->pFltrProc,&itemhit);
/*
* Reverse parameters on the call to pItemProc. The application is
allowed to trap our pItemProc and insert its own, so we must use the
Pascal calling conventions here. Programming novices can use the
LightspeedC™ CallPascal library if they want. You will have to be
careful if you do this to make sure the library is linked AFTER the
module containing these functions, or you won’t get the PDEF resource
header right.
*/
asm{
move.l tp,-(a7)
move.w itemhit,-(a7)
move.l itemproc,a0
jsr (a0)
}
}
SetPort(tempport);
CloseDialog(tp);
free(tp);
if(tp->fDoIt) (void)MyPrValidate(hPrint);
return(tp->fDoIt);
}
/*
* Validate/update a print record. Check all the fields for
compatibility with our driver. This is a three stage process.
First, we check all fields to see whether they are within the bounds
which our driver can handle. If they are, we return FALSE (no
change). If not, we obtain a copy of the current default values from
the printer resource file. Then we inspect these to see if they are
valid. If the default values in the printer resource file are valid,
we use them, update the user’s print record, and return TRUE
(changed). Otherwise, we fall back on default values which we store
in the code here. This three stage process provides some protection
against the user who attempts to adjust the print record using a
resource editor, and screws up.
*/
pascal Boolean MyPrValidate(hPrint) /* Validate/update a print
record. */
THPrint hPrint;
{
THPrint thedefault;
if(Valid(hPrint)) return FALSE;
else{
thedefault = (THPrint)(GetResource(‘PREC’,0));
LoadResource(thedefault);
if(thedefault == nil || !Valid(thedefault)) mkDefault(hPrint);
else{
**hPrint = **thedefault;
}
return TRUE;
}
}
pascal void MyPrJobMerge(hPrintSrc,hPrintDst)
/*
* Copy a job sub record. Update the destination record’s printer
information, band information, and paper rectangle, based on
information in the job sub record.
*/
THPrint hPrintSrc,hPrintDst;
{
(*hPrintDst)->prInfo.iDev = (*hPrintSrc)->prInfo.iDev;
(*hPrintDst)->prJob = (*hPrintSrc)->prJob;
(*hPrintDst)->prXInfo = (*hPrintSrc)->prXInfo;
(*hPrintDst)->rPaper = (*hPrintSrc)->rPaper;
(*hPrintDst)->prInfo = (*hPrintSrc)->prInfo;
(*hPrintDst)->prInfoPT = (*hPrintSrc)->prInfoPT;
}
pascal Boolean MyFilter(the dialog,theEvent,itemhit)
DialogPtr the dialog;
EventRecord *theEvent;
int *itemhit;
{
if(theEvent->what == keyDown &&
(theEvent->message & charCodeMask) == 13){
*itemhit = 1;
return TRUE;
}
return FALSE;
}
/*
* The next routine handles the style dialog. Two possibilities
exist. If the cancel button is hit, then we signal quit. The print
record is not changed. If the done button is hit, we validate the
user’s print record.
* (MyPrDlgMain() calls MyPrValidate() to do this.)
* We use three radio buttons to determine the number of characters
per inch (resolution), and two to determine paper orientation.
*/
pascal void HandleStyleItems(tp,itemhit)
TPPrDlg tp;
int itemhit;
{
int thenum;
Handle theitem;
Rect thebox;
if(itemhit >= CPI10BUTTON && itemhit <= CPI15BUTTON)
pushradio button(tp,itemhit,CPI10BUTTON,CPI15BUTTON);
else if(itemhit >= STRAIGHTUPITEM && itemhit <= SIDEWAYSITEM)
pushradio button(tp,itemhit,STRAIGHTUPITEM,SIDEWAYSITEM);
else if(itemhit == DONEITEM){
TPrint Print;
TPPrint pPrint;
pPrint = &Print;
mkDefault(&pPrint);
(*tp->hPrintUsr)->iPrVersion = Print.iPrVersion;
(*tp->hPrintUsr)->prInfo = Print.prInfo;
(*tp->hPrintUsr)->prXInfo = Print.prXInfo;
(*tp->hPrintUsr)->rPaper = Print.rPaper;
(*tp->hPrintUsr)->prStl = Print.prStl;
(*tp->hPrintUsr)->prInfoPT = Print.prInfoPT;
GetDItem(tp,CPI10BUTTON,&thenum,&theitem,&thebox);
if(GetCtlValue(theitem) == 1){
(*tp->hPrintUsr)->prInfo.iDev = IDEV10;
}
GetDItem(tp,CPI12BUTTON,&thenum,&theitem,&thebox);
if(GetCtlValue(theitem) == 1){
(*tp->hPrintUsr)->prInfo.iDev = IDEV12;
}
GetDItem(tp,CPI15BUTTON,&thenum,&theitem,&thebox);
if(GetCtlValue(theitem) == 1){
(*tp->hPrintUsr)->prInfo.iDev = IDEV15;
}
GetDItem(tp,SIDEWAYSITEM,&thenum,&theitem,&thebox);
if(GetCtlValue(theitem) == 1){
int temp;
flipRect(&(*tp->hPrintUsr)->prInfo.rPage);
flipRect(&(*tp->hPrintUsr)->rPaper);
flipRect(&(*tp->hPrintUsr)->prInfoPT.rPage);
temp = (*tp->hPrintUsr)->prStl.iPageV;
(*tp->hPrintUsr)->prStl.iPageV =
(*tp->hPrintUsr)->prStl.iPageH;
(*tp->hPrintUsr)->prStl.iPageH = temp;
}
tp->fDone = TRUE;
tp->fDoIt = TRUE;
}
else if (itemhit == CANCELITEM){
tp->fDone = TRUE;
tp->fDoIt = FALSE;
}
}
pascal void HandleJobItems(tp,itemhit)
TPPrDlg tp;
int itemhit;
{
int thenum;
long thelong;
Handle numitem;
Rect numbox;
Str36 title;
if(itemhit == DONEITEM){ /* NO SWITCHES! */
tp->fDone = TRUE; /* At least until I get a */
tp->fDoIt = TRUE; /* better compiler. */
GetDItem(tp,ALLBUTTON,&thenum,&numitem,&numbox);
if(GetCtlValue(numitem) == 1){
(*(tp->hPrintUsr))->prJob.iFstPage = iPrPgFst;
(*(tp->hPrintUsr))->prJob.iLstPage = iPrPgMax;
}
else{
GetDItem(tp,FROMNUM,&thenum,&numitem,&numbox);
GetIText(numitem,&title[0]);
StringToNum(&title[0],&thelong);
(*(tp->hPrintUsr))->prJob.iFstPage = thelong;
GetDItem(tp,TONUM,&thenum,&numitem,&numbox);
GetIText(numitem,&title[0]);
StringToNum(&title[0],&thelong);
(*(tp->hPrintUsr))->prJob.iLstPage = thelong;
}
GetDItem(tp,COPIES,&thenum,&numitem,&numbox);
GetIText(numitem,&title[0]);
StringToNum(&title[0],&thelong);
(*(tp->hPrintUsr))->prJob.iCopies = thelong;
GetDItem(tp,SHEETBUTTON,&thenum,&numitem,&numbox);
if(GetCtlValue(numitem) == 1){
(*(tp->hPrintUsr))->prStl.feed = feedCut;
}
else (*(tp->hPrintUsr))->prStl.feed = feedFanfold;
}
else if (itemhit == CANCELITEM){
tp->fDone = TRUE;
}
else if (itemhit == SHEETBUTTON){
pushradio button(tp,SHEETBUTTON,FANBUTTON,SHEETBUTTON);
(*(tp->hPrintUsr))->prStl.feed = feedCut;
}
else if (itemhit == FANBUTTON){
pushradio button(tp,FANBUTTON,FANBUTTON,SHEETBUTTON);
(*(tp->hPrintUsr))->prStl.feed = feedFanfold;
}
else if (itemhit == ALLBUTTON){
pushradio button(tp,ALLBUTTON,ALLBUTTON,RANGEBUTTON);
}
else if (itemhit == RANGEBUTTON){
pushradio button(tp,RANGEBUTTON,ALLBUTTON,RANGEBUTTON);
}
}
TPPrDlg TPPrDlgallocate()
{
TPPrDlg thepointer;
asm{
move.l #DLGSIZE,d0
move.l a0,thepointer
}
if(thepointer == nil){ /* We’re in deep six now! */
asm{
move.w #25,d0
SysError ;; SysError
}
}
return(thepointer);
}
void pushradio button(the dialog,itemhit,first,last)
/* push a radio Button */
DialogPtr the dialog; /* set itemhit, unset */
int itemhit,first,last; /* all others in range */
{
int itemtype,i;
Handle item handle; /* Does check boxes, too. */
Rect itemrect;
if(first ==0) return;
for(i=first-1;last-i++;){
GetDItem(the dialog,i,&itemtype,&item handle,&itemrect);
if(i == itemhit) SetCtlValue(item handle,1);
else SetCtlValue(item handle,0);
}
}
NumToString(thenum,thestring)
long thenum;
char *thestring;
{
asm{
move.l thestring,a0
move.l thenum,d0
move.w #0,-(a7)
Pack7
}
}
StringToNum(thestring,thenum)
char *thestring;
long *thenum;
{
asm{
move.l thestring,a0
move.w #1,-(a7)
Pack7
move.l thenum,a0
move.l d0,(a0)
}
}
/*
* This function answers the question: Can we possibly use this
print record?
*/
Boolean Valid(hPrint)
THPrint hPrint;
{
if (((*hPrint)->iPrVersion != VERSION)
|| ((*hPrint)->prInfo.iDev != IDEV12 &&
(*hPrint)->prInfo.iDev != IDEV10 &&
(*hPrint)->prInfo.iDev != IDEV15)
|| ((*hPrint)->prInfo.iVRes != VREZZ)
|| ((*hPrint)->prInfo.iHRes != HREZZ12)
|| ((*hPrint)->prInfo.rPage.top !=0 ||
(*hPrint)->prInfo.rPage.left !=0)
|| ((*hPrint)->rPaper.right -
(*hPrint)->rPaper.left > 11 * HREZZ12)
|| ((*hPrint)->rPaper.bottom -
(*hPrint)->rPaper.top > 11 * VREZZ)
|| ((*hPrint)->prStl.feed != feedCut &&
(*hPrint)->prStl.feed != feedFanfold)
|| ((*hPrint)->prJob.bJDocLoop != bDraftLoop))
return FALSE;
else return TRUE;
}
void free(ptr)
char *ptr;
{
asm{
move.l ptr,a0
}
}
flipRect(rect)
Rect *rect;
{
asm{
move.l rect,a0
move.l (a0),d0
swap d0 ;; swap top, left
move.l d0,(a0)+
move.l (a0),d0
swap d0 ;; swap bottom, right
move.l d0,(a0)
}
}
#include “mkDefault.c”
/*
* PDEF5.c
*
* Although this printer driver does not do spool printing, I provide
a duplicate PDEF to pretend to do so. That is just a copy of my PDEF
0, and it really does draft printing. This module is so the
application can call PrPicFile() when it is done. Some applications
just don’t get the hint, and attempt to spool print even when the
print record says otherwise.
*/
#include “prglobals.h”
pascal void myPrPicFile();
main()
{
asm{
dc.w ILLEGAL
jmp myPrPicFile
}
}
pascal void myPrPicFile(hPrint,pPrPort,pIOBuf,pDevBuf,prStatus)
THPrint hPrint;
TPPrPort pPrPort;
Ptr pIOBuf,pDevBuf;
TPrStatus *prStatus;
{
}
/*
* PACK.c
*
* Code for PACK ID -4096 to be used with the Chooser to set the
printing port options. Set the Flags longword in the PACK resource
to 0400E000 after building the PACK resource to make sure we get the
right-hand button message.
*/
#include /* includes QuickDraw.h, MacTypes.h */
#include
#include “prglobals.h”
/*
* Resources which are standard type and accessed by the PACK -4096
resource have ID # RES1ID. Resources which are non-standard type
and/or belong without a doubt to our driver code are accessed with
RES2ID. These include the Stng resource and our PREC #-8192. (IM 4
says standard resource types to be used by this PACK resource should
have IDs in the range -4080 to -4065.)
*/
/* Printer Setup Dbox item numbers */
#define SAVEITEM 1
#define MODEM 5
#define PRINTER 6
#define BAUDBUTTON 8
#define EOLITEM 11
#define INITITEM 12
#define TOPITEM 13
#define EOPITEM 14
#define EOFITEM 15
#define CTSITEM 19
#define XONXOFFITEM 20
#define CANCELITEM 21
#define NUMSTRINGS 5
#define RESPAD 24
pascal OSErr main(message,caller,objname,zonename,p1,p2)
int message,caller;
StringPtr objname,zonename;
long p1,p2;
{
if (message == buttonMsg){
prsetup();
}
return (noErr);
}
pushradio button(the dialog,itemhit,first,last) /* push a radio
Button */
DialogPtr the dialog; /* set itemhit, unset */
int itemhit,first,last; /* all others in range */
{
int itemtype,i;
Handle item handle; /* Does check boxes, too. */
Rect itemrect; /* (when range is 1 in size.) */
if(first ==0) return;
for(i=first-1;last-i++;){
GetDItem(the dialog,i,&itemtype,&item handle,&itemrect);
if(i == itemhit) SetCtlValue(item handle,1);
else SetCtlValue(item handle,0);
}
}
prsetup()
{
DialogPtr print dialog;
WindowPtr tempport;
int itemhit,i,baudtype,edittype,donetype;
Handle bauditem,doneitem,edititem;
Rect baudbox,donebox,editbox;
Str255 thestring;
unsigned char *strptr;
long length,result;
BAUDS **mybauds;
Pfg settings;
StrList mystrings;
mybauds = (BAUDS **)GetResource(‘PREC’,RES2ID);
if(mybauds == nil) return;
if ((settings = (Pfg)(GetResource(‘Stng’,RES2ID))) == nil ||
(mystrings = (StrList) (GetResource(‘STR#’,RES1ID))) == nil)
return;
if (pbaud<0 || pbaud>9) pbaud = 0;
if((printdialog = GetNewDialog(RES1ID, 0L,(WindowPtr) -1)) == nil)
return;
GetDItem(print dialog,BAUDBUTTON,&baudtype,&bauditem,&baudbox);
GetDItem(print dialog,SAVEITEM,&donetype,&doneitem,&donebox);
SetCTitle(bauditem,((*mybauds)+pbaud)->label);
/* This gets the printer control strings from a string list, then
sets the editText items in the dialog box to contain the strings.
*/
strptr = &((*mystrings)->thestrings[0]);
for(i = EOLITEM-1;EOFITEM - i++;){
GetDItem(print dialog,i,&edittype,&edititem,&editbox);
SetIText(edititem,strptr);
strptr += (*strptr) + 1;
}
GetPort(&tempport);
SetPort(print dialog);
ShowWindow(print dialog);
PenSize(4,4); /* Time to frame some buttons. */
InsetRect(&donebox,-5,-5);
FrameRoundRect(&donebox,16,16);
PenSize(2,2);
InsetRect(&baudbox,-3,-3);
FrameRoundRect(&baudbox,12,12);
pushradio button(print dialog, pport + MODEM,MODEM,PRINTER);
pushradio button(print dialog,CTSITEM + XonXoff,CTSITEM,
XONXOFFITEM);
itemhit = 0;
while(itemhit !=1){
ModalDialog(0L,&itemhit);
switch(itemhit){
/* Port change. It might be nice to check and see whether AppleTalk
is active if the user selects the Printer Port */
case MODEM:
case PRINTER:
pport = itemhit - MODEM;
pushradio button(print dialog,itemhit,
MODEM,PRINTER);
break;
case BAUDBUTTON: /* next baud rate change */
/* Ten radio buttons would be just too much. */
if(++pbaud == 10) pbaud = 0;
SetCTitle(bauditem,((*mybauds)+pbaud)->label);
break;
case CTSITEM:
case XONXOFFITEM:
XonXoff = itemhit - CTSITEM;
pushradio button(print dialog,
itemhit,CTSITEM,XONXOFFITEM);
break;
case CANCELITEM:
DisposDialog(print dialog);
SetPort(tempport);
return;
break;
}
}
/* The user has set the baud rate and the port, and also possibly
edited the printer control strings. Since we used ModalDialog() with
no filterproc we don’t know whether any of the strings have been
changed. Therefore we just rebuild the whole string list. First,
determine the length. */
length = (long) (sizeof(int)+RESPAD);
for(i = EOLITEM-1;EOFITEM - i++;){
GetDItem(print dialog,i,&edittype,&edititem,&editbox);
GetIText(edititem,thestring);
length += (long) thestring[0];
}
/* Size might have changed, so we unlock the handle and attempt to
resize it. */
asm{
move.l mystrings,a0 ;; save loading MacTraps
_HUnlock
move.l mystrings,a0
move.l length,d0
_SetHandleSize
move.l mystrings,a0
_GetHandleSize
move.l d0,result
}
if ( result != length ){ /* Abort on error. */
DisposDialog(print dialog);
SetPort(tempport);
return(FALSE);
}
asm{
move.l mystrings,a0
_HNoPurge
move.l mystrings,a0
_HLock
}
/* Rebuild the STR# from the item list. */
strptr = &((*mystrings)->thestrings[0]);
for(i = EOLITEM-1;EOFITEM - i++;){
GetDItem(print dialog,i,&edittype,&edititem,&editbox);
GetIText(edititem,strptr);
strptr += (*strptr) + 1;
}
DisposDialog(print dialog);
SetPort(tempport);
ChangedResource(settings);
ChangedResource(mystrings);
WriteResource(settings);
WriteResource(mystrings);
return;
}
/*
* mkDefault.c
*
* This function fills a print record with defaults, using coded
values. It is used only when the default print record stored in the
printer resource file is found to be invalid. It’s also for the code
to make the first copy. According to a compile-time switch, margins
are either zero or one inch all around, zero on the right. To support
paper which has more than 66 lines per page or 164 columns, you have
to make changes here and in PDEF0.c.
*/
#define ONE_INCH_MARGIN 1
void mkDefault(hPrint)
THPrint hPrint;
{
(*hPrint)->iPrVersion = VERSION;
(*hPrint)->prInfo.iDev = IDEV12;
(*hPrint)->prInfo.iVRes = VREZZ;
(*hPrint)->prInfo.iHRes = HREZZ12;
(*hPrint)->prInfo.rPage.top = 0;
(*hPrint)->prInfo.rPage.left = 0;
#if ONE_INCH_MARGIN
(*hPrint)->prInfo.rPage.right = HREZZ12 * 7;
(*hPrint)->prInfo.rPage.bottom = VREZZ * 9;
(*hPrint)->rPaper.top = -VREZZ;
(*hPrint)->rPaper.left = -HREZZ12;
(*hPrint)->rPaper.bottom = VREZZ * 10;
(*hPrint)->rPaper.right = HREZZ12 * 7 + HREZZ12/2;
#else
(*hPrint)->prInfo.rPage.right = HREZZ12 * 8 + HREZZ12/2;
(*hPrint)->prInfo.rPage.bottom = VREZZ * 11;
(*hPrint)->rPaper = (*hPrint)->prInfo.rPage;
#endif
(*hPrint)->prStl.wDev = iDevDaisy;
(*hPrint)->prStl.iPageV = 11 * iPrPgFract;
(*hPrint)->prStl.iPageH = (int)((float)iPrPgFract * 8.5);
(*hPrint)->prStl.bPort = 0;
(*hPrint)->prStl.feed = feedFanfold;
(*hPrint)->prInfoPT = (*hPrint)->prInfo;
(*hPrint)->prXInfo.iRowBytes = 0;
(*hPrint)->prXInfo.iBandV = 0;
(*hPrint)->prXInfo.iBandH = 0;
(*hPrint)->prXInfo.iDevBytes = 0;
(*hPrint)->prXInfo.iBands = 0;
(*hPrint)->prXInfo.bPatScale = 0;
(*hPrint)->prXInfo.bULThick = 0;
(*hPrint)->prXInfo.bULOffset = 0;
(*hPrint)->prXInfo.bULShadow = 0;
(*hPrint)->prXInfo.scan = scanLR;
(*hPrint)->prXInfo.bXInfoX = 0;
(*hPrint)->prJob.iFstPage = 1;
(*hPrint)->prJob.iLstPage = iPrPgMax;
(*hPrint)->prJob.iCopies = 1;
(*hPrint)->prJob.bJDocLoop = bDraftLoop;
(*hPrint)->prJob.fFromUsr = TRUE;
(*hPrint)->prJob.pIdleProc = nil;
(*hPrint)->prJob.pFileName = nil;
(*hPrint)->prJob.iFileVol = 0;
(*hPrint)->prJob.bFileVers = 0;
(*hPrint)->prJob.bJobX = 0;
(*hPrint)->printX[0] =
(*hPrint)->printX[1] =
(*hPrint)->printX[2] =
(*hPrint)->printX[3] =
(*hPrint)->printX[4] =
(*hPrint)->printX[5] =
(*hPrint)->printX[6] =
(*hPrint)->printX[7] =
(*hPrint)->printX[8] =
(*hPrint)->printX[9] =
(*hPrint)->printX[10] =
(*hPrint)->printX[11] =
(*hPrint)->printX[12] =
(*hPrint)->printX[13] =
(*hPrint)->printX[14] =
(*hPrint)->printX[15] =
(*hPrint)->printX[16] =
(*hPrint)->printX[17] =
(*hPrint)->printX[18] = 0;
}
/*
* Utils.c
*
* Utility program to format the code resources used with the Daisy
printing manager. If you run it twice, it does nothing the second
time.
*/
#include “prglobals.h”
#define PAD 24L
typedef struct{
unsigned int flags;
unsigned int delay;
unsigned int emask;
unsigned int menu;
}driver,*Pdriver,**Hdriver;
void mkDefault();
main()
{
setpackflags();
setdriverflags();
createPDEF(“\psrc:Printer:DaisyPDEF0”,0);
createPDEF(“\psrc:Printer:DaisyPDEF4”,4);
createPDEF(“\psrc:Printer:DaisyPDEF5”,5);
createPREC();
}
/*
* This is a utility function to strip off the header bytes
LightSpeedC puts on Code Resources it creates. I put an illegal
instruction right before the code I want.
*/
createPDEF(filename,idno)
char *filename;
int idno;
{
long thesize;
unsigned int **the handle,**new handle;
unsigned int *theword,*newone;
int thefile;
int result;
thefile = OpenResFile(filename);
thehandle = (unsigned int **) GetResource(‘pdef’,idno);
if (thehandle == 0L) return;
else{
thesize = SizeResource(the handle) + PAD;
asm {
move.l the handle,a0
_HLock
}
theword = *the handle;
while(*theword++ != ILLEGAL){
thesize -=2;
}
asm{
move.l thesize,d0
_NewHandle
move.l a0,new handle
}
if (newhandle == 0L) return;
else{
asm{
move.l new handle,a0
_HLock
move.l (a0),newone
move.l theword,a0
move.l newone,a1
move.l thesize,d0
_BlockMove
move.w d0,result
}
if(result)return;
AddResource(new handle,’PDEF’,idno,”\pStripped PDEF”);
WriteResource(new handle);
RmveResource(the handle);
UpdateResFile(thefile);
CloseResFile(thefile);
}
}
}
setdriverflags()
{
Hdriver Xprint;
OpenResFile(“\psrc:Printer:DaisyDRVR”);
Xprint = (Hdriver)(GetResource(‘DRVR’,2));
(*Xprint)->flags = dCtlEnable | dStatEnable;
(*Xprint)->delay = 0x0000;
(*Xprint)->emask = 0x0000;
(*Xprint)->menu = 0x0000;
ChangedResource(Xprint);
WriteResource(Xprint);
}
/* This function sets the version number and flags for the PACK
resource used to interface with the Chooser. */
setpackflags()
{
long **pack;
OpenResFile(“\psrc:Printer:DaisyPACK”);
pack = (long **)(GetResource(‘PACK’,-4096));
if (pack != 0L){
*((*pack)+2) = 0xF0000001;
*((*pack)+3) = 0x0400E000;
ChangedResource(pack);
WriteResource(pack);
}
}
/* Create a default printer settings resource based on the same code
used in our printer resource file. */
createPREC()
{
THPrint the handle;
CreateResFile(“\psrc:Printer:DaisyPREC0”);
OpenResFile(“\psrc:Printer:DaisyPREC0”);
SetResLoad(TRUE);
RmveResource(GetResource(‘PREC’,0));
asm{
move.l #((long)sizeof(TPrint)),d0
move.l a0,the handle
}
mkDefault(the handle);
AddResource(the handle,’PREC’,0,”\pPrint defaults”);
WriteResource(the handle);
}
#include “mkDefault.c”
* Daisy.r file puts it all together
*
bin:sys:Daisy
PRERDasY
*Include dialogs
INCLUDE src:Printer: dialogs.rsrc
Type PDEF = GNRL
Draft Printing Code,0 (48) ;; locked, Purgeable
.R
src:Printer:DaisyPDEF0 PDEF 0
*Type PDEF = GNRL
*Spool Printing Code,1 (48) ;; locked, Purgeable
*.R
*src:Printer:DaisyPDEF1 PDEF 1
*
Type PDEF = GNRL
Dialog Code,4 (48) ;; locked, Purgeable
.R
src:Printer:DaisyPDEF4 PDEF 4
Type PDEF = GNRL
PrPicFile stub,5
.R
src:Printer:DaisyPDEF5 PDEF 5
Type DRVR = GNRL
.XPrint,-8192 (32) ;; attributes -> Purgeable
.R
src:Printer:DaisyDRVR DRVR 2
Type PACK = GNRL
Daisy Config,-4096 (32) ;; attributes -> Purgeable
.R
src:Printer:DaisyPACK PACK -4096
Type PREC = GNRL
,0
.R
src:Printer:DaisyPREC0 PREC 0
Type PREC = GNRL
,1
.R
src:Printer:DaisyPREC0 PREC 0
Type PREC = GNRL
,-8192
.R
src:Printer:DaisyDRVR DATA -16320
Type STR
,-8191
Print File