TweetFollow Us on Twitter

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;
 FF= $0C; 
 LINEFEED = $0A; 
 QUOTE  = $22; 
 COMMA  = $2C; 
 PERIOD = $2E;
 PAREN  = $28;
 
TYPE
 Str31 = String[31]; 

PROCEDURE Reporter(pPtr:XCmdPtr);FORWARD;

{----EntryPoint----}

PROCEDURE EntryPoint(pPtr: XCmdPtr);
BEGIN
 Reporter(pPtr);
END;


{----Reporter----}

Function CalcNextWord(VAR wPtr:Ptr):INTEGER;
(**********************
* Given a pointer to a 
* word, calculate the 
* length of the next word
* in the run.  The length
* is determined by adding
* the width  of each character
* together until a word
* break is hit.
*
* The difference between 
* tpos and wpos is always
* one word (including the 
* sticky characters)
*
* IN: Pointer to text 
*wPtr == pointer to NEXT word in
*run (or NIL if No next word)
*
* OUT:  Width of the next 
* word in the line
***********************)
VAR
 done : BOOLEAN;
 wLen : INTEGER;
BEGIN
 done := FALSE;
 wLen := 0;
 
WHILE NOT done DO
BEGIN CASE wPtr^ OF

CR,FF,LINEFEED,NILCHAR:
 done := TRUE;
TAB:
 BEGIN
 WLen:=wLen+CharWidth( chr(SPACE) );
 done := TRUE;
 END;
 
 SPACE, QUOTE, COMMA, PERIOD: 
 BEGIN
 wlen:=wLen+CharWidth( chr(wPtr^));
 done := TRUE;
 wPtr:=Pointer( ORD(wPtr) +1);
 END;
 
OTHERWISE
 BEGIN
 WLen:=wLen+CharWidth( chr(wPtr^));
 wPtr := Pointer( ORD(wPtr) + 1 );
 END;
END; {*** CASE wPtr^ OF ***}
END; {*** WHILE NOT done ***}
 
IF wPtr^ = 0 THEN wPtr := NIL;
 
 CalcNextWord := wLen;
END;


Procedure PrintHandle( hand : Handle; printer : TPPrPort; Prec : THPrint 
);
(**********************
* Print the data passed 
* as a handle and 
* using the given printer
* port.
*
* Prints the data into 
* the current port and
* handles word wrap and
* page breaks.
*
**********************)
VAR 
 done   : BOOLEAN;
 lineHite,
 wordSize : INTEGER;
 pageHite, 
 pageWidth: INTEGER;
 num    : INTEGER;
 cPos   : Ptr; 
 wPos   : Ptr;
 tPos   : Ptr;
 lineRect : Rect;
 fontInfo : FontInfo;
 
PROCEDURE EjectPage;
(****************************
* Eject the current page and 
* adjust the rectangle 
* accordingly
*
****************************)
BEGIN
 PrClosePage( printer );
 PrOpenPage( printer, NIL );
 
{*** Opening a new page yields***}
{*** a new grafport, reset it ***}
 TextFont( GENEVA );
 TextSize( 9 );
 GetFontInfo( FontInfo );
 lineHite := fontInfo.ascent +
 fontInfo.descent +
 fontInfo.leading;

WITH Prec^^.prInfo.rpage DO
BEGIN
lineRect.Top   := top;
lineRect.Bottom:=lineRect.top +lineHite;
lineRect.Right := lineRect.left;
Moveto(lineRect.left,
lineRect.bottom );
END;
tpos := Pointer( ORD( tpos ) +  1 );
END;


PROCEDURE NewLine;
(******************************
* Move to a new position on the
* page.
******************************)
BEGIN
 WITH lineRect DO
 BEGIN
 top  := top + lineHite;
 bottom := bottom + lineHite;
 MoveTo( left, bottom );
 
 IF bottom > pageHite THEN 
 EjectPage;
 
 Right := Prec^^.prInfo.rpage.left;
 END;
END;

PROCEDURE DrawLine;
(******************************
* Draw the current line please
******************************)
BEGIN
 DrawText( cPos, 0, num );
 lineRect.Right := lineRect.left + wordSize;
 tpos := Pointer( ORD( tpos ) + 1 ); { debug }
 num := INTEGER( ORD(tPos) - ORD(wPos) );
 cPos := wPos;
 wPos := tPos;
END;

 
{---- Reporter ----}
BEGIN
 TextFont( GENEVA );
 TextSize( 9 );
 GetFontInfo( FontInfo );
 lineHite := fontInfo.ascent + 
 fontInfo.descent + 
 fontInfo.leading;

{*** get information about page ***}
 WITH Prec^^.prInfo.rpage DO
 BEGIN
 pageHite := bottom - top;
 pageWidth  := right-left;
 lineRect.top  := top;
 lineRect.bottom:= top + lineHite;
 lineRect.left   := left;
 lineRect.right  := left;
 MoveTo( lineRect.left, lineRect.bottom );
 END;

 Hlock( hand );
 cPos := hand^;
 wPos := cPos;
 num  := 0;
 done := false;
 
 REPEAT
 tPos := wPos;
 
 CASE tPos^ OF
 LINEFEED, CR:
 BEGIN
 DrawLine;
 NewLine;
 wpos := tpos;
 END;
 
 FF:
 BEGIN
 EjectPage;
 wpos := tpos;
 lineRect.right := lineRect.right + wordSize;
 END;
 
 NILCHAR:
 BEGIN
 DrawLine;
 done := TRUE;
 END;
 
 OTHERWISE
 BEGIN
 wordSize := CalcNextWord( tPos );
 IF( wordSize + lineRect.right ) < pageWidth THEN
 BEGIN
 num := num + INTEGER( ORD(tPos) - ORD(wPos) );
 wPos := tPos;
 END
 ELSE
 BEGIN
 DrawLine;
 NewLine;
 END;
 lineRect.right:=lineRect.right + wordSize;
 END;
 
 END {** CASE ***};
 UNTIL done;
 
 PRClosePage( printer );
 HUnlock( hand );
END;



PROCEDURE Reporter(pPtr: XCmdPtr);
(*********************
* Print the data that’s 
* passed in as a 
* parameter.
**********************)
VAR
 fieldPtr : Ptr;
 fieldName: Str255;
 fieldType: BOOLEAN;
 fieldData: Handle;
 prRecHandle: THPrint;
 prPort : TPPrPort;
 myStRec: TPrStatus;
 
{$I XCmdGlue.inc }

BEGIN
WITH pPtr^ DO
IF (paramCount <> 0) AND
(params[1] <> NIL) THEN 
BEGIN
 
PROpen;
prRecHandle := THPrint(NewHandle( SIZEOF(TPRINT) ));
PrintDefault( prRecHandle );
 
IF PRJobDialog( prRecHandle ) THEN BEGIN
prPort := PROpenDoc( prRecHandle, NIL, NIL );
PrOpenPage( prPort, NIL );
 
PrintHandle( params[1], prPort, prRecHandle );
 
PrCloseDoc( prPort );
 
 IF(prRecHandle^^.prJob.bJDocLoop = bSpoolLoop) AND (PrError = noErr) 
THEN
 PRPicFile( prRecHandle, NIL, NIL, NIL, myStRec );

END; {*** IF PRJobDialog ***}
 
 DisposHandle(Handle(prRecHandle) );
 PrClose;
 
END; {*** paramCount <> 0 ***}
pPtr^.returnValue := NIL;
END; {*** PROCEDURE Reporter ***}

END.

Listing 1. Reporter.P - A text printing XCMD.

end HyperChat
 
AAPL
$111.78
Apple Inc.
-0.87
MSFT
$47.66
Microsoft Corpora
+0.14
GOOG
$516.35
Google Inc.
+5.25

MacTech Search:
Community Search:

Software Updates via MacUpdate

NeoOffice 2014.6 - Mac-tailored, OpenOff...
NeoOffice is a complete office suite for OS X. With NeoOffice, users can view, edit, and save OpenOffice documents, PDF files, and most Microsoft Word, Excel, and PowerPoint documents. NeoOffice 3.x... Read more
LibreOffice 4.3.5.2 - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
CleanApp 5.0.0 Beta 5 - Application dein...
CleanApp is an application deinstaller and archiver.... Your hard drive gets fuller day by day, but do you know why? CleanApp 5 provides you with insights how to reclaim disk space. There are... Read more
Monolingual 1.6.2 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
NetShade 6.1 - Browse privately using an...
NetShade is an Internet security tool that conceals your IP address on the web. NetShade routes your Web connection through either a public anonymous proxy server, or one of NetShade's own dedicated... Read more
calibre 2.13 - Complete e-library manage...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
Mellel 3.3.7 - Powerful word processor w...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
ScreenFlow 5.0.1 - Create screen recordi...
Save 10% with the exclusive MacUpdate coupon code: AFMacUpdate10 Buy now! ScreenFlow is powerful, easy-to-use screencasting software for the Mac. With ScreenFlow you can record the contents of your... Read more
Simon 4.0 - Monitor changes and crashes...
Simon monitors websites and alerts you of crashes and changes. Select pages to monitor, choose your alert options, and customize your settings. Simon does the rest. Keep a watchful eye on your... Read more
BBEdit 11.0.2 - Powerful text and HTML e...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more

Latest Forum Discussions

See All

Galaxy Trucker Pocket (Games)
Galaxy Trucker Pocket 1.0.8 Device: iOS iPhone Category: Games Price: $2.99, Version: 1.0.8 (iTunes) Description: Galaxy Truckers Wanted!================================================================= (5/5) "Galaxy Trucker isn’t... | Read more »
Make your own Tribez Figures (and More)...
Make your own Tribez Figures (and More) with Toyze Posted by Jessica Fisher on December 19th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
So Many Holiday iOS Sales Oh My Goodness...
The holiday season is in full-swing, which means a whole lot of iOS apps and games are going on sale. A bunch already have, in fact. Naturally this means we’re putting together a hand-picked list of the best discounts and sales we can find in order... | Read more »
It’s Bird vs. Bird in the New PvP Mode f...
It’s Bird vs. Bird in the New PvP Mode for Angry Birds Epic Posted by Jessica Fisher on December 19th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Telltale Games and Mojang Announce Minec...
Telltale Games and Mojang Announce Minecraft: Story Mode – A Telltale Games Series Posted by Jessica Fisher on December 19th, 2014 [ permalink ] | Read more »
WarChest and Splash Damage Annouce Their...
WarChest and Splash Damage Annouce Their New Game: Tempo Posted by Jessica Fisher on December 19th, 2014 [ permalink ] WarChest Ltd and Splash Damage Ltd are teaming up again to work | Read more »
BulkyPix Celebrates its 6th Anniversary...
BulkyPix Celebrates its 6th Anniversary with a Bunch of Free Games Posted by Jessica Fisher on December 19th, 2014 [ permalink ] BulkyPix has | Read more »
Indulge in Japanese cuisine in Cooking F...
Indulge in Japanese cuisine in Cooking Fever’s new sushi-themed update Posted by Simon Reed on December 19th, 2014 [ permalink ] Lithuanian developer Nordcurrent has yet again updated its restaurant simulat | Read more »
Badland Daydream Level Pack Arrives to C...
Badland Daydream Level Pack Arrives to Celebrate 20 Million Downloads Posted by Ellis Spice on December 19th, 2014 [ permalink ] | Read more »
Far Cry 4, Assassin’s Creed Unity, Desti...
Far Cry 4, Assassin’s Creed Unity, Destiny, and Beyond – AppSpy Takes a Look at AAA Companion Apps Posted by Rob Rich on December 19th, 2014 [ permalink ] These day | Read more »

Price Scanner via MacPrices.net

Holiday sale: 13-inch 128GB MacBook Air for $...
 Best Buy has the 2014 13-inch 1.4GHz 128GB MacBook Air on sale for $849.99, or $150 off MSRP, on their online store. Choose free home shipping or free local store pickup (if available). Price valid... Read more
13-inch 2.6GHz Retina MacBook Pro on sale for...
Best Buy has lowered their price on the 2014 13″ 2.6GHz/128GB Retina MacBook Pro to $1149.99 on their online store for a limited time. That’s $150 off MSRP and the lowest price available for this... Read more
Kodak Returns to CES With New Consumer Produ...
Former photography colossus Kodak is returning to CES for the first time in three years where the Kodak booth (#21818 South Hall 1) will showcase a wide range of innovative, imaging-related products... Read more
Invaluable Launches New Eponymously -Named A...
Invaluable, the world’s largest online live auction marketplace, hhas announced the official launch of the Invaluable app for iPad, now available for download in the iTunes App Store. Invaluable... Read more
IDC Reveals Worldwide Mobile Enterprise Appli...
International Data Corporation (IDC) last week hosted the IDC FutureScape: Worldwide Mobile Enterprise Applications and Solutions 2015 Predictions Web conference. The session provided organizations... Read more
Hello Vino Wine App Launches “Safe Ride Home”...
Hello Vino has announced addition of a new “Get a Safe Ride Home” feature in its Food & Drink app with a direct connection to Uber, the technology platform that connects users with rides. The... Read more
DEVON-technologies Releases DEVONthink To Go...
Coeur d’Alene, Idaho based DEVON-technologies, LLC has updated DEVONthink To Go, its mobile companion to DEVONthink, to version 1.5. The update includes an iOS 8 extension, compatibility with the... Read more
The Apple Store offering free next-day shippi...
The Apple Store is now offering free next-day shipping on all in stock items if ordered before 12/23/14 at 10:00am PT. Local store pickup is also available within an hour of ordering for any in stock... Read more
It’s 1992 Again At Sony Pictures, Except For...
Techcrunch’s John Biggs interviewed a Sony Pictures Entertainment (SPE) employee, who quite understandably wished to remain anonymous, regarding post-hack conditions in SPE’s L.A office, explaining “... Read more
OtterBox Defender Series Case For iPad mini 3...
With their innovative Touch ID technology and ultrathin profile, the latest tranche of Apple iPads are more desirable than ever, and OtterBox has just announced the Defender Series custom-engineered... Read more

Jobs Board

*Apple* Store Leader Program (US) - Apple, I...
…Summary Learn and grow as you explore the art of leadership at the Apple Store. You'll master our retail business inside and out through training, hands-on experience, Read more
Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.