Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
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; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-03-26