XCMD Printer
Volume Number: 4
Issue Number: 10
Column Tag: HyperChat®
XCMD Cookbook 
By Donald Koscheka, Apple Computer
Print Manager Access
Printing may not come to mind as an area where writing an XCMD might be useful
since HyperCard prints card reports. Perhaps you don’t like the reports that
HyperCard prints out, or you want to set your own margins or draw borders around
each field that’s printed out. XCMDs provide access to the ToolBox Print Manager,
empowering you with the capability of performing custom printing in HyperCard. The
Print Manager, then, becomes a good candidate for XCMD exploration.
Reporter.P
This month’s XCMD, Reporter.P ( see Listing 1) is a simple text printer that you
can adapt to your own needs or use as a template for further exploring Macintosh
Printing. As always, we start with the XCMDBlockPtr as the interface to Hypercard.
We pass a handle to the text in the first parameter. We could use the other parameters
to pass formatting information. Parameter 2, for example, might be set to tell us to
draw a border around the page. Parameter 3 might contain the margins in page
coordinates.
The algorithm we use goes like this: Move to the top of the page and initialize a
rectangle, lineRect, whose height is the height of the text to be printed and whose width
is zero. Step through the text to be printed accumulating words (text separated by
“white space” or punctuation). If the width of this word plus the width of linerect is
less than the page width, add this line to the current line; otherwise, move down to the
next line and add the current word to the start of the next line. If any part of the next
line is below the bottom margin of the page, eject the page and reset lineRect’s top to
the top of the page.
Scoping
One of the nice features of writing XCMDs in Pascal is the scoping feature of
procedures and functions. Pascal allows us to define procedures and functions within
the main procedure, in this case, the procedure “Reporter”. This is useful because it
allows the callbacks and the nested procedures to share reporter’s local variables.
When we invoke a callback, we don’t need to pass the parameterBlocPointer. The
callbacks can “see” this pointer as if it was a global data declaration. Another nice
feature of writing XCMDs in Pascal is that we don’t have to be fussy about the entry
point. The variable scoping simplifies the compiler’s job of resolving the entry point.
In “C”, subroutines follow the main body of the XCMD so that the compiler can use the
start of the main body as the entry point.
Step Through
Reporter first checks the parameter block pointer to determine whether enough
parameters were passed. Params[1] should be a handle to some text to print. If the
handle is NIL, we don’t have anything to print; otherwise, we open the printer driver,
allocate a printer record handle (prRecHandle) and fill the record with the print
defaults. If you wanted to allow the user to change the page style (e.g. page
orientation), this is where you would invoke the printer’s style dialog. We’ll skip
this step for now because we want the XCMD to print the document with a minimum
amount of intervention from the user.
Next, we put up the job dialog which is the standard printing dialog that comes up
when you select print from the menu. If PRJobDialog returns true, the user clicked
the “OK” button and wants us to print the document.
PROpenDoc associates our printing with a grafport. This technique effectively
hides the gritty details of printing from the application. As long as we draw to the
grafPort returned by PROpenDoc, the underlying code will convert our QuickDraw
commands to Postscript for the LaserWriter and to a ribbon-eating bitmap for the
Imagewriter.
Once the grafport is opened, we open the first page of the document with the
PROpenPage and call PrintHandle to print the text. PrintHandle manages the opening,
closing and ejecting of successive pages so when it returns all we need to do is close
down the printing port with the PrCloseDoc call. Understanding this routine will take
you a long way to understanding the how this printing business works.
Once we’re done printing, we check to see if the document needs to be spooled out.
Spooled files are saved to disk and printed by the call to PrPicFile. This statement
insures compatibility with print spoolers that will be looking for picture files to
print.
The actual printing is performed by the procedure PrintHandle. For the sake of
argument, we set the font to nine point Geneva. A better approach would be to pass font
information as a parameter to the XCMD. GetFontInfo tells us the height of the text
since that will also be the height of a line on the page. We then calculate some
rectangles and set the coordinates of our lineRect variable. Note that lineRect starts
off at the top left of the page and has an initial width of 0. LineRect will expand to
include the width of each word that we add to the line.
Locking down the text handle allows us to maintain pointers to the text. The first
pointer, cpos, points to the current position in the text while wpos is used to point to
the first character in the current word. A third pointer, tpos, is used as a “test
pointer” and will remember the end of the current word.
The repeat loop in printhandle points tpos to the start of the next word (or the
first character in the text if we’re just starting the loop). If tpos is currently
pointing to a line termination character (e.g. carriage return), we print the current
line, move to the next line and reset wpos to point to the next word. Form feeds are
handled in a similar fashion except that we eject the current page before continuing.
If tpos points to the nilChar (the ASCII character whose value is 0), we’ve
printed all of the text in the input stream so we print the line, eject the page and exit
the loop.
The otherwise clause checks to see if the next word in the input will fit on the
current line. If so, add the word to the line and update the pointers accordingly. Since
tpos points to the end of the current word, setting wpos equal to tpos causes us to
“leap” over that word.
If the word doesn’t fit on the line, we first print out the line and then move to the
next line. After a word is “accumulated” into a line, we add the width of the word to
the linerect.
The last routine of note, CalcNextWord, checks to see if the next character in the
text is a word break character such as a space or punctuation. If so, we now have
another word to add to the output line.
Summary
Reporter left justifies the text but you can easily add full justification. When
you get to the end of the current line (i.e. wordsize + linerect > page width) calculate
the number of pixels that would be needed to “fill” out the line. Subtract the current
line’s width (right-left) from the page width. If the current line is 2000 pixels wide
and the page rectangle is 2500 pixels, then the number of pixels needed to fill out the
line is 500. Divide this number into the number of spaces on the line and call
SpaceExtra to space out the text.
You’ll be surprised out how easy text formatting is with the Macintosh toolbox
and I encourage you to use Reporter as a starting point for your explorations.
{********************************}
{* File: Reporter.p *}
{* *}
{* Prints the entire *}
{* contents of the container *}
{* *}
{* ------------------------ *}
{* In: params[1] = handle *}
{* to the text to be printed *}
{* *}
{* ------------------------ *}
{* © 1988, Donald Koscheka, *}
{* All Rights Reserved *}
{* ------------------------ *}
{********************************}
(****************************
BUILD SEQUENCE
pascal Reporter.p
link -m ENTRYPOINT -rt ∂
XCMD=6555 -sn Main=Reporter ∂
Reporter.p.o ∂
“{Libraries}”Interface.o ∂
“{PLibraries}”Paslib.o ∂
-o “{xcmds}”testxcmds
*****************************)
{$S Reporter }
UNIT Donald_Koscheka;
{----INTERFACE----}
INTERFACE
USES
MemTypes, QuickDraw, OSIntf,
ToolIntf, PackIntf, HyperXCmd,
PrintTraps;
PROCEDURE EntryPoint(pPtr:XCmdPtr);
{----IMPLEMENTATION----}
IMPLEMENTATION
{$R-}
CONST
CARD = TRUE;
BKGND = FALSE;
NILCHAR = $00;
CR = $0D;
TAB = $09;
SPACE = $20;