SampleFilter/SampleFilterOutput.c

/*
**  File:           SampleFilterOutput.c
**
**  Description:    The filter output routines for the SampleFilter 
**
**  Version:        1.0
**
**  Copyright 1999 Apple Computer. All rights reserved.
**
**  You may incorporate this sample code into your applications without
**  restriction, though the sample code has been provided "AS IS" and the
**  responsibility for its operation is 100% yours.  However, what you are
**  not permitted to do is to redistribute the source as "ABC Sample 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 Code, but that you've made changes.
**
*/
 
/*************************
    This is a sample PostScript Output Filter for LaserWriter 8.7.
    
    This demonstrates:
    
    1) how to obtain data from the collection passed from the print dialog
    2) how to insert data into the output stream BEFORE a given subsection
    3) how to insert data into the output stream AFTER a given subsection
    4) how to remove data for a given subsection
    5) how to replace data for a given subsection
    6) how to detect a given page
*/
 
 
#include "PSOutputFilters.h"
#include "PSSectionInfo.h"
#include "DebugMisc.h"
#include "Macros.h"
#include "SampleFilterHints.h"
#include "Hints.h"
#include "PSStreams.h"          // for our formatted stream writes
 
#include <stdio.h>
#include <string.h>
        
/*** Private Macros ***/
 
/*  These defines are here to flag usage of these routines by a filter. A filter should not use
    these formatting routines because they do not handle the position data on the stream
    in a manner that works for a filter's output.   
*/
 
#define psOutFormatPosition             ERROR-DONTUSE
#define psOutFormatPositionInfo         ERROR-DONTUSE
#define psOutBlockPositionInfo          ERROR-DONTUSE
 
/*
    A macro to write a Pascal string to a filter with a given position.
*/
#define WritePStringToFilter(thefilter, string, position)   psWriteNextFilter((thefilter), &((string)[1]), (string)[0], (position))
 
/*  An error to return from our psOutputFilterPreFlight routine if we don't want our
    filter to run.
*/ 
enum{
    noNeedToRunFilterErr = -1
};
 
 
/*** Private Variable Types ***/
 
/*  Our output execution time filter data.
*/
typedef struct FilterData{
    SInt32          lastFilteredMsgId;          // the last messageID our filter saw            
    PSSubsection    lastSubsection;             // the last subsection our filter saw
    Collection      jobhints;                   // the collection containing the complete job collection
    SInt32          currentPageNumber;          // the current page number being processed
    PagesCommentMode pagesCommentMode;          // the comment mode specified by the UI
    Boolean         doCustomHeaders;            // did the UI specify that we add our custom header comment
    Boolean         notSkippingData;            // indicates whether or not to skip output of data
    Str255          forText;                    // replacement text for the %%For comment
}FilterData;
 
/*** Private Prototypes ***/
static OSStatus getOurCollection(Collection hints, CollectionTag tag, SInt32 id, Collection *subCollection);
static OSStatus handleFiltering(PSOutputFilterRef filter, FilterData *filterDataP, const void *data, long nBytes, const PSPosition *pos);
static OSStatus checkForOurHints(Collection hints, FilterData *myDataP);
 
/*** Global Data ***/
 
static const Byte psCommentAfterPageSetup[] = "\p% text just after Page Setup\r";
 
static const Byte psCustomHeaderComment[] = "\p%RBI_SampleComment: (Filtered by the Sample Filter)\r";
 
 
/*** Code ***/
 
OSStatus psOutputFilterPreFlight(PSOutputFilterRef filter, Collection jobInfo, Handle papaH, void **refconP)
/*  This routine is called by PrintingLib to see if the filter wants to be
    included in the filter chain for the current job. If we determine during this
    preflight that we either don't want to run or get some kind of error
    that prevents us from running, we return an error here and we won't be
    called during the generation of PostScript output and our psOutputFilterPostflight
    routine won't be called either.
    
    In the normal case we allocate our filter private data for this print job and configure ourselves and
    return noErr.
*/
{
    OSStatus err = noErr;
    FilterData *filterDataP;
    Unused(filter);
    Unused(papaH);
 
    filterDataP = (FilterData *)NewPtrClear(sizeof(FilterData));        // this initializes all our data to 0
    if(filterDataP){
        filterDataP->notSkippingData = true;                            // initialize so that we are not skipping data
        filterDataP->jobhints = jobInfo;                                // save the job collection for later reference
        
        // check for our filter specific hints and configure ourselves accordingly
        err = checkForOurHints(jobInfo, filterDataP);
 
        if(err && filterDataP){
            // do NOT dispose of the hints collection we've got stored on filterDataP...we don't own it
             
            // if we got an error then clean up properly since our postflight routine won't be called
            DisposePtr((Ptr)filterDataP);
            filterDataP = NULL; 
        }
    }else
        err = MemError();
    
    *refconP = err ? NULL : filterDataP;
        
    return err;
    
}
 
OSStatus psOutputFilterPostFlight(PSOutputFilterRef filter, void *refcon)
/*  
    PrintingLib calls this routine when our filter is being removed from 
    the filter output chain. At this point we need to clean up.
    Here we deallocate the memory we've allocated during our execution.
*/
{
    OSStatus err = noErr;
    FilterData *filterDataP = (FilterData *)refcon;
    Unused(filter);
    
    // we should only be called here if we actually have data!
    ckAssert(filterDataP != NULL);
    if(filterDataP){
        // do NOT dispose of the hints collection we've got stored on filterDataP...we don't own it 
        DisposePtr((Ptr)filterDataP);
    }
    
    return err;
}
 
OSStatus psOutputFilterWrite(PSOutputFilterRef filter, void *refcon, const void *data, long nBytes, const PSPosition *posP)
/*  Filter the PostScript data being sent through us.
*/
{
    OSStatus err = noErr;
    FilterData *filterDataP = (FilterData *)refcon;
    
    ckAssert(filterDataP != NULL);
    
    // only examine the data if the caller is not flushing the output stream
    if(nBytes != FLUSHWRITE){
        /*  We need only look at new message IDs. If the message id associated with posP passed to us is the
            same as the last one we saw, then we have already done our processing for that posP and
            we don't need to do any new examination of the data. 
        */
        if (filterDataP->lastFilteredMsgId != posP->id) {
            // update our notion of the last id we saw
            filterDataP->lastFilteredMsgId = posP->id;
            err = handleFiltering(filter, filterDataP, data, nBytes, posP);
        }else{
            if(filterDataP->notSkippingData)
                err = psWriteNextFilter(filter, data, nBytes, posP);
        }
 
        // record the PSSubsection we are seeing now
        filterDataP->lastSubsection = posP->subsection;
    }else{
        // if the caller is flushing the output stream then we always write to the next filter and return any error
        err = psWriteNextFilter(filter, data, nBytes, posP);
    }
    
    return err;
}
 
static OSStatus handleFiltering(PSOutputFilterRef filter, FilterData *filterDataP, const void *data, 
                                    long nBytes, const PSPosition *posP)
{
    OSStatus err = noErr;
 
    /*
            This filter only filters output during the main job, not the coverpage or query job so we
            check the section before deciding whether to try and filter.
    */
    if(posP->section == kSectJob){
        PSPosition myPosition;
        
        /************ 
            Before considering any data for the current subsection, we must first generate any data
            we want to insert immediately AFTER the end of the last previously seen subsection.
            
            This is handled in this manner so that we are certain that all of the data (of the
            subsection we want to insert immediately after) has already been completely emitted.    
        */
        switch(filterDataP->lastSubsection){
            case kSubBeginPageSetup:
                // We are just after %%BeginPageSetup and we potentially want to
                // generate our data, depending on our pages mode
                if(
                    (filterDataP->currentPageNumber == 1 && filterDataP->pagesCommentMode == kPagesFirst) || 
                    filterDataP->pagesCommentMode == kPagesAll
                ){
                    /*  
                        We are going to write our new data. In order to do so we need to 
                        have a PSPosition structure that is filled in with our data.
 
                        We call psFilterSetPSPosition to fill in myPosition as we specify.
                        We're filling in the section field with the current section, the subsection
                        field to kSubAnon since our data is anonymous and the info field to NULL. 
                        The psFilterSetPSPosition properly sets the id portion of the PSPosition
                        for a filter write.
                    */
                    err = psFilterSetPSPosition(filter, &myPosition, posP->section, kSubAnon, NULL);
                    if(!err)err = WritePStringToFilter(filter, psCommentAfterPageSetup, &myPosition);
                }
                    
                break;
 
            // case statements for any additional subsections that we want to 
            // insert data AFTER should be here
 
            default:
                break;
        
        }
 
        /* 
            Now that we have inserted any data after the last subsection, we can now insert any data 
            that we want to write BEFORE the current subsection is written. Here we also process
            the subsection that is currently being written.     
        */
        if(!err){
            switch(posP->subsection){
                case kSubPage:
                    /*
                        This subsection corresponds to a %%Page: comment. When it is written, there is normally
                        an 'info' structure that points to a DSCPage structure. If so, we'll pick up the
                        ordinal value of the current page number so we know what page is currently
                        being generated
                    */
                    ckAssert(posP->info != NULL);
                    if(posP->info != NULL){
                        filterDataP->currentPageNumber = ((DSCPage *)posP->info)->ordinal;
                    }
                    /*
                        If we see this subsection, we should make sure that we no longer 
                        are skipping the write of any data.
                    */
                    filterDataP->notSkippingData = true;
                    break;
                
                case kSubEndComments:
                    /* 
                        We are about to write %%EndComments so we should check
                        whether we want to write our custom header comments and
                        if so, then we need to emit them before we write the
                        existing data.                  
                    */
 
                    if(filterDataP->doCustomHeaders){
                        /*  
                            We call psFilterSetPSPosition to fill in myPosition as we specify.
                            We're filling in the section field with the current section, the subsection
                            field to kSubAnon since our data is anonymous and the info field to NULL. 
                            The psFilterSetPSPosition properly sets the id portion of the PSPosition
                            for a filter write.
                        */
                        err = psFilterSetPSPosition(filter, &myPosition, posP->section, kSubAnon, NULL);
 
                        if(!err)
                            err = WritePStringToFilter(filter, psCustomHeaderComment, &myPosition);
                    }
                    /*
                        If we see this subsection, we should make sure that we no longer 
                        are skipping the write of any data.
                    */
                    filterDataP->notSkippingData = true;
                    break;
 
                // we are going to replace the %%For comment with the user supplied text
                case kSubFor:
                    if(!err){
                        /*  
                            As an example, we are going to take advantage of the way that psOutFormat 
                            can format data. To do this we need a stream that can write to a filter 
                            and then make a StreamInfoData structure from it.
                        */
                        PSStream outFilterStream;
                        
                        /*  Open the stream. psOpenFilterOutputStream makes sure that writes to 
                            this stream will generate calls to psWriteNextFilter with the filter 
                            reference passed here.
                        */
                        err = psOpenFilterOutputStream(&outFilterStream, filter);
                        if(!err){
                            OSStatus tempErr;
                            StreamInfoData comm;
                            /*  Now make the StreamInfoData since we need to do that formatted writes using psOutFormat
                            
                                One important note is that it is crucial that the job hints that we pass to this routine
                                properly reflect the output channel characteristics so that psOutFormat correctly formats
                                the output for the channel. Once the channel query has been performed, the hints collection
                                properly indicates the channel characteristics and we can go ahead and make our call.                           
                            */
                            err = psSetupStreamInfoData(&comm, &outFilterStream, filterDataP->jobhints);
                            if(!err){
                                /*  we must set the position on the stream represented by comm so that we get a position
                                    properly set up for filter writes. Note that we *MUST* do this, RATHER than use
                                    the formatting routines psOutFormatPosition, psOutFormatPositionInfo,
                                    or psOutBlockPositionInfo.
                                    
                                    We set the section field to the section of the position passed to us and set the
                                    subsection to kSubFor and the info field is a Str255 of the text we are writing.
                                */
                                PSStream *theStreamP = psGetStreamFromStreamInfoData(comm);
                                err = psFilterSetPSPosition(filter, &theStreamP->u.ps.pos, 
                                                    posP->section, kSubFor, filterDataP->forText);
                                if(!err){
                                    /*  
                                        Our format string uses the ^S format since we want to ensure that the string
                                        data is always formatted for the printable ASCII character set regardless of the
                                        output channel characteristics.
                                        
                                        For more information on psOutFormat and the use of streams and doing
                                        formatted writes see:
 
                                        Technote 1171: LaserWriter 8.6: How to Write a Converter Plug-in for 
                                        the Download Manager, 
                                        http://devworld.apple.com/technotes/tn/tn1171.html#AppendixA
                                    */
                                    err = psOutFormat(comm, "\p%%For: (^S)\r", filterDataP->forText);
                                }
                            
                                tempErr = psDisposeStreamInfoData(&comm);
                                if(!err)err = tempErr;
                            }
                            tempErr = psCloseFilterOutputStream(&outFilterStream);
                            if(!err)err = tempErr;
                        }
 
                    }
                    /*  Make sure we skip any additional data until we see the next subsection.                     
                    */
                    filterDataP->notSkippingData = false;
                    break;
 
 
                /*  
                    We are going to completely omit the %%Routing comment so we set notSkippingData
                    accordingly. This will skip all output until we see a new messageID
                */
                case kSubRouting:
                    filterDataP->notSkippingData = false;
                    break;
                
                /*
                    For all other subsections we are going to no longer skip writing any data
                */
                default:
                    filterDataP->notSkippingData = true;
                    break;
                    
            }
        }
    }
    
    // write the data passed to us if we are not skipping data
    if(!err && filterDataP->notSkippingData)
        err = psWriteNextFilter(filter, data, nBytes, posP);
    
    return err;
}
 
static OSStatus checkForOurHints(Collection hints, FilterData *myDataP)
{
/*
    This routine looks for our filter's private collection in the 'hints'
    collection and configures itself based on the collection items it
    finds in our private collection.
*/
    OSStatus err = noErr;
    Collection plugInHints;
    
    // initialize to our defaults data
    myDataP->doCustomHeaders = kHintCustomHeaderDef;
    myDataP->forText[0] = 0;
    myDataP->pagesCommentMode = kHintPagesCommentDef;
    
    // first get our private collection based on our signature
    err = getOurCollection(hints, kHintPlugInCollTag, kSampleFilterSignature, &plugInHints);
    if(!err){
        ckAssert(plugInHints != NULL);      // our private collection should not be NULL
        /*
            Look for our configuration data. If any piece of it does not exist then we treat
            it as if the default value is specified and there is no error.
        */
        if(!err) {
            long actualSize = sizeof(Str255);
            err = GetCollectionItem(plugInHints, kHintTextTag, kHintTextId, &actualSize, myDataP->forText);
            if(err == collectionItemNotFoundErr){
                myDataP->forText[0] = 0;
                err = noErr;
            }
        }
        if(!err) {
            long actualSize = sizeof(kHintCustomHeaderVar);
            err = GetCollectionItem(plugInHints, kHintCustomHeaderTag, kHintCustomHeaderId, 
                                                        &actualSize, &myDataP->doCustomHeaders);
            if(err == collectionItemNotFoundErr){
                myDataP->doCustomHeaders = kHintCustomHeaderDef;
                err = noErr;
            }
        }
        if(!err) {
            long actualSize = sizeof(kHintPagesCommentVar);
            err = GetCollectionItem(plugInHints, kHintPagesCommentTag, kHintPagesCommentId, 
                                                        &actualSize, &myDataP->pagesCommentMode);
            if(err == collectionItemNotFoundErr){
                myDataP->pagesCommentMode = kHintPagesCommentDef;
                err = noErr;
            }
        }
        DisposeCollection(plugInHints);
    }else{
        if(err == collectionItemNotFoundErr){
            // if we don't find our private collection we'll go ahead and run with our defaults
            err = noErr;
        }
    }
    
    // we'll map any error here into our "do not run filter error". This isn't strictly
    // necessary...any error returned from psOutputFilterPreFlight is sufficient
    if(err)     
        err = noNeedToRunFilterErr;
    
    return err;
}
 
static OSStatus getOurCollection(Collection hints, CollectionTag tag, SInt32 id, Collection *subCollection)
/*  
    getOurCollection assumes that the entry in the collection 'hints' with tag 'tag' 
    and id 'id' contains a flattened collection. This routine gets that flattened 
    collection, unflattens it, and returns the new
    collection in *'subCollection'. If an error occurs then *'subCollection' is
    set to NULL and a non-zero error value is returned.
*/
{
    Collection newCollection = NULL;
    Handle flatCollectionH = NULL;
    OSStatus err = noErr;
    
    flatCollectionH = NewHandle(0);
    if (flatCollectionH != NULL) {
        err = GetCollectionItemHdl(hints, tag, id, flatCollectionH);
        if (!err){
            newCollection = NewCollection();
            if (newCollection != NULL) {
                err = UnflattenCollectionFromHdl(newCollection, flatCollectionH);
            } else {
                err = MemError();
            }
        }
        DisposeHandle(flatCollectionH);
    } else {
        err = MemError();
    }
    
    /*  If we had an error then get rid of the collection we
        were trying to make.
    */
    if (err) {
        if (newCollection != NULL) {
            DisposeCollection(newCollection);
            newCollection = NULL;       
        }
    }
    
    /*  Pass back the new collection. It will be NULL if
        there was an error.
    */
    *subCollection = newCollection;
        
    return err;
}