source/NewApp.c

/*
    copyright © 1991-1994 Apple Computer Inc.  All rights reserved.
    
    NewApp.c
    This file contains all new API implementations for the ImageWriter driver.
    
    Modification history
    03/19/91        TED             New file today
    04/23/91        Sam Weiss       Changed Inherit to Forward
    05/29/91        TED             Added manual feed and faster mode support
    12/20/93        dmh             Sync'd with the shipping 1.0b3 GX driver.
    03/24/94        Ken Hittleman   Updated manual feed to use gxTrayFeedInfo
    03/30/94        Ken Hittleman   Added call to paper matching CheckStatus when switching to auto-feed
    07/05/94        Ken Hittleman   Removed set color and page size from IW I path, which blew cookies on it
    08/26/94        dmh             Sync'd with the shipping 1.0.1 GX driver.
    05/21/96        Jason H-H       Updated for ETO#19/MW.
    05/21/96        Don Swatman     Modifications for halftoning, dithering and plane seperations.
*/
 
 
/**** DEFINE DEBUG ON ****/
//#define DEBUGLEVEL DEBUGON
/*************************/
 
 
#include <Memory.h>
#include <Errors.h>
#include <ToolUtils.h>
#include <Resources.h>
#include <Packages.h>
#include <GXPrinterDrivers.h>
#include <GXPrinting.h>
#include <FixMath.h>
#include <GXMath.h>
#include <GXGraphics.h>
#include <GraphicsLibraries.h>
#include <GXLayout.h>
#include <CollectionLibrary.h>
#include <JobFormatModeLibrary.h>
#include <PaperTypeLibrary.h>
#include <PicturesAndPICTLibrary.h>
 
#include "CommonDefines.h"
#include "NewDriverProtos.h"
 
 
/*  -----------------------------------------------------------------------
 
    __Startup__ contains our jump table to the overrides.
    This code must be kept in sync with the assembly jump table
    in NewApp.a and the gxOverrideType resources in NewApp.rsrc
 
    ----------------------------------------------------------------------- */
 
 
#if defined(__MWERKS__)
asm void __Startup__(void);
asm void __Startup__(void)
    {
        dc.L    0                   // Reserved for owner count.
 
        // Universal messages
        jmp SD_Initialize
        jmp SD_ShutDown
        jmp SD_DefaultPrinter
        jmp SD_DefaultFormat
        jmp SD_DefaultJob
        jmp SD_JobFormatDialog
        jmp SD_JobFormatModeQuery
        jmp SD_RenderPage
        jmp SD_OpenConnection
        jmp SD_CloseConnection
        jmp SD_StartSendPage
        jmp SD_FinishSendPage
        jmp SD_DumpBuffer
        jmp SD_FreeBuffer
        jmp SD_SetupImageData
        jmp SD_JobIdle
        jmp SD_JobPrintDialog
        jmp SD_HandlePanelEvent
 
 
        // Raster messages
        jmp SD_PackageBitmap
        jmp SD_LineFeed
 
        
        rts                         // this is needed so __Startup__ symbol works
    }
#endif
 
 
/* ------------------------------------------------------------------------------------ */
/*  INTERNAL DEFINES                                                                    */
/* ------------------------------------------------------------------------------------ */  
 
// positive error for aborting job and placing on hold
#define kPutJobOnHoldErr            3
 
// timeout (in ticks) for the initial query
#define kQueryTimeout               (7*60);
 
// things this specific driver puts into the DTP config file
#define kImageWriterConfigType      'ifig'
#define kImageWriterConfigID        (0)         
typedef struct
    {
        Boolean hasColorRibbon;
        Boolean hasSheetFeeder;
        Boolean isImageWriterII;        // is this an ImageWriter II, or an older model?
    } ImageWriterConfigRecord, *ImageWriterConfigPtr, ** ImageWriterConfigHandle;
    
/* Define special characters needed */
#define ESCAPE              (char) 27
 
/* A record to hold a set margins command */
 
typedef struct SetMarginsRecord
    {
    char        cEscape;                    // ESCAPE character
    char        cCommand;                   // Set Margins command character
    char        cIndentDistance[4];         // number of dots to indent
    } SetMarginsRecord, *SetMarginsPtr;
 
#define kSetMarginsCommand  (char)'F'       // ImageWriter II uses 'F' for tabbing
#define kSetMarginsSize     6
 
 
/*  Define a record that can hold one scan line's worth of data, 1280 will
    only happen at 160 dpi. and 14 inch wide paper.   */
 
typedef struct ScanLineRecord
    {
    char            cColorEscape;                   // ESCAPE character
    char            cSetColorCommand;               // Set color command
    char            cColor;                         // The color
    char            cEscape;                        // ESCAPE character
    char            cCommand;                       // 'enter graphics' command
    char            cLineLength[4];                 // number of dots to print
    char            iTheData[2240];                 // Bits for the data, enough for one line's worth
    } ScanLineRecord, *ScanLinePtr;
 
 
#define kGraphicsCommand    (char)'G'       /* graphics printing command */
#define kRepeatGroup        (char)'V'       /* repeat group character */
#define kSetColorCommand    (char)'K'       /* Set color command */
#define kGroupSize          6               /* Size of one group header */
 
#define kScanLineSize       3               /* NOTE: this is just the header size! */
 
 
// Status flags for PAP status queries
#define kColorRibbonBit     0
#define kSheetFeederBit     1
#define kPaperOutBit        2
#define kCoverOpenBit       3
#define kOffLineBit         4
#define kPaperJamBit        5
#define kPrinterFaultBit    6
#define kHeadMovingBit      7
#define kPrinterBusyBit     8
 
#define kOutOfPaperMask         (  (0x8000 >> kPaperJamBit) | (0x8000 >> kCoverOpenBit) | (0x8000 >> kOffLineBit) )
#define kPrinterOfflineMask     (  (0x8000 >> kOffLineBit) )
#define kPrinterBusyMask        (  (0x8000 >> kPrinterBusyBit) )
#define kHeadMovingMask         (  (0x8000 >> kHeadMovingBit) )
 
// Color pass
 
#define kYellowPass   1
#define kMagentaPass  2
#define kCyanPass     3
#define kBlackPass    4
 
//-------------------------------------------------------------------
// Structures and constants for the render options collection
//
// This is structured so it can be used by the gxExtendedDITLType
// resource -'xdtl'. The types and allignment are significant.
//
// Booleans : unsigned char : These are byte alligned.
// Pop-up   : short         : Word (2Byte) alligned
// Float    : fixed         : Word (2Byte) alligned
//-------------------------------------------------------------------
 
#define kDitherIt   0
#define kHalfToneIt 1
 
#define kRenderOptsCyan    0
#define kRenderOptsMagenta 1
#define kRenderOptsYellow  2
#define kRenderOptsBlack   3
 
struct CMYKRenderCollection
{
// Plane flags, set to true to print the plane
    unsigned char cyanIsOn;
    unsigned char magentaIsOn;
    unsigned char yellowIsOn;
    unsigned char blackIsOn;
 
// Set to "kDitherIt" for dithered output, or "kHalfToneIt" for half tone
    unsigned char renderMode;
    unsigned char fill1;
    
// used when in dither mode to select the level of dither
    short ditherLevel;
 
// used when in half tone mode to select how to half tone
    short dotType;
    Fixed angles[4];
    Fixed frequency[4];
 
};
typedef struct CMYKRenderCollection CMYKRenderCollection;
 
 
//<FF>
/* ------------------------------------------------------------------------------------ */
/*  INTERNAL ROUTINES                                                               */
/* ------------------------------------------------------------------------------------ */
void Long2Dec(long aLong, Ptr emitHere)
/*
    Converts a long into an ASCII string, padded with leading zeros.
*/
{   
    char    aString[10];
    short   i, actualWidth, strLength;
    
    NumToString(aLong, (unsigned char*)aString);
    
    // Get the width of the string, check for being too small
    strLength = aString[0];
    actualWidth = strLength;
    if (actualWidth < 4)
        actualWidth = 4;
        
    // output the string, padding with the requested character
    strLength = actualWidth-strLength;
    for (i = 0; i < actualWidth; ++i)
        {
        *emitHere++ = (i < strLength) 
            ? '0' : aString[(i+1)-(strLength)];
        }
        
} // Long2Dec
 
 
//<FF>
/* ------------------------------------------------------------------------------------ */
Boolean PrinterHasColorRibbon(gxPrinter thePrinter)
/*
    Returns true if the config file says that the printer is blessed with a color ribbon,
    false if it is a black and white ribbon.
*/
{
    Boolean                     hasColor = true;
    Str32                       deviceName;
    OSErr                       anErr;
    ImageWriterConfigHandle     configHandle;
    
    // if not formatting to a particular device, assume color ribbon, for widest range of colorSpaces
    GXGetPrinterName(thePrinter, deviceName);
    if (deviceName[0] != 0)
        {
        // if we are going to a particular device, assume no color, as that is more common
        hasColor = false;
        
        anErr = GXFetchDTPData(deviceName, kImageWriterConfigType, kImageWriterConfigID, (Handle*)&configHandle);
        if (anErr == noErr)
            {
            hasColor = (**configHandle).hasColorRibbon;
            DisposHandle((Handle) configHandle);
            }
        }
    
    return(hasColor);
    
} // PrinterHasColorRibbon
 
 
//<FF>
/* ------------------------------------------------------------------------------------ */
gxViewDevice    NewDeviceResolutionViewDevice(void)
/*
    This routine creates a viewDevice and gives it a scale factor in it's mapping
    appropriate for this device (144 dpi in the case of the IW)
*/
{
    gxViewDevice        vd;
    
    // create the viewDevices with a fake bitmap
    {
    gxShape     tempBitmap;
    gxBitmap        aBitmap;
    
    aBitmap.pixelSize   = 1;
    aBitmap.rowBytes    = 0;
    aBitmap.width       = 0;
    aBitmap.height      = 0;
    aBitmap.image       = (char*)gxMissingImagePointer;
    aBitmap.space       = gxNoSpace;
    aBitmap.set         = nil;
    aBitmap.profile     = nil;
    
    tempBitmap = GXNewBitmap(&aBitmap, nil);
    vd = GXNewViewDevice(gxScreenViewDevices, tempBitmap);
    GXDisposeShape(tempBitmap);
    }
 
    // setup a mapping for a 144 (2X) viewDevice
    {
    gxMapping   vdMapping;
    
    ResetMapping(&vdMapping);
    ScaleMapping(&vdMapping, ff(2), ff(2), ff(0), ff(0));
    
    GXSetViewDeviceMapping(vd, &vdMapping);
    }
    
    return(vd);
    
} // NewDeviceResolutionViewDevice
 
//<FF>
/* ------------------------------------------------------------------------------------ */
Boolean JobIsBest(long *imagewriterOptions)
/*
    Returns true if the current job is a final quality mode job, else returns false.
    Also, returns the imagewriter rendering options.
*/
{
    Boolean         isFinal;
    gxQualityInfo   jobQualitySettings;
    long            itemSize = sizeof(jobQualitySettings);
    OSErr           status;
    Collection      jobCollection;
    
    // cache the collection
    jobCollection = GXGetJobCollection(GXGetJob());
    
    // find out the info
        
    isFinal = false;
    
    status = GetCollectionItem(jobCollection, 
                                    gxQualityTag, gxPrintingTagID, 
                                    &itemSize, &jobQualitySettings);
    
    if ( (status == noErr) && (jobQualitySettings.currentQuality == (jobQualitySettings.qualityCount-1)) )
        isFinal = true;
    
    // we default to super res
    *imagewriterOptions = kSuperRes;
    itemSize = sizeof(imagewriterOptions);
    status = GetCollectionItem(jobCollection, 
                                    DriverCreator, 0, 
                                    &itemSize, imagewriterOptions);
                
    // and return the job quality mode
    return(isFinal);
    
} // JobIsBest
 
 
//<FF>
/* ------------------------------------------------------------------------------------ */
 
OSErr   DoTheQuery(unsigned short * statusReturn, Boolean papStatus)
/*
    Returns in statusString the current status for the printer.  Returns various
    errors from IO package if the printer's status could not be found.
*/
{
    OSErr               anErr = noErr;
    long                statusLength;                   // status string size
    SpecGlobalsHdl      hGlobals = GetMessageHandlerInstanceContext();
    char                cmdData[4];
    Str255              theTerminatorStr;
 
    theTerminatorStr[0] = 0x01;     //  teminator codes for query
    theTerminatorStr[1] = 0x0D;     //  CR == 0x0D
    cmdData[0] =    0x1B;           // cmdData control codes to query ImageWriter II 
    cmdData[1] =    0x3F;
 
    // default to a clear status
    *statusReturn = 0;
 
    // send the query
    if (papStatus)
    {
        char    statusString[255];              // returned string
        
        // According to the old IW driver, sometimes it will return all of the bits
        // set.  This is an error, but we can just try again.
        do
        {
            statusLength = 255;
            anErr = Send_GXGetDeviceStatus(nil, 0, statusString, &statusLength, nil);
        } while ( (anErr == noErr) && (statusString[0] == 0xFF) );
        // return the printer status bits in the PAP case
        *statusReturn = *(unsigned short*)&statusString[0];
    }
    else
    {
        char    statusString[8];                // returned string
 
        if ((**hGlobals).isImageWriterII)
        {
            statusLength = 8; // max number of characters to get back
            anErr = Send_GXGetDeviceStatus((Ptr)&cmdData, 2, statusString, &statusLength, theTerminatorStr);
                
            if ( anErr == gxAioTimeout)
            {
                *statusReturn = kPrinterOfflineMask;
                anErr = noErr;
            }
            else
            {
                if (anErr == noErr)
                {
                    // generate printer status bits in the serial case
                    if (statusString[4] == 'C')
                        *statusReturn |= 0x8000 >> kColorRibbonBit;
                    if ( (statusString[5] == 'F') || (statusString[4] == 'F') )
                        *statusReturn |= 0x8000 >> kSheetFeederBit;
                }
            }
        }
    }
        
    nrequire(anErr, Send_GXGetDeviceStatus);
 
// FALL THROUGH EXCEPTION HANDLING  
Send_GXGetDeviceStatus:
    
    return(anErr);
 
} // DoTheQuery
 
//<FF>
/* ------------------------------------------------------------------------------------ */
OSErr FetchStatusString(unsigned short * statusReturn, Boolean papStatus, Boolean doRetry)
/*
    Returns in statusString the current status for the printer.  Returns various
    errors from IO package if the printer's status could not be found.
    
    Handles reporting error conditions to the user.
*/
{
    OSErr               anErr;
    SpecGlobalsHdl      hGlobals = GetMessageHandlerInstanceContext();
    
    anErr = DoTheQuery(statusReturn, papStatus);        
    nrequire(anErr, Send_GXGetDeviceStatus);
    
    // printer offline?
    if (    
        (doRetry) &&
        ( ((*statusReturn) & kPrinterOfflineMask) != 0 )  
        )
        {
        gxStatusRecord      theStat;
        gxStatusRecord      *pStat = &theStat;
        Boolean             printerIsFixed = false;
        
        pStat->statusOwner  = 'drvr';
        pStat->statResId    = kDriverStatus;        
        pStat->statResIndex = kCheckOnline;         
        pStat->bufferLen    = 0;
        pStat->dialogResult = 0;    // ORIGINALLY SET TO ===> nil;
        
 
        // keep sending the user the alert until either
        //  a) the problem resolves itself
        //  b) the user responds via the dialog
        //  c) some other (fatal) error happens
        do
            {
            
            // tell the user
            anErr = GXAlertTheUser(pStat);
            
            // based on the user's response, continue or cancel
            switch (pStat->dialogResult)
                {
                case ok:
                    // retry
                    break;
                    
                case cancel:
                    anErr = gxPrUserAbortErr;
                    break;
                    
                case 3:
                    anErr = kPutJobOnHoldErr;
                    break;
                }
                
            // error to return from next idle
            (**hGlobals).idleError = anErr;
 
            // if printer got suddenly turned online, do an OK
            if ( (anErr == noErr) && ((papStatus) || (pStat->dialogResult == ok)) )
                {
                pStat->dialogResult = 0;    // ORIGINALLY SET TO ===> nil;
                (void) DoTheQuery(statusReturn, papStatus);     
                if (    
                    ( ((*statusReturn) & kPrinterOfflineMask) == 0 ) 
                    )
                    {
                    printerIsFixed = true;
                    pStat->dialogResult = ok;
                    anErr = noErr;
                    }
                }
                
            } while ((anErr == noErr) && (pStat->dialogResult == 0)); // WAS ORIGINALLY ====>  (pStat->dialogResult == nil)
 
        // printer is okay -- no error
        if (printerIsFixed)
            anErr = noErr;
        
        // display "sending data to the printer" message
        if (anErr == noErr)
            anErr = GXReportStatus(kDriverStatus, kSendingData);
        }
 
// FALL THROUGH EXCEPTION HANDLING  
Send_GXGetDeviceStatus:
    
    return(anErr);
    
} // FetchStatusString
 
//<FF>
/* ------------------------------------------------------------------------------------ */
OSErr UpdateConfiguration(void)
/*
    This routine queries the printer for its hardware configuration (color ribbon and
    sheet feeder options), and stores that info into the configuration file.
*/
{
    SpecGlobalsHdl              hGlobals;
    Str32                       deviceName;
    OSErr                       anErr = noErr;
    ImageWriterConfigHandle     configHandle;
    ImageWriterConfigPtr        configPtr;
    Boolean                     isImageWriterII;
    ResType                     commType;
    
    hGlobals = GetMessageHandlerInstanceContext();  
    isImageWriterII = false;
// find out what we are printing to, and how we are connected
    GXGetPrinterName(GXGetJobOutputPrinter(GXGetJob()), deviceName);
    anErr = GXFetchDTPData(deviceName, gxDeviceCommunicationsType, gxDeviceCommunicationsID, (Handle*)&configHandle);
    nrequire(anErr, FetchCommType);
    commType = **(ResType**)configHandle;
    DisposHandle((Handle) configHandle);
    
    
    // store away the communications type for future use
    //{
    //  SpecGlobalsHdl          hGlobals = GetMessageHandlerInstanceContext();
    //  
    //  (**hGlobals).commType = commType;
    //}
    
    // find out the original configuration
    if (GXFetchDTPData(deviceName, kImageWriterConfigType, kImageWriterConfigID, (Handle*)&configHandle) == noErr)
    {
        // remember if we thought we had an IW2 when we started
        configPtr = *configHandle;
        (**hGlobals).isImageWriterII = isImageWriterII = configPtr->isImageWriterII;
        DisposeHandle((Handle) configHandle);
 
        // if we aren't an ImageWriter II, bail out now - because the timeout takes two minutes!
        if (!isImageWriterII)
        {
            return(noErr);          
        }
    }
    else
    {       
        if (commType == 'PPTL')
        {
            isImageWriterII = true;
        }   
        // Assume IW 2 so we do the query for real
        (**hGlobals).isImageWriterII = true;
    }
        
    // make a handle to hold our configuration information for the printer
    configHandle = (ImageWriterConfigHandle) NewHandle(sizeof(ImageWriterConfigRecord) );
    anErr = MemError();
    nrequire(anErr, NewHandle);
    
    // setup the default for the device - in case the query fails
    configPtr = *configHandle;
    configPtr->hasColorRibbon = false;
    configPtr->hasSheetFeeder = false;
    configPtr->isImageWriterII = true;
    
    // send initial data first to make sure IO handshaking is working,
    // to load the first sheet of paper into the feeder (if any),
    // and to take up "gear lash" in the device.  This is copied from
    // what the old driver did.  Not doing this will cause the sheet
    // feeder not to feed the initial page of data.
    {
    char    sendBuffer[11];
    
    // <CR>
    sendBuffer[0] = 0x0D;
    
    // linefeed size = 18/144th
    sendBuffer[1] = ESCAPE;
    sendBuffer[2] = 'T';
    sendBuffer[3] = '1';
    sendBuffer[4] = '8';
    
    // reverse line feed
    sendBuffer[5] = ESCAPE;
    sendBuffer[6] = 'r';
    sendBuffer[7] = 0x0A;
    
    // forward line feed
    sendBuffer[8] = ESCAPE;
    sendBuffer[9] = 'f';
    sendBuffer[10] = 0x0A;
    
    anErr = Send_GXWriteData(sendBuffer, 11);
    nrequire(anErr, Failed_SendInitialData);
    }
    
    {
        unsigned short  statusReturn;
        
        // query the device
        if ((isImageWriterII) && (commType == 'SPTL') )
            (**hGlobals).idleTimeout = TickCount() + kQueryTimeout;
        anErr = FetchStatusString(&statusReturn, (Boolean)(commType == 'PPTL'), isImageWriterII);
 
        if ( (anErr == noErr) && ( (statusReturn & kPrinterOfflineMask) != 0 )  )
            anErr = gxAioTimeout;
        (**hGlobals).idleTimeout = 0;
        GXReportStatus(kDriverStatus, kSendingData);
        
        // and scan the string looking for information about printer kind and options
        configPtr = *configHandle;
        if ( anErr == gxAioTimeout )
        {
            // if we timeout and we don't know the printer kind - assume IW1
            if (!isImageWriterII)
            {
                anErr = noErr;
                isImageWriterII = configPtr->isImageWriterII = false;
            }
        }
        else
        {
            isImageWriterII = true;
            configPtr->hasColorRibbon = (statusReturn & (0x8000 >> kColorRibbonBit)) != 0;
            configPtr->hasSheetFeeder = (statusReturn & (0x8000 >> kSheetFeederBit)) != 0;
        }
        nrequire(anErr, FetchStatusString);
    }
    
    // Remember if this was an ImageWriter II after the query
    (**hGlobals).isImageWriterII = isImageWriterII;
    
    // write out the new configuration
    anErr = GXWriteDTPData(deviceName, kImageWriterConfigType, kImageWriterConfigID, (Handle)configHandle);
    
    
// CLEANUP EXCEPTION HANDLING
FetchStatusString:
Failed_SendInitialData:
    DisposHandle((Handle) configHandle);
    
NewHandle:
Send_GXWriteData:
FetchCommType:
    return(anErr);
    
} // UpdateConfiguration
 
 
/* ------------------------------------------------------------------------------------ */
OSErr WriteDraftChars(long **draftTable, unsigned char *draftChar, long numChars)
/*
    This routine writes out a single character in the native set of the printer.
    It uses a table that's part of the driver to do the right thing in order to generate this
    character.
*/
{
    OSErr       anErr = noErr;
    char        outputChars[20];                // a maximum of 20 characters can be generated
    short       charCount;                  
    
    // For each character in the buffer, determine how to map the character to a draft character
    for (; numChars > 0; --numChars, ++draftChar)
    {
        // No characters yet for this output character
        charCount = 0;
            
        // Only consider characters in the printable range
        if (*draftChar >= 0x20)
        {
            unsigned long   draftControl = (*draftTable)[*draftChar-0x20];  // Fetch native mode long word corresponding to this character
            unsigned char   outChar;
            unsigned char   nationalSet;
            short               i;
            
            // For each word which composes the native mode long word 
            for (i = 1; i >= 0; --i)
            {
                // Should we send a backspace character (to overstrike)?
                if ( (draftControl & 0x80000000) != 0 )
                    outputChars[charCount++] = 0x08;
                
                outChar = (draftControl >> 16) & 0xFF;
                if (outChar != 0)
                {
                    // Determine the national character set to select
                    nationalSet = (draftControl >> 24) & 0xF;   
    
                    //  Is this character in the standard, built-in character set?
                    if (nationalSet == 0)
                    {
                        outputChars[charCount++] = outChar;
                    }
                    else    //  T => Must select a foreign language character set 
                    {
                        outputChars[charCount++] = 0x1B;
                        outputChars[charCount++] = 0x44;
                        outputChars[charCount++] = nationalSet;
                        outputChars[charCount++] = 0x00;
                        outputChars[charCount++] = outChar;
                        outputChars[charCount++] = 0x1B;                // We always switch back to the kAmerican character set
                        outputChars[charCount++] = 0x5A;
                        outputChars[charCount++] = 0x07;
                        outputChars[charCount++] = 0x00;
                    }
                }
                
                // Take the next (low) word and process it (if we're not all done)
                draftControl <<= 16;
            }   
        }
            
        // If we generated any data, send it out now
        if (charCount > 0)
            anErr = Send_GXBufferData(outputChars, charCount, gxNoBufferOptions);
    }
        
    return(anErr);  
    
} // WriteDraftChars
 
/* ------------------------------------------------------------------------------------ */
OSErr GetPointerThisBig(Ptr *theBuff, long numBytes) 
{
    OSErr       anErr = noErr;
    
    if (*theBuff != nil)
    {
        if ( GetPtrSize(*theBuff) < numBytes )  //  T => Won't be big enough; make a new one
        {
            DisposPtr(*theBuff);
            *theBuff = nil;
        }
    }
 
    if (*theBuff == nil)
    {
        *theBuff = NewPtrClear(numBytes);
        anErr = MemError();
    }
    
    return(anErr);
    
} // GetPointerThisBig
 
/* ------------------------------------------------------------------------------------ */
OSErr GetTextAndPosition(   gxShape         theShape, 
                            Ptr             *theChars, 
                            long            *numChars, 
                            gxPoint         *textPosition)
{
    OSErr       anErr = noErr;
    long        textLength;
    
    // Determine the size of the text data and the position of the text
    textLength = GXGetLayout(theShape, nil, nil, nil, nil, nil, nil, nil, nil, textPosition);
 
    // Make sure we have a buffer pointer large enough to hold all of the data
    
    anErr = GetPointerThisBig(theChars, textLength);
    require(anErr == noErr, CantAllocTextBuff);
    
    // Now we retrieve the text
    GXGetLayout(theShape, *theChars, nil, nil, nil, nil, nil, nil, nil, nil);
    
    // Remember the number of characters in the shape
    *numChars = textLength;
    
 
/******* Clean-up *******/
 
CantAllocTextBuff:
    return(anErr);
    
} // GetTextAndPosition
 
/* ------------------------------------------------------------------------------------ */
OSErr PrintPageInDraftMode(gxShape thePage, gxRasterImageDataHdl imageData)
{
    OSErr                   anErr = noErr;
    long                    i;
    long                    numItems;
    Fixed                   currYPos = ff(0);
    Ptr                     theChars = nil;
    long                    numChars = 0;
    gxPoint                 textPosition;
    Fixed                   oldTextSize = ff(0);
    SpecGlobalsHdl          hGlobals = GetMessageHandlerInstanceContext();
    
    // Since the page picture we need to process is a picture shape that's embedded in
    // thePage (a shape containing one item => a picture), we need to extract the real
    // page picture from thePage.
    
    thePage = GetPictureItem(thePage, 1, nil, nil, nil, nil);
    numItems = GXGetPicture(thePage, nil, nil, nil, nil);
    
    // For each shape within the picture, check its type and process it accordingly
    
    for (i = 1; i <= numItems; ++i)
    {
        gxShape             theShape;
        short               theType;
                
        theShape = GetPictureItem(thePage, i, nil, nil, nil, nil);
        theType = GXGetShapeType(theShape);
        
        if (theType == gxLayoutType)    //  T => We have a layout shape
        {
            Fixed       textSize;
            char        buff[12];
            char        theFace;
            short       cmndBuffSz;
            
            // First determine the style in which we're printing
            
            theFace = GetStyleCommonFace( GXGetShapeStyle(theShape) );
            
            buff[0] = ESCAPE;
            if ( (theFace & bold) != 0 )    //  T => Turn bold facing on
                buff[1] = '!';
            else                                    //  T => Turn it off
                buff[1] = '"';
            
            buff[2] = ESCAPE;
            if ( (theFace & underline) != 0 )   //  T => Turn underline facing on
                buff[3] = 'X';
            else                                            //  T => Turn it off
                buff[3] = 'Y';
                
            cmndBuffSz = 4;
            
            // Next determine if we need to change the size of the font being used
            
            textSize = GXGetShapeTextSize(theShape);
            if (textSize != oldTextSize)    //  T => Must issue LQ command to change font size
            {
                buff[4] = ESCAPE;               //  The first escape command selects black color
                buff[5] = kSetColorCommand;
                buff[6] = '0';
                
                buff[7] = ESCAPE;               //  The second escape command selects a draft font
                buff[8] = 'a';
                buff[9] = '1';
                
                buff[10] = ESCAPE;              //  The third escape command selects the character pitch
                
                if ( textSize <= ff(10) )   //  T => Select 10 cpi
                {
                    buff[11] = 'N';
                }
                else    //  T => All other sizes get mapped to 12 cpi
                {
                    buff[11] = 'E';
                }
                
                // Remember the last text size
                oldTextSize = textSize; 
                
                // Adjust the size of the data to be sent to the printer
                cmndBuffSz += 8;
            }
            // else - no change in font size
            
            // Send the commands to the printer
            anErr = Send_GXBufferData(buff, cmndBuffSz, gxDontSplitBuffer);
            require(anErr == noErr, CantSendFontCmnd);
 
            // Get the ASCII text and the starting position of the data
            anErr = GetTextAndPosition(theShape, &theChars, &numChars, &textPosition);
            require(anErr == noErr, CantGetTextAndPos);
            
            if ( (currYPos != ff(0)) && (currYPos != textPosition.y) )  //  T => Moving to a lower line, finish the last ;line with a CR
            {
                char        c = 0x0D;
                
                anErr = Send_GXBufferData(&c, 1, gxNoBufferOptions);
                require(anErr == noErr, CantSendCRCmnd);
            }
            
            // Position the print head to the proper location on the page
            {
                gxMapping               theMapping;
                long                    lineFeedSize;
                Str255                  positionCmndsBuff;
                unsigned long           bytesInBuff = 0;
                unsigned char           *p;
 
                GXGetShapeMapping(theShape, &theMapping);
                MapPoints(&theMapping, (long) 1, &textPosition);    //  Just map the first point
                
                // Now position the print head vertically
 
                lineFeedSize = (textPosition.y - currYPos) >> 16;
                anErr = Send_GXRasterLineFeed(&lineFeedSize, (char*)positionCmndsBuff, &bytesInBuff, imageData);
                require(anErr == noErr, CantEmitLineFeeds);
                
                // Update the current Y position pointer on the page
                currYPos = textPosition.y;
 
                // Now position the print head horizontally on the page
 
                p = &positionCmndsBuff[bytesInBuff];        
                *p++ = ESCAPE;
                *p++ = 'F';
                Long2Dec((*hGlobals)->leftMargin + FixedToInt(textPosition.x), (char*)p);   // Convert left margin into ASCII and place it at the start of the scan line
                
                // Update the number of bytes in the buffer
                bytesInBuff += 6;
 
                // Send the positioning info to the printer
                anErr = Send_GXBufferData((char*)positionCmndsBuff, bytesInBuff, gxDontSplitBuffer);
                require(anErr == noErr, CantSendPositionCmnds);
            }
            
            // Now we send the text data to the printer
            anErr = WriteDraftChars((long **) (*hGlobals)->draftTable, (unsigned char*)theChars, numChars);
            require(anErr == noErr, CantWriteChars);
        }
    }   // for
 
    // Send one last CR to wrap the last line (if there was one)
    {
        char        c = 0x0D;
        
        anErr = Send_GXBufferData(&c, 1, gxNoBufferOptions);
    }
 
 
/******* Clean-up *******/
 
CantWriteChars:
CantSendPositionCmnds:
CantEmitLineFeeds:
CantSendCRCmnd:
CantGetTextAndPos:
CantSendFontCmnd:
    if (theChars != nil)
        DisposPtr(theChars);
                
    return(anErr);
    
} // PrintPageInDraftMode 
 
//<FF>
/* ------------------------------------------------------------------------------------ */
/*  SPECIFIC DRIVER UNIVERSAL OVERRIDES                                                 */
/* ------------------------------------------------------------------------------------ */
OSErr SD_Initialize (void) 
/*
    The SD_Initalize message is called when a new job is created.  The standard
    thing to do is to allocate and fill out your globals as you see fit.
*/
{
 
    SpecGlobalsHdl  hGlobals;
    OSErr           anErr;
        
    // we make our globals
    hGlobals = (SpecGlobalsHdl) NewHandleClear( sizeof(SpecGlobals) );
    anErr = MemError();
 
    // and we save them away
    SetMessageHandlerInstanceContext(hGlobals);
 
    // is everything okay?
    nrequire(anErr, MNewHandleClear);
    
    // Don't need to initialize because of the NewHandleCLEAR
    //(**hGlobals).draftTable = nil;
    //(**hGlobals).lineFeeds = 0;
    //(**hGlobals).packagingOptions = kNoPackagingOptions;
    //(**hGlobals).idleError        = noErr;
    //(**hGlobals).idleQuery        = false;
    //(**hGlobals).idleTimeout      = 0;
    //(**hGlobals).timeoutPending   = false;
    
    
    return(noErr);
    
    
/*-----EXCEPTION HANDLING------*/
 
 
MNewHandleClear:
    return(anErr);
    
} // SD_Initialize
 
 
//<FF>
/* ------------------------------------------------------------------------------------ */
OSErr SD_ShutDown(void) 
/*
    Shutdown is called when the job is done with.  A good thing to do is to get
    rid of any additional storage that is laying around.
*/
{
    // clean up our stuff
    SpecGlobalsHdl hGlobals = GetMessageHandlerInstanceContext();
 
    // get rid of the draft table (if we have one)
    if (hGlobals)
        DisposHandle((**hGlobals).draftTable);
    
    // we get rid of our storage
    DisposHandle((Handle) hGlobals);
    
    // clear out our globals - to avoid double disposes
    SetMessageHandlerInstanceContext(nil);
 
    return(noErr);
    
    
} // SD_ShutDown
 
//<FF>
/* ------------------------------------------------------------------------------------ */
OSErr   SD_DefaultPrinter(gxPrinter thePrinter)
/*
    This call is made to setup the default printer object.  The job of the
    specific driver is to add in any viewDevices that it wishes applications
    to be able to format specifically for.
*/
{
    OSErr           anErr;
    gxViewDevice    vd;
    gxJob           theJob = GXGetJob();
    
    // add the standard viewDevices first
    anErr = Forward_GXDefaultPrinter(thePrinter);
    nrequire(anErr, DefaultPrinter);
    
    // add a 144 b/w viewDevice
    vd = NewDeviceResolutionViewDevice();
    {
    gxSetColor      theColors[2];
    gxSetColor      *pColor;
    gxColorSet      theSet;
    
    pColor = &theColors[0];
    
    pColor->rgb.red = pColor->rgb.green = pColor->rgb.blue = 0xFFFF;
    
    pColor++;
    pColor->rgb.red = pColor->rgb.green = pColor->rgb.blue = 0x0000;
    
    theSet = GXNewColorSet(gxRGBSpace, 2, theColors);
    SetViewDeviceColorSet(vd, theSet);
    GXDisposeColorSet(theSet);
    }
        
    anErr = GXAddPrinterViewDevice(thePrinter, vd);
    nrequire(anErr, FailedAddBWViewDevice);
 
    
    // add a 144 color viewDevice with 8 colors in it
    //
    //  Color       Index       R           G           B
    //  white       0           0xFFFF      0xFFFF      0xFFFF      
    //  yellow      1           0xFFFF      0xFFFF      0x0000
    //  magenta     2           0xFFFF      0x0000      0xFFFF
    //  red         3           0xFFFF      0x0000      0x0000
    //  cyan        4           0x0000      0xFFFF      0xFFFF
    //  green       5           0x0000      0xFFFF      0x0000
    //  blue        6           0x0000      0x0000      0xFFFF
    //  black       7           0x0000      0x0000      0x0000
    
    if (PrinterHasColorRibbon(thePrinter))
        {
        gxSetColor      theColors[8];
        gxSetColor      *pColor;
        gxColorSet      theSet;
        short           idx;
 
        vd = NewDeviceResolutionViewDevice();
        
        pColor = &theColors[0];
        for (idx = 0; idx < 8; ++idx)
            {
            // default the color to black
            pColor->rgb.red = pColor->rgb.green = pColor->rgb.blue = 0x0000;
            
            // and give it componants to go along with this index
            if (idx & 0x04)
                pColor->rgb.red     = 0xFFFF;
            if (idx & 0x02)
                pColor->rgb.green   = 0xFFFF;
            if (idx & 0x01)
                pColor->rgb.blue    = 0xFFFF;
                
            // move on to the next color
            ++pColor;
            }
        
        theSet = GXNewColorSet(gxRGBSpace, 8, theColors);
        SetViewDeviceColorSet(vd, theSet);
        GXDisposeColorSet(theSet);
 
        anErr = GXAddPrinterViewDevice(thePrinter, vd);
        nrequire(anErr, FailedAddColorViewDevice);
        }
    
    /* Only if we are the output printer (not the formatting printer) */
    if (GXGetJobPrinter(theJob) == GXGetJobOutputPrinter(theJob)) {
        Collection          jobCollection = GXGetJobCollection(GXGetJob());
        Handle              jobQualitySettingsHdl;  
        gxQualityInfo       *qualitySettings;
        Ptr                 p;
        Str255              bestString, roughString;
 
        // read in our quality mode strings
        {
        short   curResFile = CurResFile();
        
        UseResFile(GXGetMessageHandlerResFile());
        
        GetIndString( bestString, kNewQualityID, kBestString);
        GetIndString( roughString, kNewQualityID, kRoughString);
        UseResFile(curResFile);
        }
        
        jobQualitySettingsHdl = NewHandle(0);
        anErr = MemError();
        nrequire(anErr, FailedNewHandle);
 
        anErr = GetCollectionItemHdl (  jobCollection,
                                        gxQualityTag,
                                        gxPrintingTagID,
                                        jobQualitySettingsHdl );
 
        if (anErr == noErr) 
            {   /* Check for proper structure -- count as not found if different */
            HLockHi(jobQualitySettingsHdl);
 
            qualitySettings = *((gxQualityInfo **) jobQualitySettingsHdl);
            p = qualitySettings->qualityNames;
 
            if (qualitySettings->disableQuality) 
                anErr = collectionItemNotFoundErr;
            else if (qualitySettings->qualityCount != 2)
                anErr = collectionItemNotFoundErr;
            else if (! IUEqualString((ConstStr255Param)p, bestString))
                anErr = collectionItemNotFoundErr;
            else if (! IUEqualString((ConstStr255Param)(p + p[0] + 1), roughString))
                anErr = collectionItemNotFoundErr;
 
            HUnlock(jobQualitySettingsHdl);
            }
 
        if (anErr == collectionItemNotFoundErr) 
            {
            Size            count;
 
            /* Create the proper quality item */
            SetHandleSize(jobQualitySettingsHdl,(sizeof(gxQualityInfo) + bestString[0] + roughString[0] + 2 ));
            anErr = MemError();
            nrequire( anErr, FailedSetHandleSize );
                
            qualitySettings = *((gxQualityInfo **) jobQualitySettingsHdl);
            
            qualitySettings->disableQuality = false;
            qualitySettings->defaultQuality = 1;
            qualitySettings->currentQuality = 1;
            qualitySettings->qualityCount = 2;
 
            count = bestString[0]+1;
            p = qualitySettings->qualityNames;
            BlockMove( bestString, p, count );
 
            p += count;
            BlockMove( roughString, p, roughString[0]+1 );
 
            /* Add the proper quality item */
            anErr = AddCollectionItemHdl (  jobCollection,
                                            gxQualityTag,
                                            gxPrintingTagID,
                                            jobQualitySettingsHdl );
 
            /* Make it vilatile by driver */
            if (anErr == noErr)
                (void) SetCollectionItemInfo(jobCollection, gxQualityTag, gxPrintingTagID, 0x0000FFFF, gxVolatileOutputDriverCategory);
 
            }
        
FailedSetHandleSize:
        DisposHandle(jobQualitySettingsHdl);
    }
FailedNewHandle:
    return(noErr);
    
    
    
// EXCEPTION HANDLING
FailedAddColorViewDevice:
FailedAddBWViewDevice:
    GXDisposeViewDevice(vd);
    
DefaultPrinter:
    return(anErr);
    
} // SD_DefaultPrinter
 
//<FF>
/* ------------------------------------------------------------------------------------ */
 
OSErr SD_DefaultFormat(gxFormat theFormat)
{
    OSErr               anErr;
    Handle              jobQualitySettingsHdl;  
    
    anErr = Forward_GXDefaultFormat(theFormat);
    
    // now, if the application has set up a special formatting mode, we need to update
    // the quality mode collection item (and any private ones we use)
    if (anErr == noErr)
        {
        gxPoint             dpiPoint;
        gxMapping           vdMapping;
        gxViewDevice        selectedDevice;
 
        dpiPoint.x = ff(72);
        dpiPoint.x = ff(72);
        selectedDevice = GXGetPrinterViewDevice(GXGetJobPrinter(GXGetFormatJob(theFormat)), 0);
        if (selectedDevice != GXGetPrinterViewDevice(GXGetJobPrinter(GXGetFormatJob(theFormat)), 1) )
            {
            GXGetViewDeviceMapping(selectedDevice, &vdMapping);
            MapPoints(&vdMapping, 1, &dpiPoint);
            
            {
            Collection          jobCollection = GXGetJobCollection(GXGetJob());
            gxQualityInfo       *qualitySettings;
    
            jobQualitySettingsHdl = NewHandle(0);
            anErr = MemError();
            nrequire(anErr, FailedNewHandle);
 
            anErr = GetCollectionItemHdl (  jobCollection,
                                            gxQualityTag,
                                            gxPrintingTagID,
                                            jobQualitySettingsHdl );
 
            nrequire(anErr, FailedGetCollectionItemHdl);
 
            qualitySettings = *((gxQualityInfo **) jobQualitySettingsHdl);
 
            qualitySettings->currentQuality = 
                (dpiPoint.y > ff(100)) ? (qualitySettings->qualityCount-1) : 0;
 
            anErr = AddCollectionItemHdl (  jobCollection,
                                            gxQualityTag,
                                            gxPrintingTagID,
                                            jobQualitySettingsHdl );
                                                 
            DisposHandle(jobQualitySettingsHdl);
            }
 
        
            if (anErr == noErr)
                {
                long    formatOptions;
                
                // turn off super-res
                formatOptions = 0;
                anErr = AddCollectionItem(GXGetFormatCollection(theFormat), 
                    DriverCreator, 0,
                    sizeof(formatOptions),
                    &formatOptions);
                }
            }
        }
 
FailedNewHandle:        
    return(anErr);
 
FailedGetCollectionItemHdl:
    DisposHandle(jobQualitySettingsHdl);
    return(anErr);
    
} // SD_DefaultFormat
 
//<FF>
/* ------------------------------------------------------------------------------------ */
OSErr SD_DefaultJob()
/*
    We override this message to add our default - highest res possible
*/
{
    OSErr   anErr;
    
    anErr = Forward_GXDefaultJob();
    if (anErr == noErr)
        {
        long        imagewriterOptions = kSuperRes;
        
        anErr = AddCollectionItem(GXGetJobCollection(GXGetJob()), 
                    DriverCreator,
                    0,
                    sizeof(imagewriterOptions),
                    &imagewriterOptions);
                    
        }
 
 
    return(anErr);
    
} // SD_DefaultJob
 
/* ------------------------------------------------------------------------------------ */
OSErr SD_OpenConnection(void)
/*
    The OpenConnection message is sent in order to open the connection to the device.
*/
{
    OSErr   anErr;
    SpecGlobalsHdl  hGlobals = (SpecGlobalsHdl)GetMessageHandlerInstanceContext();
    
    // how to process idle events
    (**hGlobals).idleError      = noErr;
    (**hGlobals).idleQuery      = false;
    (**hGlobals).idleTimeout    = 0;
    (**hGlobals).timeoutPending = false;
    
    // first, open the connection the standard way
    anErr = Forward_GXOpenConnection();
    nrequire(anErr, OpenConnection);
    
    // then, bring the configuration file up to date
    anErr = UpdateConfiguration();
    nrequire(anErr, UpdateConfiguration);
 
    return(noErr);
    
// EXCEPTION HANDLING
UpdateConfiguration:
    GXCleanupOpenConnection();
    
OpenConnection:
 
    return(anErr);
    
} // SD_OpenConnection
 
/* ------------------------------------------------------------------------------------ */
OSErr SD_CloseConnection(void)
{
    unsigned short  statusReturn;
    OSErr           anErr, anErr2;
    SpecGlobalsHdl  hGlobals = (SpecGlobalsHdl)GetMessageHandlerInstanceContext();
    ResType         commType = (**hGlobals).commType;
 
    if (commType == 'PPTL')
        {
        // flush out all data so that we can query the printer properly one last time
        anErr = Send_GXWriteData(nil, 0);
        nrequire(anErr, Send_GXWriteData1);
        
        // for PAP: bring the configuration file up to date & check that the printer is online
        anErr2 = UpdateConfiguration();
        if (anErr == noErr) anErr = anErr2;
        }
    else
        {
        // for serial: flush out all data so that we can query the printer properly one last time
        anErr = Send_GXWriteData(nil, 0);
        nrequire(anErr, Send_GXWriteData2);
 
        // one last time check up on printer status 
        if ((**hGlobals).isImageWriterII)
            {
            anErr2 = FetchStatusString(&statusReturn, false, true);
            if (anErr == noErr) anErr = anErr2;
            }
        }
    
Send_GXWriteData2:
Send_GXWriteData1:
FetchStatusString:
    // close the connection the standard way
    anErr2 = Forward_GXCloseConnection();
    if (anErr == noErr) anErr = anErr2;
        
    return(anErr);
    
} // SD_CloseConnection
 
/* ------------------------------------------------------------------------------------ */
OSErr SD_JobIdle()
{
    OSErr           anErr = noErr;
    SpecGlobalsHdl  hGlobals = (SpecGlobalsHdl)GetMessageHandlerInstanceContext();
    SpecGlobalsPtr  pGlobals;
    
    pGlobals = *hGlobals;
    if ( (pGlobals->idleQuery) && (pGlobals->commType == 'PPTL') )
        {
        unsigned short  statusReturn;
 
        pGlobals->idleQuery = false;
        
        anErr = FetchStatusString(&statusReturn, true, true);
        nrequire(anErr, FetchStatusString);
 
        anErr = Forward_GXJobIdle();
 
        // EXCEPTION HANDLING
        FetchStatusString:
            pGlobals = *hGlobals;
            pGlobals->idleQuery = true;
        }
    else    
        anErr = Forward_GXJobIdle();
    
    // if we continue looping here too long during the initial query -- give the user
    // a chance to bail or correct the problem
    pGlobals = *hGlobals;
    if ( (!(pGlobals->timeoutPending)) && (pGlobals->idleTimeout != 0) )
        {
        if (TickCount() > pGlobals->idleTimeout)
            {
            gxStatusRecord      theStat;
            gxStatusRecord      *pStat = &theStat;
            
            pStat->statusOwner  = 'drvr';
            pStat->statResId    = kDriverStatus;        
            pStat->statResIndex = kCheckOnline;         
            pStat->bufferLen    = 0;
            pStat->dialogResult = 0;    // ORIGINALLY SET TO ===> nil;
                        
            // tell the user to check the printer
            pGlobals->timeoutPending = true;
            (void) GXAlertTheUser(pStat);
            pGlobals = *hGlobals;
            pGlobals->timeoutPending = false;
                
            // based on the user's response cancel
            switch (pStat->dialogResult)
                {
                case ok:
                    pGlobals->idleTimeout = TickCount() + kQueryTimeout;
                    break;
                    
                case cancel:
                    pGlobals->idleError = gxPrUserAbortErr;
                    break;
                    
                case 3:
                    pGlobals->idleError = kPutJobOnHoldErr;
                    break;
                }
 
            // display "sending data to the printer" message
            if (anErr == noErr)
                anErr = GXReportStatus(kDriverStatus, kSendingData);
            }
        }
        
    if (anErr == noErr)
        anErr = pGlobals->idleError;
        
    return(anErr);
    
} // SD_JobIdle
 
/* ------------------------------------------------------------------------------------ */
OSErr SD_FreeBuffer(gxPrintingBuffer * theBuffer)
{
    OSErr           anErr = noErr;
    OSErr           firstError = noErr;
    SpecGlobalsHdl  hGlobals = (SpecGlobalsHdl)GetMessageHandlerInstanceContext();
    
 
    if ((**hGlobals).commType == 'PPTL')
        {
        unsigned short  statusReturn;
 
        anErr = FetchStatusString(&statusReturn, true, true);
        nrequire(anErr, FetchStatusString);
        }
        
    do
        {
        // we can idle query now if we need to  
        (**hGlobals).idleQuery = true;
 
        // try to send the buffer again
        anErr = Forward_GXFreeBuffer(theBuffer);
        if (firstError == noErr)
            firstError = anErr;
    
        (**hGlobals).idleQuery = false;
 
        // timeout dialog!
        if (anErr == gxAioTimeout)
            {
            gxStatusRecord      theStat;
            gxStatusRecord      *pStat = &theStat;
            
            pStat->statusOwner  = 'drvr';
            pStat->statResId    = kDriverStatus;        
            pStat->statResIndex = kCheckOnline;         
            pStat->bufferLen    = 0;
            pStat->dialogResult = 0;    // ORIGINALLY SET TO ====> nil;
                        
            // tell the user to check the printer
            (void) GXAlertTheUser(pStat);
                
            // based on the user's response cancel
            switch (pStat->dialogResult)
                {
                case ok:
                    anErr = gxAioTimeout;
                    break;
                    
                case cancel:
                    anErr = gxPrUserAbortErr;
                    break;
                    
                case 3:
                    anErr = kPutJobOnHoldErr;
                    break;
                }
            }
            
        } while (anErr == gxAioTimeout);
    
    // put down the timeout dialog, if we ever put one up
    if (firstError != noErr)
        (void) GXReportStatus(kDriverStatus, kSendingData);
 
    // error to return from next idle
    (**hGlobals).idleError = anErr;
    
FetchStatusString:      
    return(anErr);
    
} // SD_FreeBuffer
 
/* ------------------------------------------------------------------------------------ */
OSErr SD_DumpBuffer(gxPrintingBuffer * theBuffer)
{
    OSErr   anErr;
    SpecGlobalsHdl  hGlobals = (SpecGlobalsHdl)GetMessageHandlerInstanceContext();
        
    if ((**hGlobals).commType == 'PPTL')
        {
        unsigned short  statusReturn;
        anErr = FetchStatusString(&statusReturn, true, true);
        nrequire(anErr, FetchStatusString);
        }
    anErr = Forward_GXDumpBuffer(theBuffer);
 
FetchStatusString:      
    return(anErr);
    
} // SD_DumpBuffer
 
/* ------------------------------------------------------------------------------------ */
OSErr SD_StartSendPage(gxFormat pageFormat)
/*
    The StartSendPage message is sent just before the page begins to be rendered.
    
    Note that the StartSendPage message will not be sent until imaging/communication
    time, so that user interaction alerts are considered okay here
*/
{
    OSErr                       anErr = noErr;
    gxJob                       theJob = GXGetJob();
    Collection                  jobCollection;
    gxPaperType                 thePaperType;
    gxTrayFeedInfo              trayFeedInfo;
    long                        itemSize = sizeof(trayFeedInfo);
    ResType                     commType;
    unsigned short              statusReturn;
    
    jobCollection = GXGetJobCollection(theJob);
    
    // cache communications type
    commType = (**(SpecGlobalsHdl)GetMessageHandlerInstanceContext()).commType;
    if (commType == 'PPTL')
        {
        anErr = FetchStatusString(&statusReturn, true, true);
        nrequire(anErr, FetchStatusString);
        }
    else
        statusReturn = 0;
        
    // check to see if this particular page is to be manually fed
    anErr = GetCollectionItem(jobCollection, gxTrayFeedTag, gxPrintingTagID, &itemSize, &trayFeedInfo);
    nrequire(anErr, FailedGetCollectionItem);
            
    // manual feed or out of paper?  Time to ask the user what to do
    if  (   trayFeedInfo.manualFeedThisPage
        ||  ( ( (statusReturn & kOutOfPaperMask) != 0 ) )
        )
        {
        // Wait for all IO to complete, so that we can correctly tell the user what to do.
        // Since the WriteData message makes sure all data is flushed before performing the
        // IO, this call insures that pending IO is complete.
        anErr = Send_GXWriteData(nil, 0);
        nrequire(anErr, FlushAllData);
 
 
        // then, conduct the alert with the user
        {
        gxStatusRecord      *pStat;
        
        // make a status record containing the request to the user - note that 
        // we have to make room for ManualFeedRecord OR OutOfPaperRecord, but manual is bigger
        pStat = (gxStatusRecord *)NewPtrClear(sizeof(gxStatusRecord)  + sizeof(gxManualFeedRecord));
        anErr = MemError();
        nrequire(anErr, NewPtrClear);
                
        pStat->statusOwner  = 'univ';
        pStat->statResId    = gxUnivAlertStatusResourceId;  // we use the built-in status for this
        pStat->dialogResult = 0;    // ORIGINALLY SET TO =====> nil;
        
        if (trayFeedInfo.manualFeedThisPage)
            {
            gxManualFeedRecord  *pFeed;
            
            pStat->statResIndex = gxUnivManualFeedIndex;            // status meaning "manual feed alert"
            pStat->bufferLen    = sizeof(gxManualFeedRecord);
            pFeed = (gxManualFeedRecord*)&pStat->statusBuffer;
        
            // we can switch to autofeed if we want - and tell the user what kind of paper to load in
            pFeed->canAutoFeed = true;
            GXGetPaperTypeName(thePaperType = GXGetFormatPaperType(pageFormat), pFeed->paperTypeName);
            }
        else
            {
            gxOutOfPaperRecord  *pOut;
            
            pStat->statResIndex = gxUnivOutOfPaperIndex;            // status meaning "manual feed alert"
            pStat->bufferLen    = sizeof(gxOutOfPaperRecord);
            
            pOut = (gxOutOfPaperRecord*)&pStat->statusBuffer;
            GXGetPaperTypeName(GXGetFormatPaperType(pageFormat), pOut->paperTypeName);
            }
            
        // keep sending the user the alert until either
        //  a) the problem resolves itself
        //  b) the user responds via the dialog
        //  c) some other (fatal) error happens
        do
            {
            
            // tell the user
            anErr = GXAlertTheUser(pStat);
            
            // if the paper got suddenly loaded, do an OK
            if (commType == 'PPTL')
                {
                (void) FetchStatusString(&statusReturn, true, true);
                if ((statusReturn & kOutOfPaperMask) == 0)
                    {
                    pStat->dialogResult = ok;
                    anErr = noErr;
                    }
                }
                
            } while ((anErr == noErr) && (pStat->dialogResult == 0));   // ORIGINALLY SET TO ==> (pStat->dialogResult == nil)
 
        // based on the user's response, continue, cancel, or switch to auto feed
        switch ( pStat->dialogResult )
            {
            case ok:
                // paper is loaded
                break;
                
            case cancel:
                // user wishes to stop the printing process
                anErr = gxPrUserAbortErr;
                break;
                
            case gxAutoFeedButtonId:
                // do rest of job with auto feed
                
                {
                gxPaperFeedInfo paperFeed;
                
                /* Update for job */
                paperFeed.autoFeed = true;
                (void) AddCollectionItem(jobCollection, gxPaperFeedTag, gxPrintingTagID, sizeof(paperFeed), &paperFeed);
                }
                
                /* Update as it may be reused */
                trayFeedInfo.manualFeedThisPage = false;    /* Other trayInfo fields are still valid */
                (void) AddCollectionItem(jobCollection, gxTrayFeedTag, gxPrintingTagID, sizeof(trayFeedInfo), &trayFeedInfo);
                
                /* Can pass paper type reference as this IS device communication time */
                anErr = Send_GXCheckStatus((Ptr) &thePaperType, sizeof(thePaperType), 0, 'univ');
                
                /* No need to reset tray from gxTrayFeedInfo.feedTrayIndex as there is only one tray! */
                break;
                
            } // switch
            
            
        // done with the status now
        DisposPtr((Ptr) pStat);
        }
            
        } // if manual feed job
        
    // display "sending data to the printer" message
    if (anErr == noErr)
        anErr = GXReportStatus(kDriverStatus, kSendingData);
        
    nrequire(anErr, FailedWaitForPaper);
 
    // continue with the standard starting of the page
    anErr = Forward_GXStartSendPage(pageFormat);
        
        
// FALL THROUGH AND HANDLE EXCEPTIONS
 
FailedWaitForPaper:
NewPtrClear:
FlushAllData:
FailedGetCollectionItem:
FetchStatusString:
    return(anErr);
    
} // SD_StartSendPage
 
/* ------------------------------------------------------------------------------------ */
OSErr SD_FinishSendPage()
{
    OSErr       anErr = noErr;
    Str63       formLength;         // should be more than big enough for form skipping
    char        len = 0;
 
    // we may have issued line feeds RIGHT up to the end of the page.  If
    // we do that and then issue a form feed, we'll kick out a blank page.
    // to avoid that, we back up a tad and then let the normal form feed
    // go through.  Option 2 would be to track each and every motion control
    // we send to the printer -- but that's more work than this.  In addition,
    // this method makes sure we are synced up exactly to the hardware
    formLength[len++] = ESCAPE;
    formLength[len++] = 'T';
    formLength[len++] = '0';
    formLength[len++] = '1';
    formLength[len++] = ESCAPE;
    formLength[len++] = 'r';
    formLength[len++] = 0x0A;
    
    // reset to forward motion for the form feed
    formLength[len++] = ESCAPE;
    formLength[len++] = 'f';
    
    anErr = Send_GXBufferData((char*)&formLength[0], len, gxNoBufferOptions );
    nrequire(anErr, Send_GXBufferData);
    
    // Default implementation provides the actual form feed
    anErr = Forward_GXFinishSendPage();
 
// FALL THROUGH EXCEPTION HANDLING
Send_GXBufferData:
 
    return(anErr);
    
} // SD_FinishSendPage
 
/* ------------------------------------------------------------------------------------ */
OSErr SD_JobFormatDialog(gxDialogResult*    theResult)
/*
    This message is sent in response to the user's request to put up a formatting dialog
*/
{
    OSErr                   anErr;
    gxJobFormatModeTableHdl theJobFormatModeList;
    long                    i;
    gxJob                   theJob = GXGetJob();
    
    // set up the JobFormatMode information
    
    anErr = GXGetAvailableJobFormatModes(&theJobFormatModeList);
    if ((!anErr) && (theJobFormatModeList))
        {
        for (i = 0; i <= (*theJobFormatModeList)->numModes - 1; ++i) 
            {
            if ((*theJobFormatModeList)->modes[i] == gxTextJobFormatMode) 
                {
                GXSetPreferredJobFormatMode(gxTextJobFormatMode, false);
                break;
                }
            }
        DisposHandle((Handle)theJobFormatModeList);
        }
        
    // do the normal dialogs after handling the job format mode stuff
    return(Forward_GXJobDefaultFormatDialog(theResult));
    
} // SD_JobFormatModeQuery
 
/* ------------------------------------------------------------------------------------ */
OSErr SD_JobFormatModeQuery(    gxQueryType     theQuery,
                                void*           srcData,
                                void*           dstData)
/*
    This message is sent to find out information about the current job format mode.
*/
{
    OSErr       anErr = noErr;
    Handle      theFonts;
    Handle      theStyles;
    
    // What type of query is being requested?
    switch(theQuery) 
    {
        case gxSetStyleJobFormatCommonStyleQuery:
        {
            char                *pStyleName;
 
            // Fetch the list of supported styles
            
            anErr = Send_GXFetchTaggedDriverData('STR#', kFormatModeStylesID, &theStyles);
            require(anErr == noErr, FailedToLoadStyles1);
            
            HNoPurge(theStyles);
            HLock(theStyles);
            
            // Determine which style is being referenced and set the corresponding style (only 2 styles
            // are currently supported)
            
            if (**((short **) theStyles) == 2)  //  T => We have the correct number of styles
            {
                char        whichFace = 0;
                
                pStyleName = ((char *) *theStyles) + sizeof(short); 
                
                if ( IUCompString((ConstStr255Param)pStyleName, (ConstStr255Param)srcData) == 0 )   //  T => They want bold face
                {
                    whichFace = bold;
                }
                else
                {
                    // Point to the next name in the list
                    pStyleName += *pStyleName + 1;
 
                    if ( IUCompString((ConstStr255Param)pStyleName, (ConstStr255Param)srcData) == 0 )   //  T => They want underline face
                    {
                        whichFace = underline;
                    }
                }
 
                //  If the client specified a valid face, set it now
                if (whichFace != 0)
                {
                    SetStyleCommonFace((gxStyle) dstData, GetStyleCommonFace((gxStyle) dstData) | whichFace);
                }
            }
            // else - something is wrong with our resource
            
            // Dump the temporary handle
            DisposHandle(theStyles);
            
            break;
        }
            
        case gxGetJobFormatFontCommonStylesQuery:
        {
            short               numStyles;
            short               i;
            char                *pStyleName;
 
            // Fetch the list of supported styles
            
            anErr = Send_GXFetchTaggedDriverData('STR#', kFormatModeStylesID, &theStyles);
            require(anErr == noErr, FailedToLoadStyles2);
            
            HNoPurge(theStyles);
            HLock(theStyles);
            
            // Determine the number of styles in the list
            numStyles = **((short **) theStyles);
 
            if (*(Handle *)dstData != nil)  //  T => Resize the handle to hold info. on the styles
                SetHandleSize(*(Handle *)dstData, sizeof(gxStyleNameTable) + ((numStyles - 1) * sizeof(Str255)));
            else
                *(Handle *)dstData = NewHandle(sizeof(gxStyleNameTable) + ((numStyles - 1) * sizeof(Str255)));
            
            anErr = MemError();
            require(anErr == noErr, StyleTableResizeFailed);
            
            // Now extract the name of each of the supported fonts
            
            for (i = 1, pStyleName = ((char *) *theStyles) + sizeof(short); i <= numStyles; ++i, pStyleName += *pStyleName + 1)
            {
                BlockMove(pStyleName, (*((gxStyleNameTableHdl) *(Handle *)dstData))->styleNames[i - 1], *pStyleName + 1);
            }
            
            (*((gxStyleNameTableHdl) *(Handle *)dstData))->numStyleNames = numStyles;
            
            // Dump the temporary handle
            DisposHandle(theStyles);
            
            break;
        }
            
        case gxGetJobFormatLineConstraintQuery:         //  This type of query is not supported
            if (*(Handle *)dstData != nil)
                SetHandleSize(*(Handle *)dstData, 0);       // Don't return any data
            break;
            
        case gxGetJobFormatFontsQuery:
        {
            short               numFonts;
            short               i;
            char                *pFontName;
 
            // Fetch the list of supported fonts
            
            anErr = Send_GXFetchTaggedDriverData('STR#', kFormatModeFontsID, &theFonts);
            require(anErr == noErr, FailedToLoadFonts);
            
            HNoPurge(theFonts);
            HLock(theFonts);
            
            // Determine the number of fonts in the list
            numFonts = **((short **) theFonts);
 
            if (*(Handle *)dstData != nil)  //  T => Resize the handle to hold info. on the fonts
                SetHandleSize(*(Handle *)dstData, sizeof(gxFontTable) + ((numFonts - 1) * sizeof(gxFont)));
            else
                *(Handle *)dstData = NewHandle(sizeof(gxFontTable) + ((numFonts - 1) * sizeof(gxFont)));
            
            anErr = MemError();
            require(anErr == noErr, FontTableResizeFailed);
            
            // Now generate a reference to each of the supported fonts
            
            for (i = 1, pFontName = ((char *) *theFonts) + sizeof(short); i <= numFonts; ++i, pFontName += *pFontName + 1)
            {
                gxFont          thisFont;
                gxFontTable     *pFontTable;
            
                //  ORIGINAL CODE
                //  thisFont = FindPNameFont(gxFullFontName, (ConstStr255Param)pFontName);
                // END OF ORIGINAL CODE
                
                // CODE ADDED IN FROM GX FONTLIBRARY.C AS THINK C WON'T COMPILE LIBRARY
 
                GXFindFonts(0, gxFullFontName,
                            gxMacintoshPlatform,
                            gxRomanScript,
                            gxEnglishLanguage,
                            pFontName[0], (ConstStr255Param)pFontName, 1, 1, &thisFont);
    
                // ENDCODE ADDED IN FROM LIBRARY
                
                pFontTable = *((gxFontTableHdl) *(Handle *)dstData);
                pFontTable->fonts[i - 1] = thisFont;
            }
            
            (*((gxFontTableHdl) *(Handle *)dstData))->numFonts = numFonts;
            
            // Dump the temporary handle
            DisposHandle(theFonts);
 
            break;
        }
            
        case gxGetJobFormatFontConstraintQuery:
        {
            gxPositionConstraintTable       *pPositionTable;
            
            if ( *(Handle *)dstData != nil) //  T => Resize the handle to hold info. on position constraints
                SetHandleSize(*(Handle *)dstData, sizeof(gxPositionConstraintTable) + sizeof(Fixed));
            else
                *(Handle *)dstData = NewHandle( sizeof(gxPositionConstraintTable) + sizeof(Fixed) );
            
            pPositionTable = *((gxPositionConstraintTableHdl) *(Handle *)dstData);
            
            pPositionTable->phase.x     = 0;                //  Start at the top left corner of the page
            pPositionTable->phase.y     = 0;
            pPositionTable->offset.x    = ff(12);       // Indent from the top left by a six lines per inch margin
            pPositionTable->offset.y    = ff(12);       
            pPositionTable->numSizes    = 2;                // Two font sizes supported
            pPositionTable->sizes[0]    = ff(10);       // 10 pitch
            pPositionTable->sizes[1]    = ff(12);       // 12 pitch
            
            break;
        }
    } // switch
    
    return(anErr);
    
 
/******* Clean-up *******/
 
StyleTableResizeFailed:
    DisposHandle((Handle) theStyles);
    return(anErr);
 
FontTableResizeFailed:
    DisposHandle((Handle) theFonts);
 
FailedToLoadStyles1:
FailedToLoadStyles2:
FailedToLoadFonts:
    return(anErr);
    
} // SD_JobFormatModeQuery
 
//<FF>
/* ------------------------------------------------------------------------------------ */
OSErr SD_SetupImageData(
    gxRasterImageDataHdl hImageData)        // raster image data stuff
/*
    This message is called to setup the constant data used for imaging the entire job.
*/
{
 
    SpecGlobalsHdl              hGlobals = GetMessageHandlerInstanceContext();
    OSErr                       anErr;
    gxRasterImageDataPtr        pImageData;
    Boolean                     isJobNotFinalQuality;
    Boolean                     isTextJobFormatMode;
    long                        imagewriterOptions;
    short                       count;
    Handle                      draftTable;
    
    CMYKRenderCollection planes;
    long                 itemSize = sizeof(CMYKRenderCollection);
    Collection           jobCollection;
    gxJob                theJob;
    
    gxSetColor          theColors[9];
    gxColorSet          theSet;
    short               idx;
    gxPlaneSetupRec     *pOnePlane;
 
// do the default setup
    anErr = Forward_GXSetupImageData(hImageData);
    nrequire(anErr, Forward_GXSetupImageData);
    
// test for 'final' quality mode
    isJobNotFinalQuality = !JobIsBest(&imagewriterOptions);
    
// test for textJobFormatMode
    isTextJobFormatMode = ( GXGetJobFormatMode( GXGetJob() ) == gxTextJobFormatMode);
            
// if the job is not final quality or using textJobFormatMode,
//               downgrade the imaging data to our lower quality
    if (isJobNotFinalQuality  ||  isTextJobFormatMode)
    {
//
// ROUGH OR TEXT MODE
//      
 
    // dereference for size and speed   
        pImageData = *hImageData;
                
    // image at 80 or 72 dpi
        if (imagewriterOptions & kSuperRes)
            pImageData->hImageRes = ff(80);
        else
            pImageData->hImageRes = ff(72);
        pImageData->vImageRes = ff(72);
        
    // textJobFormatMode loads up the draft table, else setup halftones
        if (isTextJobFormatMode)
        {
 
            anErr = Send_GXFetchTaggedDriverData('idft', gxPrintingDriverBaseID, &draftTable);
            nrequire(anErr, FailedToLoadDraftTable);
            
        // store away the draft table
            (**hGlobals).draftTable = draftTable;
 
        // Download something?  
            anErr = Send_GXFetchTaggedDriverData('idft', gxPrintingDriverBaseID+1, &draftTable);
            if (anErr == resNotFound)
            {
                draftTable = nil;
                anErr = noErr;
            }
            nrequire(anErr, GetDownloadTable);  
        
            if (draftTable)
            {
                HLock(draftTable);
                anErr = Send_GXBufferData(*draftTable, GetHandleSize(draftTable), gxDontSplitBuffer);
                DisposHandle(draftTable);
                nrequire(anErr, SendDownloadTable);
            }
        }
        else
        {           
    // use dither level that will look better at 72 dpi 
    // resolution than our default values (MAYBE: 4 is the default now anyway)
            pImageData->theSetup.planeSetup[0].planeHalftone.method = 4;
            
    // of course, turn off color matching when in non-final mode!
            pImageData->theSetup.planeSetup[0].planeProfile = nil;
        }
            
        if (isJobNotFinalQuality)
        {
            if (imagewriterOptions & kSuperRes)
            {
        // use bidirectional instead of unidirectional
        // and also <esc>N instead of <esc>p for quality mode
 
                pImageData->packageControls.startPageStringID = gxPrintingDriverBaseID+3;
            }
            else
            {
        // use bidirectional instead of unidirectional
        // and also <esc>n instead of <esc>p for quality mode
        
                pImageData->packageControls.startPageStringID = gxPrintingDriverBaseID+2;
            }
        }
        
    // packaging data
        pImageData->packagingInfo.headHeight        = 8;        // 8 pins (instead of 16)
        pImageData->packagingInfo.numberPasses      = 1;        // in 1 head pass (instead of 2)
        pImageData->packagingInfo.passOffset        = 0;        // with no space between passes
    }
    else
    {
//
// FINAL QUALITY
//
 
    // dereference for size and speed   
        pImageData = *hImageData;
                
    // image at 160 or 144 dpi
        if (imagewriterOptions & kSuperRes)
        {
            pImageData->hImageRes = ff(160);
            pImageData->packageControls.startPageStringID = gxPrintingDriverBaseID+1;
        }
        else
        {
            pImageData->hImageRes = ff(144);
            pImageData->packageControls.startPageStringID = gxPrintingDriverBaseID+0;
        }
    }
    
// not a color ribbon?  Setup for black and white - do a B/W halftone rather than a dither
    if (!PrinterHasColorRibbon(GXGetJobOutputPrinter(GXGetJob())))
    {
// dereference for size and speed   
        pImageData = *hImageData;
 
// one plane, no color flags, move the halftone info up into correct position
        pImageData->theSetup.planes = 1;
        pImageData->theSetup.depth = 1;
        pImageData->packagingInfo.colorPasses = 1;
        pImageData->packagingInfo.packageOptions = 0;
        pImageData->theSetup.planeSetup[0].planeSpace = gxNoSpace;
        pImageData->theSetup.planeSetup[0].planeSet = nil;
        pImageData->theSetup.planeSetup[0].planeProfile = nil;
        pImageData->theSetup.planeSetup[0].planeOptions = gxDefaultOffscreen;
        pImageData->theSetup.planeSetup[0].planeHalftone.method = gxRoundDot;
        pImageData->theSetup.planeSetup[0].planeHalftone.tintSpace = gxRGBSpace;
    }
    else
    {
// It has a color ribon, so if it has a render options collection, then set up the options
// approriately.
 
// First, see if the job has render options
 
        theJob = GXGetJob();
 
        jobCollection = GXGetJobCollection(theJob);
 
        if (!GetCollectionItem( jobCollection, 
                                kCMYKRenderCollectionType, kCMYKRenderCollectionID, 
                                &itemSize, &planes))
        {
 
            pImageData = *hImageData;               
    
            if (planes.renderMode == kDitherIt)
            {
//
// Dither setup
//
    // Create a color set
                for (idx = 0; idx < 9; ++idx)
                    {
                // default the color to black
                    theColors[idx].rgba.red
                        = theColors[idx].rgba.green
                        = theColors[idx].rgba.blue
                        = theColors[idx].rgba.alpha = 0x0000;
                    
                // and give it componants to go along with this index
                    if (   (idx == 0)
                        || (idx == 1)
                        || (idx == 2)
                        || (idx == 3)
                        || (idx == 7))
                        theColors[idx].rgba.red     = 0xFFFF;
                    if (   (idx == 0)
                        || (idx == 1)
                        || (idx == 4)
                        || (idx == 5)
                        || (idx == 7))
                        theColors[idx].rgba.green   = 0xFFFF;
                    if (   (idx == 0)
                        || (idx == 2)
                        || (idx == 4)
                        || (idx == 6)
                        || (idx == 7))
                        theColors[idx].rgba.blue    = 0xFFFF;
                        
                    }
 
                theSet = GXNewColorSet(gxRGBSpace, 9, theColors);
 
// Step through each plane, setting up the appropriate values
                for (count = 0; count < 4; ++count)
                {
        // dereference for size and speed           
                    pOnePlane = &(pImageData->theSetup.planeSetup[count]);
                    
        // Set up the options for dithering
                    pOnePlane->planeOptions = gxDontSetHalftone + gxDotTypeIsDitherLevel;
 
        // Angle and frequency are not used when dithering
                    pOnePlane->planeHalftone.angle     = 0;
                    pOnePlane->planeHalftone.frequency = 0;
                    
        // When the planeOption's gxDotTypeIsDitherLevel flag is set, then planeHalftone.method
        // is used for the dither level
        
                    pOnePlane->planeHalftone.method  = planes.ditherLevel;
        
        // Set default for luminance
                    pOnePlane->planeHalftone.tinting = gxLuminanceTint;
    
        // Set up the dot color
                    pOnePlane->planeHalftone.dotColor.space              = gxRGBSpace;
                    pOnePlane->planeHalftone.dotColor.profile            = nil;
                
                // default each plane to 0
                    pOnePlane->planeHalftone.dotColor.element.rgba.red
                            = pOnePlane->planeHalftone.dotColor.element.rgba.green
                            = pOnePlane->planeHalftone.dotColor.element.rgba.blue
                            = pOnePlane->planeHalftone.dotColor.element.rgba.alpha
                            = 0;
 
                    if (   (count == 0)
                        || (count == 1))
                        pOnePlane->planeHalftone.dotColor.element.rgba.red   = 0xFFFF;
                    if (   (count == 0)
                        || (count == 2))
                        pOnePlane->planeHalftone.dotColor.element.rgba.green   = 0xFFFF;
                    if (   (count == 2)
                        || (count == 3))
                        pOnePlane->planeHalftone.dotColor.element.rgba.blue   = 0xFFFF;
    
        // Set up the background dot color
                    pOnePlane->planeHalftone.backgroundColor.space              = gxRGBSpace;
                    pOnePlane->planeHalftone.backgroundColor.profile            = nil;
                    pOnePlane->planeHalftone.backgroundColor.element.rgba.red
                        = pOnePlane->planeHalftone.backgroundColor.element.rgba.green
                        = pOnePlane->planeHalftone.backgroundColor.element.rgba.blue
                        = pOnePlane->planeHalftone.backgroundColor.element.rgba.alpha
                        = 0xFFFF;
    
        // Set up for an RGB tint space
                    pOnePlane->planeHalftone.tintSpace = gxRGBSpace;
 
        // And an indexed plane space
                    pOnePlane->planeSpace    = gxIndexedSpace;
 
        // Set up the plane to the same as the one we just created
                    pOnePlane->planeSet     = theSet;
 
        // no color profile so set to nil
                    pOnePlane->planeProfile = nil;
                }
            }
            else
            {
//
// Half-Tone setup
//
                for (count = 0; count < 4; ++count)
                {
        // dereference for size and speed           
                    pOnePlane = &(pImageData->theSetup.planeSetup[count]);
    
        // Set up the options for halftonning...gxDefaultOffscreen
                    pOnePlane->planeOptions = gxDefaultOffscreen;
 
        // Set this dot type from the collection
                    pOnePlane->planeHalftone.method    = planes.dotType;
 
        // Set this planes angle and frequency from the collection
        // and the appropriate tint for this plane
                    switch (count+1) {
                        case (kYellowPass) : 
                            pOnePlane->planeHalftone.angle     = planes.angles   [kRenderOptsYellow];
                            pOnePlane->planeHalftone.frequency = planes.frequency[kRenderOptsYellow];
                            pOnePlane->planeHalftone.tinting   = gxComponent3Tint;
                            break;
                        case (kMagentaPass) : 
                            pOnePlane->planeHalftone.angle     = planes.angles   [kRenderOptsMagenta];
                            pOnePlane->planeHalftone.frequency = planes.frequency[kRenderOptsMagenta];
                            pOnePlane->planeHalftone.tinting   = gxComponent2Tint;
                            break;
                        case (kCyanPass) : 
                            pOnePlane->planeHalftone.angle     = planes.angles   [kRenderOptsCyan];
                            pOnePlane->planeHalftone.frequency = planes.frequency[kRenderOptsCyan];
                            pOnePlane->planeHalftone.tinting   = gxComponent1Tint;
                            break;
                        case (kBlackPass) : 
                            pOnePlane->planeHalftone.angle     = planes.angles   [kRenderOptsBlack];
                            pOnePlane->planeHalftone.frequency = planes.frequency[kRenderOptsBlack];
                            pOnePlane->planeHalftone.tinting   = gxComponent4Tint;
                            break;
                    }
 
 
        // Set up the dot color to black
                    pOnePlane->planeHalftone.dotColor.space              = gxRGBSpace;
                    pOnePlane->planeHalftone.dotColor.profile            = nil;
                    pOnePlane->planeHalftone.dotColor.element.rgba.red
                        = pOnePlane->planeHalftone.dotColor.element.rgba.green
                        = pOnePlane->planeHalftone.dotColor.element.rgba.blue
                        = pOnePlane->planeHalftone.dotColor.element.rgba.alpha
                        = 0;
    
        // Set up the background dot color to white
                    pOnePlane->planeHalftone.backgroundColor.space              = gxRGBSpace;
                    pOnePlane->planeHalftone.backgroundColor.profile            = nil;
                    pOnePlane->planeHalftone.backgroundColor.element.rgba.red
                        = pOnePlane->planeHalftone.backgroundColor.element.rgba.green
                        = pOnePlane->planeHalftone.backgroundColor.element.rgba.blue
                        = pOnePlane->planeHalftone.backgroundColor.element.rgba.alpha
                        = 0xFFFF;
    
        // Set up for an CMYK tint space
                    pOnePlane->planeHalftone.tintSpace = gxCMYKSpace;
 
        // No explicit color space
                    pOnePlane->planeSpace   = gxNoSpace;
        
        // No color set
                    pOnePlane->planeSet     = nil;
 
        // No profile specified
                    pOnePlane->planeProfile = nil;
                }
            }
 
        }
 
    }
 
    return(noErr);
    
// EXCEPTION HANDLING
SendDownloadTable:
GetDownloadTable:
    DisposHandle((**hGlobals).draftTable);
    (**hGlobals).draftTable = nil;
    
FailedToLoadDraftTable:
Forward_GXSetupImageData:
    return(anErr);
    
} // SD_SetupImageData
 
/* ------------------------------------------------------------------------------------ */
OSErr SD_FetchDriverData(
    ResType         theType,
    short           theID,
    Handle*         theData)
{
 
    OSErr   anErr;
    
    anErr = Forward_GXFetchTaggedDriverData(theType, theID, theData);
    
    // do the translation at the proper DPI by modifying the old API
    // customization resource
    if ( (anErr   == noErr)    &&               // got the resource okay
         (theType == 'cust')   &&               // it was a customization resource 
         (theID   == -8192)   )                 // with the old API id
        {
        long imagewriterOptions;
        
        if (!JobIsBest(&imagewriterOptions))
            {
            **((short**)theData)   = 72;
            **((short**)theData+1) = 72;
            }
        }
        
    return(anErr);
    
} // SD_FetchDriverData
 
 
/* ------------------------------------------------------------------------------------ */
OSErr SD_RenderPage(    gxFormat                theFormat,
                        gxShape                 thePage,
                        gxPageInfoRecord        *pageInfo,
                        gxRasterImageDataHdl    imageInfo)
/*
    The message sent to render an entire page.
*/
{
 
    OSErr   theError = noErr;
 
    // if not text mode, do it the normal (raster) way
    if (GXGetJobFormatMode(GXGetJob()) != gxTextJobFormatMode) 
        {
        gxRectangle         paperSize;
        Str63               formLength;         // should be more than big enough for form skipping
        char                aNumber[8];
        char                len = 0;
        short               formLen;            // form length (in 144 dpi)
        short               i;
        
        
        // find out how big our paper is
        GXGetPaperTypeDimensions(GXGetFormatPaperType(theFormat), nil, &paperSize);
        
        // determine the left margin (in pixels)
        {
        SpecGlobalsHdl          hGlobals = GetMessageHandlerInstanceContext();
        SpecGlobalsPtr          pGlobals;
        gxRasterImageDataPtr    pImageData;
 
        // dereference for size and speed   
        pImageData  = *imageInfo;
        pGlobals = *hGlobals;
        paperSize.left += ff(18);       // ImageWriter's can't go tighter than .25 inch
        if (paperSize.left > 0)
            paperSize.left = 0;
        pGlobals->leftMargin    = FixedToInt(
                                    FixMul(-paperSize.left, 
                                        FixDiv(pImageData->hImageRes, ff(72))));
        
        // set this to be the top of form
        formLength[len++] = ESCAPE;
        formLength[len++] = 'v';
 
        // set the form length to be the size of the page iff ImageWriterII
        if (pGlobals->isImageWriterII)
            {
            formLength[len++] = ESCAPE;
            formLength[len++] = 'H';
            formLen = FixedToInt(FixMul(paperSize.bottom-paperSize.top, ff(2)) );   // length is set in 144 dpi
            NumToString(formLen, (unsigned char*)aNumber);
            for (i = 0; i < 4-aNumber[0]; ++i)
                formLength[len++] = '0';
            for (i = 1; i <= aNumber[0]; ++i)
                formLength[len++] = aNumber[i];
            }
        }
 
        // NOW: move over the top margin
        formLen = -FixedToInt( FixMul(paperSize.top, ff(2)) );
        
            // Forward line feed
            formLength[len++] = ESCAPE;
            formLength[len++] = 'f';
 
            // send multiples of 99
            if (formLen >= 99)
                {
                formLength[len++] = ESCAPE;
                formLength[len++] = 'T';
                formLength[len++] = '9';
                formLength[len++] = '9';
                while (formLen >= 99)
                    {
                    formLength[len++] = 0x0A;       // line feed
                    
                    formLen -= 99;
                    }
                }
                
            // send remaining line feeds
            if (formLen > 0)
                {
                formLength[len++] = ESCAPE;
                formLength[len++] = 'T';
                NumToString(formLen, (unsigned char*)aNumber);
                if (aNumber[0] == 1)
                    {
                    formLength[len++] = '0';
                    formLength[len++] = aNumber[1];
                    }
                else
                    {
                    formLength[len++] = aNumber[1];
                    formLength[len++] = aNumber[2];
                    }
                formLength[len++] = 0x0A;       // line feed
                }
 
        // we've got all of this data, now send it
        theError = Send_GXBufferData((char*)formLength, len, gxNoBufferOptions );
        nrequire(theError, SetFormLength);      
                
        // continue with normal rendering
        theError = Forward_GXRenderPage(theFormat, thePage, pageInfo, imageInfo);
        } 
    else 
        {
        theError = PrintPageInDraftMode(thePage, imageInfo);
        }
 
failed_WrongShape:
SetFormLength:
    return(theError);
    
} // SD_RenderPage
 
 
//<FF>
/* ------------------------------------------------------------------------------------ */
/*  SPECIFIC DRIVER RASTER OVERRIDES                                                    */
/* ------------------------------------------------------------------------------------ */
OSErr SD_LineFeed (
    long *lineFeedSize,                         // amount to line feed by
    Ptr buffer, unsigned long   * bufferPos,    // data goes here
    gxRasterImageDataHdl hImageData)            // raster image data stuff
/*
    Message is sent to output paper advance commands to the printer
*/
{
 
    OSErr   anErr;
    Boolean amLowRes;
    long    actualLineFeed = *lineFeedSize;
 
    amLowRes = ((**hImageData).vImageRes == ff(72));
    // if we are in low res mode, we double the line feed size, as all ImageWriter 
    // line feed commands are expressed at 144 dpi.
    if (amLowRes)
        *lineFeedSize <<= 1;
    
    // optimize small motions (particularlly -1 followed by +1 with no data between)
    // into groups.  This gets rid of the "paper dance" for blank colors passes.
    {   
    SpecGlobalsHdl  hGlobals = GetMessageHandlerInstanceContext();
    SpecGlobalsPtr  pGlobals = *hGlobals;
    
    if (    (pGlobals->packagingOptions == kDoSmallLineFeeds) || 
            (*lineFeedSize < -1) || 
            (*lineFeedSize > 1) )
        {
        *lineFeedSize += pGlobals->lineFeeds;
        pGlobals->lineFeeds = 0;
        // do the line feed in the default way
        anErr = Forward_GXRasterLineFeed(lineFeedSize, buffer, bufferPos, hImageData);
        }
    else
        {
        pGlobals->lineFeeds += *lineFeedSize;
        *lineFeedSize = 0;
        anErr = noErr;
        }
    }
    
    // and if in low quality mode, we divide the result to make up for the multiplication
    // that we do above
    if (amLowRes)
        *lineFeedSize >>= 1;    
            
    return(anErr);
    
} // SD_LineFeed
 
//<FF>
 
//-------------------------------------------------------------------
// PrintThisBand
//
// Gets the render options collection, and tests the current printer
// band against it to see if it needs to draw this band
//-------------------------------------------------------------------
 
Boolean PrintThisBand ( short printerBand )
{
    Boolean                 displayPlane = true;
    OSErr                   theErr;
    CMYKRenderCollection    planes;
    Collection              jobCollection;
    long                    itemSize = sizeof(CMYKRenderCollection);
    gxJob                   theJob;
    
    theJob = GXGetJob();
 
    jobCollection = GXGetJobCollection(theJob);
 
    theErr = GetCollectionItem( jobCollection, 
                                kCMYKRenderCollectionType, kCMYKRenderCollectionID, 
                                &itemSize, &planes);
    nrequire(theErr, FailedGetCollectionItem);
 
    if (!theErr)
    {
        switch (printerBand){
            case kYellowPass  : displayPlane = planes.yellowIsOn;
                break;
            case kMagentaPass : displayPlane = planes.magentaIsOn;
                break;
            case kCyanPass    : displayPlane = planes.cyanIsOn;
                break;
            case kBlackPass   : displayPlane = planes.blackIsOn;
                break;
        }
    }
 
FailedGetCollectionItem:
 
    return displayPlane;
}
 
//<FF>
/* ------------------------------------------------------------------------------------ */
 
// This macro stores one group into the pointer:
//  P = Pointer to fill into
//  G = Character for group
//  S = Length of group run in pixels
 
#define EMITGROUP(P, G, S)                  \
        P->cEscape = ESCAPE;                \
        P->cCommand = G;                    \
        Long2Dec(S, P->cLineLength);        
 
//<FF>
/* ------------------------------------------------------------------------------------ */
 
OSErr SD_PackageBitmap (
    gxRasterPackageBitmapRec    *pPackage,
    Ptr                         buffer,     // data goes here + bufferPos
    unsigned long               *bufferPos, // how much of the buffer already is full
    gxRasterImageDataHdl        hImageData) // private image data
/*
    Packages a bitmap for the ImageWriter
    This routine is called in order to add your rotated and packaged pixel
    data to the buffer.  It is called once for each head pass.  This routine
    is pretty complex because it also does IW run length compression. 
 
    It has also been modified so it can handle color seperations. It does this by
    by comparing the color of the current pass to the render options collection.
    This is done by PrintThisBand().
    
    It must do the following:
        
    1)  Start filling the buffer from buffer+bufferPos.  Remember
        that this pointer may not be word aligned - so be careful
        assigning things into it.
        
    2)  If your printer does SetMargins, put a "fake" set of commands at
        the begining of the data.  Since most of the time you don't
        know the margins, you can save away the value of the bufferPos,
        and backpatch it after you have finished with the offscreen.
        SetMargins is used on printers that allow you to not send starting
        and ending whitespace.
        
    3)  Add in the rotated data for your printer.  The data to stuff starts
        at location startY in hOffscreen.  Stuff the bits from here until
        you reach startY+<your band size>, which is the size of your single
        band in this resolution mode.  Increment your number by 
        <your pass offset> + 1, which is the number of microspaces
        you will send between head passes to form this band.   Take care
        that you don't step off of the end of the offscreen in this operation,
        you may be called with startY at the end of the offscreen if the first part
        of the band is white.
        
        colorBand contains the color band which you should be stuffing, from
        1 to the number of color passes your printer needs (usually 4).
        Pack in the correct color band.  The packager will call you once
        for each color band, and correctly handle line feeds and backward
        line feeds to do the correct thing.  If your printer takes full
        RGB or CYMK data, I would define your number of colors to be
        1 and pack the full RGB or CYMK data with the one call to StuffBuffers.
        
        If you request kSendAllColors in your raster pack resource, then this
        message will always be called for all color passes, even those that
        do not have data on them.  For some printers, this is useful.  For the
        case of the ImageWriter, this option is not specified, so this message
        will only be sent for colors that actually have dirty bits within them.
        
    4)  Backpatch SetMargins from your saved value in step 2) now that you
        know the margins.
        
    5)  Increment bufferPos by the number of bytes you have
        added to the buffer.  Be sure to take into account the Set Margins
        command if you added one.
 
    
*/
{
// #pragma unused (isColorDirty)
 
    OSErr                   anErr;                  // would you beleive we could make mistakes?
    ScanLinePtr             pTheScanLine;           // Pointer to the start of scan line data
    unsigned short          lastDirtyCol;           // Last dirty part of the scan line
    unsigned short          firstDirty;             // First dirty pixel
    unsigned short          lastDirty;              // Last dirty pixel
    Boolean                 bandIsDirty;            // Is this band dirty?
    unsigned short          numberBytesAdded;       // Number of bytes we have added to the row
    unsigned short          repeatCount;            // Number of times we have seen this bitmap
    
    register unsigned short whichCol;               // Index into the scan line data
    register unsigned short x,y;                    // Index values into the offscreen
        
    register Ptr            thePtr;                 // Pointer to each Y scanline
    register unsigned char  tempColumn;             // Placeholder for the working column
    unsigned char           lastColumn;             // What was in the contents of the last column?
    
    Ptr                     basePtr;                // Pointer to current X byte
    unsigned char           outputMask;             // Mask of bit to set in rotated image
    unsigned char           inputMask;              // Mask of bit to look at in X
    unsigned char           startingInputMask;      // Mask of first bit of interest
    unsigned short          yPointerOffset;         // Increment pointer by this to get to next scanline
    
    unsigned short          endY, endX, incrY;      // To remove loop invariants.
    unsigned short          packingColor;           // number of colors packing
    unsigned long           originalBufferPos;      // where we were in the buffer before we started;
    long                    originalLineFeeds;      // how many line feeds did we have before?
    SpecGlobalsHdl          hGlobals;
    SpecGlobalsPtr          pGlobals;
    SetMarginsPtr           marginBuffer;
 
// Get the globals
    hGlobals = GetMessageHandlerInstanceContext();
    pGlobals = *hGlobals;
    
// save away original position in order to do a restore should the band be clean
    originalBufferPos = *bufferPos;
    originalLineFeeds = pGlobals->lineFeeds;
 
    if (originalLineFeeds == 0)
    {
        anErr = noErr;
    }
    else
    {
// if we have any extra line feeds saved up, do them now!   
        pGlobals->lineFeeds = 0;
        pGlobals->packagingOptions = kDoSmallLineFeeds;
        anErr = Send_GXRasterLineFeed((long*)&originalLineFeeds, buffer, bufferPos, hImageData);
        pGlobals = *hGlobals;
        pGlobals->packagingOptions = kNoPackagingOptions;
    }
    nrequire(anErr, SendInitialLineFeeds);
    
// See if we need to print this color band
    if (   (  (*hImageData)->packagingInfo.colorPasses == 4)
        && (! PrintThisBand ( pPackage->colorBand )) )
        bandIsDirty = false;
    else
    {
 
    // Set color if ImageWriterII
        if ((**hGlobals).isImageWriterII)
        {
            pTheScanLine = (ScanLinePtr) (buffer + kSetMarginsSize + (*bufferPos));
            
    // Set color mode for this scan line, if needed
            pTheScanLine->cColorEscape      = ESCAPE;
            pTheScanLine->cSetColorCommand  = kSetColorCommand;
            
            packingColor = (*hImageData)->packagingInfo.colorPasses;
            if (packingColor == 4)
            {
                switch (pPackage->colorBand)
                {
                    case kYellowPass:
                        pTheScanLine->cColor    = '1';
                        startingInputMask = 0x10;
                        break;
                        
                    case kMagentaPass:
                        pTheScanLine->cColor    = '2';
                        startingInputMask = 0x20;
                        break;
                        
                    case kCyanPass:
                        pTheScanLine->cColor    = '3';
                        startingInputMask = 0x40;
                        break;
                        
                    case kBlackPass:
                        pTheScanLine->cColor    = '0';
                        startingInputMask = 0x80;
                        break;
                
                }
            }
            else
            {
                pTheScanLine->cColor = '0';
                startingInputMask = 0x80;
            }
        }
        else    // Backup to eliminate cColorEscape, cSetColorCommand and cColor
        {
            pTheScanLine = (ScanLinePtr) (buffer + kSetMarginsSize + (*bufferPos) - 3);
            packingColor = (*hImageData)->packagingInfo.colorPasses;
            startingInputMask = 0x80;
        }
 
    
// Start with the first bit in the offscreen 
        inputMask = startingInputMask;
        
// We start out with no dirty bits 
        firstDirty = 0;
        lastDirty = 0;
        bandIsDirty = false;
        
// Set our array index to zero 
        whichCol = 0;
        lastDirtyCol = 0;
        numberBytesAdded = 0;
        
// Set up RLL variables 
        repeatCount = 0;
        lastColumn = 0;
        
// Get the byte pointer for the start of this color band 
        basePtr = pPackage->bitmapToPackage->image;
        
// Get the byte pointer for the start of the first scan line  
        basePtr += pPackage->startRaster * pPackage->bitmapToPackage->rowBytes;
        
// Save away loop invariants 
        endY    = pPackage->startRaster + (*hImageData)->packagingInfo.headHeight;  // Ending scan line
        incrY   = (*hImageData)->packagingInfo.passOffset + 1;          // Number of scanlines to increment by
        endX    = pPackage->dirtyRect.right;                            // Ending X pos
        yPointerOffset = incrY * pPackage->bitmapToPackage->rowBytes;   // amount to add to the input
 
// pointer to move to the next scanline
        
// If the ending position is too large for the bitmap we have been given,
//  truncate it, so that we don't print garbage
        if (endY > pPackage->bitmapToPackage->height)
            endY = pPackage->bitmapToPackage->height;
        
// For the entire width of the offscreen, move a rolling mask along in the
// X direction, rotating up 8 bits of Y data per column.  In addition, compress
// runs of columns that are > 14 length. 
        for (x = 0; x < endX; x++)
        {       
    // The bits in this column are clear to begin with 
            tempColumn = 0;
            
    // Which byte to look at in the input buffer 
            thePtr = basePtr;
            
    //  Where to place the bit in the output. The ImageWriter takes the bit
    //  pattern upside down. 
            outputMask = 0x01;
            
    // Scan through this band, setting each of the 8 bits == the bit in that scan line 
            for (y = pPackage->startRaster; y < endY; y += incrY)
            {
    // If we have a bit in the input, rotate it into the output 
                if ((*thePtr) & inputMask)
                    tempColumn |= outputMask;
                
    // move onto next position in the output data               
                outputMask <<= 1;
                
    // move onto the next scan line in the input data
                thePtr += yPointerOffset;
            } // for y
            
            
    // Save the column info  
            pTheScanLine->iTheData[whichCol] = tempColumn;
            
    // Get the next bit from the current pointer 
            inputMask >>= packingColor;
            if (!inputMask)
            {
    // If we run out of bits, get the next byte 
                basePtr++;
            
    // And reset the bit mask to the first bit 
                inputMask = startingInputMask;
            }
            
    // If we have some form of data 
            if (tempColumn != 0)
            {
                if (!bandIsDirty)
                {
    // This is the first dirty pixels we have so far 
                    bandIsDirty = true;
                    firstDirty = x;
                }
            
    // This is also the last dirty pixels so far 
                lastDirty = x;
            } // SetDirty
            
    // If we have some dirty bits 
            if (bandIsDirty)
            {
    // Move on to the next column 
                whichCol++;
                
    // If this is a dirty column, then it is the last one so far 
                if (tempColumn != 0)
                    lastDirtyCol = whichCol;
                
    // If we have a duplication, up the repeat count 
                if (tempColumn == lastColumn) // if (false) // turn off repeat groups
                {
                    repeatCount++;
                    if (repeatCount == 14)
                    {
    // Kick out the old group 
                        whichCol -= 14;
                        EMITGROUP(pTheScanLine, kGraphicsCommand, whichCol);
                        numberBytesAdded += whichCol + kGroupSize;
                        pTheScanLine = (ScanLinePtr)(((Ptr) pTheScanLine) +
                        whichCol + kGroupSize);
                        
                        whichCol = 1;
                        lastDirtyCol = 1;
                        pTheScanLine->iTheData[0] = tempColumn;
                    }
                }
                else
                {
    // If we were repeating, emit the repeat group 
                    if (repeatCount >= 14)
                    {
                        EMITGROUP(pTheScanLine, kRepeatGroup, repeatCount);
                        numberBytesAdded += 1 + kGroupSize;
                        pTheScanLine = (ScanLinePtr) (((Ptr) pTheScanLine) +  1 + kGroupSize);
                        
                        whichCol = 1;
                        lastDirtyCol = 1;
                        pTheScanLine->iTheData[0] = tempColumn;
                    }
                    repeatCount = 0;
                    lastColumn = tempColumn;
                }
                
            } // BandIsDirty
            
        } // end of loop for width of bitmap
        
    }
 
 
// if we have a dirty band - emit the final bit of data we have packaged up 
    if (bandIsDirty)
    {           
    
// Set the margins to be the first and last dirty pixels in the scan line -
// the ImageWriter only does left margin optimization.
        {
        
    // Get the location for placing the set margin command 
            marginBuffer = (SetMarginsPtr) (buffer + (*bufferPos));
        
    // Stuff in the set margin command 
            marginBuffer->cEscape  = ESCAPE;
            marginBuffer->cCommand = kSetMarginsCommand;
        
    // convert left margin into ASCII and place it at the start of the buffer 
            Long2Dec((**hGlobals).leftMargin + firstDirty, (Ptr)(marginBuffer->cIndentDistance));
        }
        
    // Send the last group command 
        if (repeatCount < 14)
        {
        // Emit a normal group 
            EMITGROUP(pTheScanLine, kGraphicsCommand, lastDirtyCol);
            numberBytesAdded += lastDirtyCol + kGroupSize;
        }
        else
        {
        // Don't stuff a final repeat group if it's blank space 
 
            if (tempColumn != 0)
            {
 
        // Emit a repeat group 
                EMITGROUP(pTheScanLine, kRepeatGroup, repeatCount);
                numberBytesAdded += 1 + kGroupSize;
            }
        } // end of repeatCount < 14
        
        
    //  Increment the count of the buffer by bytes added for groups, plus
    //  the header, if any, plus the set margins command 
        (*bufferPos) += numberBytesAdded + kScanLineSize + kSetMarginsSize;
        
    // and put a <cr> at the end of the line
        *(char*)(buffer + (*bufferPos)) = 0x010D;
        (*bufferPos) += 1;
        
    } // bandIsDirty
    else
    {
// Band is not dirty, so don't output data as we didn't have any!
 
        *bufferPos = originalBufferPos;
    
    // restore original number of line feeds
        pGlobals = *hGlobals;
        pGlobals->lineFeeds = originalLineFeeds;
    } 
 
// always return your errors!
SendInitialLineFeeds:
    return(anErr);
 
} // SD_PackageBitmap
 
 
//===================================================================
//             Print dialog handling stuff 
//===================================================================
 
// Dialog panel items constants
 
#define kDitherRB   1
#define kHalfToneRB 2
#define k_Dth_DotPU 3
#define k_HT_DotShapePU 4
#define k_HT_AngleTE0   5
#define k_HT_FreqTE0    6
#define k_HT_AngleTE1   7
#define k_HT_FreqTE1    8
#define k_HT_AngleTE2   9
#define k_HT_FreqTE2    10
#define k_HT_AngleTE3   11
#define k_HT_FreqTE3    12
#define k_Dth_Box       20
#define k_HT_Box        21
 
//-------------------------------------------------------------------
// SD_JobPrintDialog
//
// overides : GXJobPrintDialog
//
// Called by the system when the  print dialog is being created.
// This gives us a chance to add out own panels. This done by
// SetUpPrintPanel.
//-------------------------------------------------------------------
 
OSErr SD_JobPrintDialog(gxDialogResult *dlogResult )
{
    OSErr theErr = noErr;
 
    theErr = SetUpPrintPanel( ); 
    
    if (!theErr)
        theErr = Forward_GXJobPrintDialog(dlogResult);
 
    return theErr;
}
 
//-------------------------------------------------------------------
// SD_HandlePanelEvent
//
// overides : HandlePanelEvent
//
// Messages are passed by the system to SD_HandlePanelEvent so it can
// do any special processing while the print panel is up. We use this
// to set up any custom items or gray fields, etc. Because we've 
// added two panels, we need to check from which panel the message
// has been passed. This can be found in gxPanelInfoRecord.panelResId 
//-------------------------------------------------------------------
 
 
OSErr SD_HandlePanelEvent( gxPanelInfoRecord *panelInfo )
{
    OSErr                   theErr = noErr;
    GrafPtr                 oldPort;
    DialogPtr               pDlg;
    CMYKRenderCollection    planes;
    Collection              jobCollection;
    long                    itemSize = sizeof(CMYKRenderCollection);
    gxJob                   theJob;
 
    pDlg = panelInfo->pDlg;
 
    GetPort(&oldPort);
    SetPort(pDlg);
    
    switch (panelInfo->panelEvt)
    {
 
        case gxPanelOpenEvt:
 
// Every time a panel is opened, set up any items on it that may need setting.
            if (panelInfo->panelResId == kDitherPanelID)
                OpenDitherPanel(pDlg, panelInfo->itemCount);
            break;
 
        case gxPanelHitEvt:
    
// If the user clicks the dither or half-tone buttons then we need to gray
// out items appropriately. As We're using a gxExtendedDITLType resource
// -'xdtl' to handle the collection, we don't need to worry about changing
// the state of the dialog box then writting back the collection. We get the
// collection so we can see if the state has changed, this saves on unneeded
// setting of dialog items states and possible flicker.
 
            if (panelInfo->panelResId == kDitherPanelID)
            {
                theJob = GXGetJob();
                jobCollection = GXGetJobCollection(theJob);
                theErr = GetCollectionItem( jobCollection, 
                                            kCMYKRenderCollectionType, kCMYKRenderCollectionID, 
                                            &itemSize, &planes);
                if (!theErr)
                    switch (panelInfo->itemHit - panelInfo->itemCount)
                    {
                        case kDitherRB:
                        case kHalfToneRB:
                                GrayDitherDialog ( pDlg, panelInfo->itemCount,
                                                    planes.renderMode == kDitherIt );
                        break;
                    }
            }
            break;
    }
    
    SetPort(oldPort);   
 
    return theErr;
}
 
//-------------------------------------------------------------------
// SetUpPrintPanel
//
// This creates the render options collection and adds it to a print
// job.Then it adds two extra panels to the print panel. 
//-------------------------------------------------------------------
 
OSErr SetUpPrintPanel( void )
{
    OSErr                theErr = noErr;
    gxPanelSetupRecord   panelSetupRec;
    CMYKRenderCollection planesConfig;
    Collection           jobCollection;
    long                 itemSize = sizeof(CMYKRenderCollection);
 
//  Get the job collection and then try to find the
//  render collection item in there.
    
    jobCollection = GXGetJobCollection(GXGetJob());
 
    theErr = GetCollectionItem( jobCollection, 
                                kCMYKRenderCollectionType, kCMYKRenderCollectionID, 
                                &itemSize, &planesConfig);
 
//  If the collection item doesnÕt yet exist, create a new
//  item, set it up with default values, and add it to the
//  job collection
    
    if (theErr == collectionItemNotFoundErr)
    {
// Turn all planes on
        planesConfig.cyanIsOn       = true;
        planesConfig.magentaIsOn    = true;
        planesConfig.yellowIsOn     = true;
        planesConfig.blackIsOn      = true;
 
// Print in half tone mode
        planesConfig.renderMode = kHalfToneIt;
 
// Put default values into the dither parameters
        planesConfig.ditherLevel  = 4;
 
// Put default values into the half tone parameters
        planesConfig.dotType      = gxRoundDot;
 
        planesConfig.angles   [kRenderOptsYellow] = 0x000F0000;
        planesConfig.frequency[kRenderOptsYellow] = 0x002D0000;
 
        planesConfig.angles   [kRenderOptsCyan] = 0x003C0000;
        planesConfig.frequency[kRenderOptsCyan] = 0x002D0000;
 
        planesConfig.angles   [kRenderOptsMagenta] = 0x00000000;
        planesConfig.frequency[kRenderOptsMagenta] = 0x002D0000;
 
        planesConfig.angles   [kRenderOptsBlack] = 0x002D0000;
        planesConfig.frequency[kRenderOptsBlack] = 0x002D0000;
 
// Add the new collection to the job
        theErr = AddCollectionItem  (   jobCollection,
                                        kCMYKRenderCollectionType,
                                        kCMYKRenderCollectionID,
                                        sizeof(CMYKRenderCollection),
                                        &planesConfig);
    }
 
    nrequire(theErr, GetSettings_Failed);
 
 
//  Set up the two panels: store the ID of the panel resource to use,
//  the resource file in which it is located, and the type of panel
//  that is being stored.
    
    panelSetupRec.panelResId     = kSeperationPanelID;
    panelSetupRec.resourceRefNum = GXGetMessageHandlerResFile();
    panelSetupRec.refCon         = 0;
    panelSetupRec.panelKind      = gxDriverPanel;
    theErr                       = GXSetupDialogPanel(&panelSetupRec);
 
    panelSetupRec.panelResId     = kDitherPanelID;
    panelSetupRec.resourceRefNum = GXGetMessageHandlerResFile();
    panelSetupRec.refCon         = 0;
    panelSetupRec.panelKind      = gxDriverPanel;
    theErr                       = GXSetupDialogPanel(&panelSetupRec);
 
GetSettings_Failed:
    return theErr;
}
 
//-------------------------------------------------------------------
// OpenDitherPanel
//
// Called by SD_HandlePanelEvent every time it is sent a 
// gxPanelOpenEvt for the kDitherPanelID. Need to get the collection
// and gray out any items as appropriate. Also add the frame user
// items.
//-------------------------------------------------------------------
 
OSErr OpenDitherPanel ( DialogPtr pDlg, short itemCount )
{
    CMYKRenderCollection planes;
    OSErr                theErr = noErr;
    long                 itemSize = sizeof(CMYKRenderCollection);
    Collection           jobCollection;
    gxJob                theJob;
    Handle               hItem;
    Rect                 itemRect;
    short                itemType;
 
// Get the job
    theJob = GXGetJob();
 
// Get the collection
    jobCollection = GXGetJobCollection(theJob);
 
// Get the render options collection
    theErr = GetCollectionItem( jobCollection, 
                                kCMYKRenderCollectionType, kCMYKRenderCollectionID, 
                                &itemSize, &planes);
    nrequire(theErr, FailedGetCollectionItem);
 
// Gray out the items depending on the render mode
    GrayDitherDialog ( pDlg, itemCount,
                        planes.renderMode == kDitherIt );
 
// Add the frame items
    GetDItem(pDlg, itemCount + k_Dth_Box, &itemType, &hItem, &itemRect);
    SetDItem(pDlg, itemCount + k_Dth_Box, itemType, (Handle)DrawBoundBox, &itemRect);
 
    GetDItem(pDlg, itemCount + k_HT_Box, &itemType, &hItem, &itemRect);
    SetDItem(pDlg, itemCount + k_HT_Box, itemType, (Handle)DrawBoundBox, &itemRect);
 
 
FailedGetCollectionItem:
    return theErr;
}
 
 
//-------------------------------------------------------------------
// SetDialogCtrlGray
//
// Sets a control dialog item to gray.
//-------------------------------------------------------------------
 
void SetDialogCtrlGray ( DialogPtr pDlg, short theItem, Boolean isEnable)
{
    Handle             hItem;
    Rect               itemRect;
    short              itemType;
 
    GetDItem(pDlg, theItem, &itemType, &hItem, &itemRect);
    if (isEnable)
        HiliteControl ((ControlHandle)hItem, 0);
    else
        HiliteControl ((ControlHandle)hItem, 255);
}
 
//-------------------------------------------------------------------
// GrayDitherDialog
//
// Gray out unused items in the hilite control panel dependent on
// whether the dialog is half-tonning or drawing in color.
//-------------------------------------------------------------------
 
void GrayDitherDialog ( DialogPtr pDlg, short itemCount, Boolean isDither)
{
    if (isDither)
    {
        SetDialogCtrlGray ( pDlg, itemCount + k_Dth_DotPU,   true);
        SetDialogCtrlGray ( pDlg, itemCount + k_HT_DotShapePU, false);
    }
    else
    {
        SetDialogCtrlGray ( pDlg, itemCount + k_Dth_DotPU,   false);
        SetDialogCtrlGray ( pDlg, itemCount + k_HT_DotShapePU, true);
    }
}
 
//-------------------------------------------------------------------
// DrawBoundBox
//
// Draws a Rectangle user item in a dialog
//-------------------------------------------------------------------
 
pascal void DrawBoundBox( WindowPtr theWindow, short theItem )
{
    Handle hItem;
    Rect   itemRect;
    short  itemType;
 
    GetDItem(theWindow, theItem, &itemType, &hItem, &itemRect);
    FrameRect ( &itemRect );
}