PAPPostScriptStuff.c

/*
    File:       PAPPostScriptStuff.c
 
    Contains:   The following are routines used to support the handling of Postscript query 
                processing.  These are my own routines implemented to provide support for
                the postscript queries from a laserwriter client.  All of the postscript
                parsing related functions are included in this file.  I'd like to warn the
                reader that this code is written to handle some basic postscript queries as
                sent by Apple LaserWriter Client prior to v8.5.
    
                The design of the routine presented here are to
                1. recognize postscript queries as opposed to postscript data
                2. parse postscript commands and return the default response.
    
                This code does not support other Postscript clients or handling of other
                than the default responses.  This may not be sufficient to support your
                printer and client.
    
                For more information on processing Postscript queries, contact Adobe for their
                technote on this subject *** Get web address ***
    
                A RIP or printer spooler would have to handle queries in a different manner.
                In order to respond to the queris the spooler code might already have the
                desired response, or may have to delay response until it can query the
                    printer to obtain the correct response.  
    
    My thanks to Mark Fleming for his help with debugging this code.
 
    Written by: Rich Kubota 
 
    Copyright:  Copyright © 1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                7/22/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
                
 
*/
 
#include <ctype.h>
#include "StringUtils.h"
#include "PAPServerSample.h"
#include "PAPPostScriptStuff.h"
 
extern PacketPtr    gTempPackPtr;
char                gEOFStr[8] = kEOFStr;
char                gBeginPSStr[8] = "\045\041PS";      // = "%!PS"
char                qBeginQueryStr[8] = kBeginQueryStr;
char                gEndStr[8] = kEndStr;
char                gQueryStr[8] = kQueryStr;
extern OTLIFO*      gFreeQ; 
extern Boolean      gDone;
 
 
// prototypes
 
Boolean FindString(const char *buffer, char *str, SInt16 lenStr, SInt16 *lenMatched,
                        SInt16 *pos, UInt16 numCharsInBuffer, Boolean matchAll);
 
 
/*
    The TestDataIsPSQuery is used to determine whether an incoming packet is the initial packet
    for a postscript query, or is a continuing packet of a prelvious query started with some 
    previous packet.
*/
Boolean TestDataIsPSQuery(PacketPtr packetPtr)
{
    MyEndpointRef   *theEp;
    Boolean         result;
 
    theEp = packetPtr->theEp;
 
    if (TstInPSQueryFlag(theEp->flags)) // check if the endpoint is already processing a postscript
        result = true;                  // query for which we have not reached the EndQuery
    else
    {
        result = IsPacketAPSQuery(packetPtr);
        if (result == true)
        {
#if SHOW_DEBUG_FLOW
        
            DebugStr("\p processing a postscript query;g");
#endif
            theEp->prevPtr = nil;   // initialize the prevPtr field to nil
            SetInPSQueryFlag(theEp->flags);
            theEp->psState = kLookingForEndStr; // indicate that we have identified query beginning
                                        // and that the next step is to look for the EndQuery string
 
                // set the time Data In timestamp field
            BlockMove((Ptr)&packetPtr->timeStamp, (Ptr)&theEp->timeDataIn, sizeof(OTTimeStamp));
                // reset the numBytesIn field to zero
            theEp->numBytesIn = 0;
            
        }
    }
    
    return result;
}
 
/*
    ProcessPSQuery as used in this sample looks for the ps end...query string, then
    gets the response and and sends it
    The routine is designed to be called at deferred task time and uses OTAllocMem
    instead of NewPtr or NewHandle
    
    Note that this routine is responsible for enqueueing the processed packet ptr
    to the freeQ, unless it appears that there is a partial match to a string that
    we are looking for at the end of the buffer.  If so, then the packetPtr is
    save with the endpoint ref to be used when the next incoming packet is processed
    
    In this routine, the first while loop is where the contents of the ps query are
    analyzed until the end of the packet is read.  As each default response is read,
    an individual OTSnd call is made to send the default response.  Each response will 
    be made with the T_MORE flag bit set to keep the EOF bit from being sent in the
    PAP responses. This routine does not queue multiple responses to send in a single
    response.
    
    When the end of the packet has been reached, the routine exits the while loop 
    and checks for the EOF indicator signaling the end of the query.  If the 
    EOF is read, then the call is made to SendEmptyPacket, which will result in
    an empty packet with the EOF bit set.  The Apple LaserWriter client requires
    receiving a last packet that has the eof bit set to know that the query has been
    completely processed by the printer/server (even if the printer/server has responded
    to all of the query items).
*/
OSStatus ProcessPSQuery(PacketPtr packetPtr)
{
    OSStatus        err;
    MyEndpointRef   *theEp;
    PacketPtr       packPtr;
    UInt16          offset;
    UInt16          matchResult;
    Boolean         done = false;
    Boolean         testflag = false;
 
    theEp = packetPtr->theEp;
        // add the number of bytes in the packet to the numBytesIn field
    theEp->numBytesIn += packetPtr->numBytes;
 
        // set the time Data End timestamp field
    BlockMove((Ptr)&packetPtr->timeStamp, (Ptr)&theEp->timeDataEnd, sizeof(OTTimeStamp));
    
    err = kOTNoError;
        // check whether we need to concatenate the current and previous packet ptrs
    if ((packetPtr == theEp->prevPtr) || (theEp->prevPtr == nil))
    {
        DebugStr("\p no packet to append");
        testflag = false;
        packPtr = packetPtr;
    }
    else
    {
        DebugStr("\p checking how we append packets");
        packPtr = gTempPackPtr;
 
            // copy contents of the previous packet buffer to the new buffer
        BlockMove((Ptr)theEp->prevPtr, (Ptr)packPtr, sizeof(PacketBuffer));
        offset = sizeof(PacketBuffer) - kPAPDataSize + (theEp->prevPtr)->numBytes;
        BlockMove((Ptr)&(packetPtr->data), (Ptr)&(packPtr->data[offset]), packetPtr->numBytes);
            // adjust the numbytes field
        packPtr->numBytes += packetPtr->numBytes;
        
            // enqueue the previous packet ptr back to the gFreeQ since I not expecting a 
            // ps default response that overlaps 3 packets.
            // fixed bug here where I release the current packetPtr instead of the previous
            // packetPtr
    
        OTLIFOEnqueue(gFreeQ, &((theEp->prevPtr)->fLink));  // first field is fLink field
    }
    
    while (done == false)
    {
        switch (theEp->psState)
        {
            case kLookingForEndStr:
                matchResult = FindQueryString(packPtr, kLookingForEndStr);
                break;
 
            case kLookingForQueryStr:
                matchResult = FindQueryString(packPtr, kLookingForQueryStr);
                break;
 
            case kLookingForDefaultResponse:
                if (testflag)
                    DebugStr("\p about to call ProcessDefaultResponse");
                
                matchResult = ProcessDefaultResponse(packPtr);
                break;
        }               
                
        switch (matchResult)
        {
            case kMatch:
                if (theEp->psState == kLookingForDefaultResponse)
                    theEp->psState = kLookingForEndStr;
                else
                    theEp->psState += 1;    // increment the state
                break;
            
            case kPartialMatch:
                done = true;        // have to come around with the next packet
                break;
 
            case kNoMatch:
                DebugStr("\p no match");
                                    // wasn't able to find a match, not even a 
                                    // partial match, so set the pos to the end of the
                                    // packet
                packPtr->lastPos = packPtr->numBytes;
                done = true;        // have to come around with the next packet
                break;
        }
 
    }
 
        // check to see if we see the EOF flag
        
 
        // this is the last packet of this query.
        // check to see if the lastPos field is set to the end of the buffer
        // this tels us the we are not awaiting a pending match for a partial
        // string
    if (packPtr->lastPos == packPtr->numBytes)
    {
            // reset the lastPos field so that we can search for the EOF string as the
            // last few characters
        packPtr->lastPos -= clen(gEOFStr);
            // search for the EOF string at the end of the packet
        matchResult = FindQueryString(packPtr, kLookingForEOFStr);
            // since we reset lastPos, we need to restore it
        packPtr->lastPos = packPtr->numBytes;
            
        if (matchResult == kMatch)
        {
#if SHOW_DEBUG_FLOW
    
            DebugStr("\p Sending a null packet;g");
#endif
                // send an empty response with EOF flag set since this is what the Laserwriter 
                // client wants to see.
            SendEmptyPacket(packPtr);
                // clear bit that indicates we are processing a postscript query
            ClrInPSQueryFlag(theEp->flags);
#if SHOW_DEBUG_FLOW
    
            DebugStr("\p Have finished ps query;g");
#endif
        }
        
    }
        
    
        // check to see whether we processed all of the bytes in the packet.
    if (packPtr->lastPos == packPtr->numBytes)
    {
        theEp->prevPtr = nil;   // if so, then don't save this packet in the prevPtr field.
 
                // queue the buffer to the freeQ
        OTLIFOEnqueue(gFreeQ, &(packetPtr->fLink)); // first field is fLink field
    }
    else
    {
        theEp->prevPtr = packetPtr; // we still have bytes to process.
            //adjust the lastPos field
        packetPtr->lastPos = packetPtr->numBytes - (packPtr->numBytes - packPtr->lastPos);
        
    }   
        
    return err;
    
}
 
 
/*
    DoProcessPSQuery is used as the entry point to processing a postscript query.
*/
Boolean DoProcessPSQuery(PacketPtr  packetPtr)
{
    OSStatus    err = kOTNoError;
    Boolean     result = false;
        
    if (TestDataIsPSQuery(packetPtr))  // is the packet a PS query
    {
        result = true;      // packet is a PostScript query and is being processed
        err = ProcessPSQuery(packetPtr);
            
        if (err < kOTNoError)
        {
            DoValueBreak(err, "error occured calling ProcessPSQuery #");
                // show the error and quit the main event loop
            gDone = true;
        }
        
    }
    else
    {
            // we aren't processing PS query packets, so exit this routine
            // the IsPacketQuery routine potentially sets the lastPos field
            // so reset it back to 0;
        packetPtr->lastPos = 0;
    }
    
    return result;
}
 
/*
    FindString is used to find the iterate through the characters passed in the buffer
    to find the string passed in str.  The length of the search string is passed in 
    lenStr.  pos is a pointer to the index where to start searching.  stopChar is the
    last position of the buffer to search
    input 
        buffer - pointer to buffer to search
        str - string to match
        lenStr - length of the str to match in buffer
        pos - index where to begin search
        numCharsInBuffer - index of last valid char in buffer
        matchAll - match both upper and lower cases of the input string.
    
    output -
        lenMatched - number of character which were matched.  If no match, then
                        returns 0.
        pos - index of the next char to look at for the next search.
        
    If the str is not found in the buffer between the pos index and the stopChar,
    *pos returns -1,  If a partial match is made at the end of the buffer, return the
    negative offset
*/
 
Boolean FindString(const char *buffer, char *str, SInt16 lenStr, SInt16 *lenMatched,
                        SInt16 *pos, UInt16 numCharsInBuffer, Boolean matchAll)
{
    SInt16      i;
    SInt16      numToCompare;
    Boolean     done = false;
    Boolean     ok;
    
    numToCompare = lenStr;  // set the number of chars to compare as the string len.
    while (done == false)   // search until the flag is set to true.
    {
            // check whether the first character matches the character at the
            // current index position
        if ((buffer[*pos] == str[0]) || ((matchAll == true) && (toupper(buffer[*pos]) == 
                        toupper(str[0]))))
        {
            if (numCharsInBuffer < (*pos + lenStr))
            {
                    // the has too few remaining characters to compare all of the current
                    // string.
                numToCompare = numCharsInBuffer - *pos;
                
            }
                //init OK boolean
            ok = true;
            for (i = 1; (i < numToCompare) && ok; i++)
            {
                if ((buffer[*pos+i] == str[i]) || ((matchAll == true) && 
                        (toupper(buffer[*pos+1]) == toupper(str[i]))))
                    continue; 
                else
                    ok = false;
                
            }
                // we've done our testing, now see if we found a match
 
            if (ok == true)     // was match found
            {
                *lenMatched = numToCompare;     // return the number of chars matched
                *pos += numToCompare;           // return the next position in the buffer to start next search
                done = true;                    // search finished.
            }
        }
        
        if (done == false)
        {
            *pos += 1;      // no match found so increment the place to start the next search iteration
            if ((*pos+1) == numCharsInBuffer)
            {
                    // no more characters in the buffer to search
                *lenMatched = 0;        // set len matched to nothing   
                done = true;            // search finished.
            }
        }
        
    }
    
    return (*lenMatched != 0);
}
 
/*
    IsPacketAPSQuery is used to check whether the contents of a packet are the beginning of
    a PostScript query.  This can be found since there will be the signature stuff
    at the beginning of the query.
*/
Boolean IsPacketAPSQuery(PacketPtr packetPtr)
{
    SInt16      lenMatched, pos;
    Boolean     result;
    
    pos = 0;        // start search from the beginning of the buffer
    result = FindString((char*)&(packetPtr->data), gBeginPSStr, clen(gBeginPSStr),
                &lenMatched, &pos, 4, kCaseMatchAll);   // must find the "%!PS" string in position 3 & 4 of the buffer
    if (result == true)
    {
            // the search found something in the first 4 characters of the buffer
        if (lenMatched < clen(gBeginPSStr))
        {
            result = false;     // didn't match 2 characters
        }
        else
        {
            result = FindString((char*)&(packetPtr->data), qBeginQueryStr, 
                            clen(qBeginQueryStr), &lenMatched, 
                            &pos, 24, kCaseMatchAll);       // must find "Query\015" in pos 15 - 20 of the buffer
            if (result == true)
            {
                if (lenMatched < clen(qBeginQueryStr))
                    result = false;
                else
                {
                        // we're going to return a true result, to set the packetPtr.lastPos
                        // field to the current pos field.  When we start our next search, we 
                        // do so at this position.
                    packetPtr->lastPos = pos;
                }
            }
        }
    }
    
    return result;
}
 
UInt16  FindQueryString(PacketPtr packetPtr, SInt16 whichStr)
{
    SInt16      lenMatched, pos;
    UInt16      result1, len;
    Boolean     result;
    char        *str;
    
    result1 = kNoMatch;             // initialize to no Match
    pos = packetPtr->lastPos;       // start search from where we last stopped looking in buffer
                                    // first find the query prefix
    
    switch (whichStr)
    {
        case kLookingForEndStr:
            str = gEndStr;
            len = clen(gEndStr);
            break;
        
        case kLookingForQueryStr:
            str = gQueryStr;
            len = clen(gQueryStr);
            break;
            
        case kLookingForEOFStr:
            str = gEOFStr;
            len = clen(gEOFStr);
            break;
    }
 
    result = FindString((char*)&(packetPtr->data), str, len,
                &lenMatched, &pos, packetPtr->numBytes, kCaseMustMatch);    
    
    if (result == true)
    {
            // check that the proper string len was returned
        if (lenMatched < len)
        {
                // we have reached the end of the buffer and have found a partial match.
            result1 = kPartialMatch;
                // set the lastPos field to where the partial match begins
            packetPtr->lastPos = pos - lenMatched;
        }
        else
        {
                // all parts found
            result1 = kMatch;
                // complete match found, so advance the lastPos field
            packetPtr->lastPos = pos;
        }
    }
    
    return result1;
    
}
 
 
UInt16 ProcessDefaultResponse(PacketPtr packetPtr)
{
    OTResult    otErr;
    UInt32      pos, begin;
    UInt16      result;
    char        response[255];
 
        // since we've found the end of the query chars, then there is at least a 
        // partial match
    result = kPartialMatch;
        // set pos to the lastPos position since this is the beginning of 
        // the query
    begin = pos = packetPtr->lastPos;
    
        // remove the leading space character which may precede the default response
    while (packetPtr->data[pos] == kSpaceChar)
    {
        pos++;
        begin++;
    }   
    
        // gather the default response characters   
    while ((packetPtr->data[pos] != kReturnChar) && (pos < packetPtr->numBytes))
    {
            // gather default response characters
        response[pos - begin] = packetPtr->data[pos];
            // increment the counter
        pos++;
    }
    
        // go ahead and set the terminating null character for the "c" string
    if ((pos - begin) < 255)
        response[pos - begin] = 0;
    else
    {
            // this should never happen, but if it does, I want to know about it.
#if SHOW_DEBUG_FLOW
//      DebugStr((const unsigned char *)"\p response greater than 255 chars");
#endif
        response[255] = 0;
    }
            
    if (packetPtr->data[pos] == kReturnChar)
    {
            // we have found the end of the default response 
            // catenate a line feed character to the  response 
    
        ccatchr(response, kLineFeedChar, 1);
 
        otErr = OTSnd((packetPtr->theEp)->ep, response, clen(response), T_MORE);
        if (otErr < 0)
        {
#if SHOW_DEBUG_FLOW
            DoValueBreak((long)otErr, "error occured sending PS response #");
#endif
        }
        packetPtr->lastPos = pos;
        result = kMatch;
    }
 
#if SHOW_DEBUG_FLOW
    ccatchr(response, ';', 1);
    ccatchr(response, 'g', 1);
    c2p(response);
//  DebugStr((const unsigned char*)response);
#endif
 
    
    return result;
}
 
/*
    SendEmptyPacket is implemented to send an empty packet with the T_MORE flag not set.
    The Apple LaserWriter client requires that the last response to a Postscript query
    have the EOF flag set.  By having the EOM option enabled and sending a packet with 
    the T_MORE flag not set, OT/PAP will send an empty packet that has the EOF bit set.
*/
void SendEmptyPacket(PacketPtr packetPtr)
{
    OTResult    err;
    char        response[16];
 
    err = OTSnd((packetPtr->theEp)->ep, &response, 0, 0);
    if (err != kOTNoError)
    {
#if SHOW_DEBUG_FLOW
        DoValueBreak((long)err, "error occured sending empty packet #");
#endif
    }
    
}