/*++

Copyright (c) 1996-1997  Microsoft Corporation

Module Name:

    ppdparse.c

Abstract:

    Parser for converting PPD file from ASCII text to binary data

Environment:

    PostScript driver, PPD parser

Revision History:

    12/03/96 -davidx-
        Check binary file date against all source printer description files.

    09/30/96 -davidx-
        Cleaner handling of ManualFeed and AutoSelect feature.

    09/17/96 -davidx-
        Add link field to order dependency structure.

    08/22/96 -davidx-
        New binary data format for NT 5.0.

    08/20/96 -davidx-
        Common coding style for NT 5.0 drivers.

    03/26/96 -davidx-
        Created it.

--*/


#include "lib.h"
#include "ppd.h"
#include "ppdparse.h"
#include "ppdrsrc.h"

//
// Round up n to a multiple of m
//

#define ROUND_UP_MULTIPLE(n, m) ((((n) + (m) - 1) / (m)) * (m))

//
// Round up n to a multiple of sizeof(DWORD) = 4
//

#define DWORD_ALIGN(n) (((n) + 3) & ~3)

//
// Raise an exception to cause VPackBinaryData to fail
//

#define PACK_BINARY_DATA_EXCEPTION() RaiseException(0xC0000000, 0, 0, NULL);

//
// Display a semantic error message
//

#define SEMANTIC_ERROR(arg) { TERSE(arg); pParserData->bErrorFlag = TRUE; }

//
// Data structure to store meta-information about a printer feature
// Note that the default order dependency value is relative to MAX_ORDER_VALUE.
// Explicitly specified order value must be less than MAX_ORDER_VALUE.
//
// We assume all printer-sticky features have higher priority than
// all doc-sticky features. The priority values for printer-sticky
// feature must be >= PRNPROP_BASE_PRIORITY.
//

#define MAX_ORDER_VALUE         0x7fffffff
#define PRNPROP_BASE_PRIORITY   0x10000

typedef struct _FEATUREDATA {

    DWORD   dwFeatureID;        // predefined feature ID
    DWORD   dwOptionSize;       // size of the associated option structure
    DWORD   dwPriority;         // feature priority
    DWORD   dwFlags;            // feature flags

} FEATUREDATA, *PFEATUREDATA;


//
// Special code page value used internally in this file.
// Make sure they don't conflict with standard code page values.
//

#define CP_ERROR        0xffffffff
#define CP_UNICODE      0xfffffffe



PFEATUREDATA
PGetFeatureData(
    DWORD   dwFeatureID
    )

/*++

Routine Description:

    Return meta-information about the requested feature

Arguments:

    dwFeatureID - Specifies what feature the caller is interested in

Return Value:

    Pointer to a FEATUREDATA structure corresponding to the request feature

--*/

{
    static FEATUREDATA FeatureData[] =
    {
        { GID_RESOLUTION,     sizeof(RESOLUTION),  10,  0},
        { GID_PAGESIZE,       sizeof(PAGESIZE),    50,  0},
        { GID_PAGEREGION,     sizeof(OPTION),      40,  FEATURE_FLAG_NOUI},
        { GID_DUPLEX,         sizeof(DUPLEX),      20,  0},
        { GID_INPUTSLOT,      sizeof(INPUTSLOT),   30,  0},
        { GID_MEDIATYPE,      sizeof(MEDIATYPE),   10,  0},
        { GID_COLLATE,        sizeof(COLLATE),     10,  0},
        { GID_OUTPUTBIN,      sizeof(OUTPUTBIN),   10,  0},
        { GID_MEMOPTION,      sizeof(MEMOPTION),   10,  0},
        { GID_LEADINGEDGE,    sizeof(OPTION),      25,  FEATURE_FLAG_NOUI | FEATURE_FLAG_NOINVOCATION},
        { GID_USEHWMARGINS,   sizeof(OPTION),      25,  FEATURE_FLAG_NOUI | FEATURE_FLAG_NOINVOCATION},
        { GID_UNKNOWN,        sizeof(OPTION),       0,  0},
    };

    DWORD   dwIndex;

    for (dwIndex = 0; FeatureData[dwIndex].dwFeatureID != GID_UNKNOWN; dwIndex++)
    {
        if (FeatureData[dwIndex].dwFeatureID == dwFeatureID)
            break;
    }

    return &FeatureData[dwIndex];
}



VOID
VGrowPackBuffer(
    PPARSERDATA pParserData,
    DWORD       dwBytesNeeded
    )

/*++

Routine Description:

    Grow the buffer used to hold packed binary data if necessary

Arguments:

    pParserData - Points to parser data structure
    dwBytesNeeded - Number of bytes needed

Return Value:

    NONE

--*/

#define PACK_BUFFER_MAX 1024    // measured in number of pages

{
    VALIDATE_PARSER_DATA(pParserData);

    //
    // We need to commit more memory if the number of bytes needed plus the
    // number of bytes used is over the maximum number of bytes committed.
    //

    if ((dwBytesNeeded += pParserData->dwBufSize) > pParserData->dwCommitSize)
    {
        //
        // Check if we're being called for the first time.
        // In that case, we'll need to reserved the virtual address space.
        //

        if (pParserData->pubBufStart == NULL)
        {
            SYSTEM_INFO SystemInfo;
            PBYTE       pbuf;

            GetSystemInfo(&SystemInfo);
            pParserData->dwPageSize = SystemInfo.dwPageSize;

            pbuf = VirtualAlloc(NULL,
                                PACK_BUFFER_MAX * SystemInfo.dwPageSize,
                                MEM_RESERVE,
                                PAGE_READWRITE);

            if (pbuf == NULL)
            {
                ERR(("Cannot reserve memory: %d\n", GetLastError()));
                PACK_BINARY_DATA_EXCEPTION();
            }

            pParserData->pubBufStart = pbuf;
            pParserData->pInfoHdr = (PINFOHEADER) pbuf;
            pParserData->pUIInfo = (PUIINFO) (pbuf + sizeof(INFOHEADER));
            pParserData->pPpdData = (PPPDDATA) (pbuf + sizeof(INFOHEADER) + sizeof(UIINFO));
        }

        //
        // Make sure we're not overflowing
        //

        if (dwBytesNeeded > (PACK_BUFFER_MAX * pParserData->dwPageSize))
        {
            ERR(("Binary printer description is too big.\n"));
            PACK_BINARY_DATA_EXCEPTION();
        }

        //
        // Commit the extra amount of memory needed (rounded up
        // to the next page boundary). Note that the memory allocated
        // using VirtualAlloc is zero-initialized.
        //

        dwBytesNeeded -= pParserData->dwCommitSize;
        dwBytesNeeded = ROUND_UP_MULTIPLE(dwBytesNeeded, pParserData->dwPageSize);
        pParserData->dwCommitSize += dwBytesNeeded;

        if (! VirtualAlloc(pParserData->pubBufStart,
                           pParserData->dwCommitSize,
                           MEM_COMMIT,
                           PAGE_READWRITE))
        {
            ERR(("Cannot commit memory: %d\n", GetLastError()));
            PACK_BINARY_DATA_EXCEPTION();
        }
    }
}



PVOID
PvFindListItem(
    PVOID   pvList,
    PCSTR   pstrName,
    PDWORD  pdwIndex
    )

/*++

Routine Description:

    Find a named item from a linked-list

Arguments:

    pParserData - Points to parser data structure
    pstrName - Specifies the item name to be found
    pdwIndex - Points to a variable for returning a zero-based item index

Return Value:

    Points to the named listed item, NULL if the named item is not in the list

Note:

    We're not bothering with fancy data structures here because the parser
    is used infrequently to convert a ASCII printer description file to its
    binary version. After that, the driver will access binary data directly.

--*/

{
    PLISTOBJ pItem;
    DWORD    dwIndex;

    for (pItem = pvList, dwIndex = 0;
        pItem && strcmp(pItem->pstrName, pstrName) != EQUAL_STRING;
        pItem = pItem->pNext, dwIndex++)
    {
    }

    if (pdwIndex)
        *pdwIndex = dwIndex;

    return pItem;
}



DWORD
DwCountListItem(
    PVOID   pvList
    )

/*++

Routine Description:

    Count the number of items in a linked-list

Arguments:

    pvList - Points to a linked-list

Return Value:

    Number of items in a linked-list

--*/

{
    PLISTOBJ pItem;
    DWORD    dwCount;

    for (pItem = pvList, dwCount = 0;
        pItem != NULL;
        pItem = pItem->pNext, dwCount++)
    {
    }

    return dwCount;
}



VOID
VPackStringUnicode(
    PPARSERDATA pParserData,
    PTRREF     *ploDest,
    PWSTR       pwstrSrc
    )

/*++

Routine Description:

    Pack a Unicode string into the binary data file

Arguments:

    pParserData - Points to the parser data structure
    ploDest - Returns the byte offset of the packed Unicode string
    pwstrSrc - Specifies the source Unicode string to be packed

Return Value:

    NONE

--*/

{
    if (pwstrSrc == NULL)
        *ploDest = 0;
    else
    {
        DWORD   dwSize = (wcslen(pwstrSrc) + 1) * sizeof(WCHAR);

        VGrowPackBuffer(pParserData, dwSize);
        CopyMemory(pParserData->pubBufStart + pParserData->dwBufSize, pwstrSrc, dwSize);

        *ploDest = pParserData->dwBufSize;
        pParserData->dwBufSize += DWORD_ALIGN(dwSize);
    }
}



VOID
VPackStringRsrc(
    PPARSERDATA pParserData,
    PTRREF     *ploDest,
    INT         iStringId
    )

/*++

Routine Description:

    Pack a Unicode string resource into the binary data file

Arguments:

    pParserData - Points to the parser data structure
    ploDest - Returns the byte offset of the packed Unicode string
    iStringId - Specifies the resource ID of the Unicode string to be packed

Return Value:

    NONE

--*/

{
    WCHAR   awchBuffer[MAX_XLATION_LEN];

    if (! LoadString(ghInstance, iStringId, awchBuffer, MAX_XLATION_LEN))
        awchBuffer[0] = NUL;

    VPackStringUnicode(pParserData, ploDest, awchBuffer);
}



VOID
VPackStringAnsi(
    PPARSERDATA pParserData,
    PTRREF     *ploDest,
    PSTR        pstrSrc
    )

/*++

Routine Description:

    Pack an ANSI string into the binary data file

Arguments:

    pParserData - Points to the parser data structure
    ploDest - Returns the byte offset of the packed ANSI string
    pstrSrc - Specifies the source ANSI string to be packed

Return Value:

    NONE

--*/

{
    if (pstrSrc == NULL)
        *ploDest = 0;
    else
    {
        DWORD   dwSize = strlen(pstrSrc) + 1;

        VGrowPackBuffer(pParserData, dwSize);
        CopyMemory(pParserData->pubBufStart + pParserData->dwBufSize, pstrSrc, dwSize);

        *ploDest = pParserData->dwBufSize;
        pParserData->dwBufSize += DWORD_ALIGN(dwSize);
    }
}



INT
ITranslateToUnicodeString(
    PWSTR   pwstr,
    PCSTR   pstr,
    INT     iLength,
    UINT    uCodePage
    )

/*++

Routine Description:

    Translate an ANSI string to Unicode string

Arguments:

    pwstr - Buffer for storing Unicode string
    pstr - Pointer to ANSI string to be translated
    iLength - Length of ANSI string, in bytes
    uCodePage - Code page used to do the translation

Return Value:

    Number of Unicode characters translated
    0 if there is an error

--*/

{
    ASSERT(iLength >= 0);

    if (uCodePage == CP_UNICODE)
    {
        INT i;

        //
        // Make sure the Unicode translation string has even number of bytes
        //

        if (iLength & 1)
        {
            TERSE(("Odd number of bytes in Unicode translation string.\n"));
            iLength--;
        }

        //
        // We assume Unicode values are specified in big-endian format in
        // the PPD file. Internally we store Unicode values in little-endian
        // format. So we need to swap bytes here.
        //

        iLength /= sizeof(WCHAR);

        for (i=iLength; i--; pstr += 2)
            *pwstr++ = (pstr[0] << 8) | ((BYTE) pstr[1]);
    }
    else
    {
        if (uCodePage == CP_ERROR)
            uCodePage = CP_ACP;

        iLength = MultiByteToWideChar(uCodePage, 0, pstr, iLength, pwstr, iLength);

        ASSERT(iLength >= 0);
    }

    return iLength;
}



VOID
VPackStringAnsiToUnicode(
    PPARSERDATA pParserData,
    PTRREF     *ploDest,
    PSTR        pstrSrc,
    INT         iLength
    )

/*++

Routine Description:

    Convert an ANSI string to Unicode and pack it into the binary data file

Arguments:

    pParserData - Points to the parser data structure
    ploDest - Returns the byte offset of the packed Unicode string
    pstrSrc - Specifies the source ANSI string to be packed
    iLength - Specifies the byte length of the ANSI string

Return Value:

    NONE

--*/

{
    INT     iSize;
    PTSTR   ptstr;

    //
    // Source string is NULL
    //

    if (pstrSrc == NULL)
    {
        *ploDest = 0;
        return;
    }

    //
    // If source string length is -1, it means
    // the source string is null-terminated.
    //

    if (iLength == -1)
        iLength = strlen(pstrSrc);

    if (pParserData->uCodePage == CP_UNICODE)
    {
        //
        // Source string is Unicode string
        //

        iSize = iLength + sizeof(WCHAR);
    }
    else
    {
        //
        // Source string is ANSI string
        //

        iSize = (iLength + 1) * sizeof(WCHAR);
    }

    VGrowPackBuffer(pParserData, iSize);
    ptstr = (PTSTR) (pParserData->pubBufStart + pParserData->dwBufSize);
    *ploDest = pParserData->dwBufSize;
    pParserData->dwBufSize += DWORD_ALIGN(iSize);

    ITranslateToUnicodeString(ptstr, pstrSrc, iLength, pParserData->uCodePage);
}



VOID
VPackStringXlation(
    PPARSERDATA pParserData,
    PTRREF     *ploDest,
    PSTR        pstrName,
    PINVOCOBJ   pXlation
    )

/*++

Routine Description:

    Figure out the display name of an item, convert it from ANSI
    to Unicode string, and pack it into the binary data

Arguments:

    pParserData - Points to the parser data structure
    ploDest - Returns the byte offset of the packed Unicode string
    pstrName - Specifies the name string associated with the item
    pXlation - Specifies the translation string associated with the item

Return Value:

    NONE

--*/

{
    //
    // The display name of an item is its translation string if there is one.
    // Otherwise, the display name is the same as the name of the item.
    //
    // If the translation is present, use the current language encoding
    // to convert it to Unicode. Otherwise, we always use the ISOLatin1
    // encoding to convert the name of the item to Unicode.
    //

    if (pXlation && pXlation->pvData && pParserData->uCodePage != CP_ERROR)
        VPackStringAnsiToUnicode(pParserData, ploDest, pXlation->pvData, pXlation->dwLength);
    else
    {
        UINT uCodePage = pParserData->uCodePage;

        pParserData->uCodePage = 1252;
        VPackStringAnsiToUnicode(pParserData, ploDest, pstrName, -1);
        pParserData->uCodePage = uCodePage;
    }
}



VOID
VPackInvocation(
    PPARSERDATA pParserData,
    PINVOCATION pInvocation,
    PINVOCOBJ   pInvocObj
    )

/*++

Routine Description:

    Pack an invocation string into the binary data

Arguments:

    pParserData - Points to the parser data structure
    pInvocation - Returns information about the packed invocation string
    pInvocObj - Points to the invocation string to be packed

Return Value:

    NONE

--*/

{
    if (IS_SYMBOL_INVOC(pInvocObj))
    {
        //
        // The invocation is a symbol reference
        //

        PSYMBOLOBJ  pSymbol = pInvocObj->pvData;

        pInvocation->dwCount = pSymbol->Invocation.dwLength;

        //
        // For symbol invocation, Invocation.pvData actually stores the
        // 32-bit offset value (See function VPackSymbolDefinitions), so
        // it's safe to cast it into ULONG/DWORD.
        //

        pInvocation->loOffset = (PTRREF) PtrToUlong(pSymbol->Invocation.pvData);
    }
    else if (pInvocObj->dwLength == 0)
    {
        pInvocation->dwCount = 0;
        pInvocation->loOffset = 0;
    }
    else
    {
        //
        // Notice that we're always padding a zero byte at the end of
        // the invocation string. This byte is not counted in dwLength.
        //

        VGrowPackBuffer(pParserData, pInvocObj->dwLength+1);

        CopyMemory(pParserData->pubBufStart + pParserData->dwBufSize,
                   pInvocObj->pvData,
                   pInvocObj->dwLength);

        pInvocation->dwCount = pInvocObj->dwLength;
        pInvocation->loOffset = pParserData->dwBufSize;
        pParserData->dwBufSize += DWORD_ALIGN(pInvocObj->dwLength+1);
    }
}


VOID
VPackPatch(
    PPARSERDATA pParserData,
    PJOBPATCHFILE     pPackedPatch,
    PJOBPATCHFILEOBJ  pPatchObj
    )

/*++

Routine Description:

    Pack an job file patch invocation string into the binary data

Arguments:

    pParserData - Points to the parser data structure
    pInvocation - Returns information about the packed invocation string
    pInvocObj - Points to the invocation string to be packed

Return Value:

    NONE

--*/

{
    if (pPatchObj->Invocation.dwLength == 0)
    {
        pPackedPatch->dwCount = 0;
        pPackedPatch->loOffset = 0;
    }
    else
    {
        //
        // Notice that we're always padding a zero byte at the end of
        // the invocation string. This byte is not counted in dwLength.
        //

        VGrowPackBuffer(pParserData, pPatchObj->Invocation.dwLength+1);

        CopyMemory(pParserData->pubBufStart + pParserData->dwBufSize,
                   pPatchObj->Invocation.pvData,
                   pPatchObj->Invocation.dwLength);

        pPackedPatch->loOffset = pParserData->dwBufSize;
        pPackedPatch->dwCount = pPatchObj->Invocation.dwLength;

        pParserData->dwBufSize += DWORD_ALIGN(pPatchObj->Invocation.dwLength+1);
    }

    pPackedPatch->lJobPatchNo = pPatchObj->lPatchNo;
}



VOID
VPackSymbolDefinitions(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Pack all symbol definitions into the binary data

Arguments:

    pParserData - Points to the parser data structure

Return Value:

    NONE

--*/

{
    PINVOCOBJ   pInvocObj;
    PSYMBOLOBJ  pSymbol;

    VALIDATE_PARSER_DATA(pParserData);

    for (pSymbol = pParserData->pSymbols;
        pSymbol != NULL;
        pSymbol = pSymbol->pNext)
    {
        pInvocObj = &pSymbol->Invocation;
        ASSERT(! IS_SYMBOL_INVOC(pInvocObj));

        if (pInvocObj->dwLength == 0)
            pInvocObj->pvData = NULL;
        else
        {
            //
            // Notice that we're always padding a zero byte at the end of
            // the invocation string. This byte is not counted in dwLength.
            //

            VGrowPackBuffer(pParserData, pInvocObj->dwLength+1);

            CopyMemory(pParserData->pubBufStart + pParserData->dwBufSize,
                       pInvocObj->pvData,
                       pInvocObj->dwLength);

            pInvocObj->pvData = (PVOID)ULongToPtr(pParserData->dwBufSize);
            pParserData->dwBufSize += DWORD_ALIGN(pInvocObj->dwLength+1);
        }
    }
}



VOID
VResolveSymbolInvocation(
    PPARSERDATA pParserData,
    PINVOCOBJ   pInvocObj
    )

/*++

Routine Description:

    Check if an invocation string is a symbol reference and resolve it if necessary

Arguments:

    pParserData - Points to the parser data structure
    pInvocObj - Specifies the invocation string to be resolved

Return Value:

    NONE

--*/

{
    if (IS_SYMBOL_INVOC(pInvocObj))
    {
        PSTR        pstrName;
        PSYMBOLOBJ  pSymbol;

        pstrName = (PSTR) pInvocObj->pvData;

        if ((pSymbol = PvFindListItem(pParserData->pSymbols, pstrName, NULL)) == NULL)
        {
            SEMANTIC_ERROR(("Undefined symbol: %s\n", pstrName));
            pInvocObj->dwLength = 0;
            pInvocObj->pvData = NULL;
        }
        else
            pInvocObj->pvData = (PVOID) pSymbol;
    }
}



VOID
VResolveSymbolReferences(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Resolve all symbol references in the parsed PPD data

Arguments:

    pParserData - Points to the parser data structure

Return Value:

    NONE

--*/

{
    PFEATUREOBJ pFeature;
    POPTIONOBJ  pOption;
    PJOBPATCHFILEOBJ  pJobPatchFile;

    VALIDATE_PARSER_DATA(pParserData);

    VResolveSymbolInvocation(pParserData, &pParserData->Password);
    VResolveSymbolInvocation(pParserData, &pParserData->ExitServer);
    VResolveSymbolInvocation(pParserData, &pParserData->PatchFile);
    VResolveSymbolInvocation(pParserData, &pParserData->JclBegin);
    VResolveSymbolInvocation(pParserData, &pParserData->JclEnterPS);
    VResolveSymbolInvocation(pParserData, &pParserData->JclEnd);
    VResolveSymbolInvocation(pParserData, &pParserData->ManualFeedFalse);

    for (pFeature = pParserData->pFeatures;
        pFeature != NULL;
        pFeature = pFeature->pNext)
    {
        VResolveSymbolInvocation(pParserData, &pFeature->QueryInvoc);

        for (pOption = pFeature->pOptions;
            pOption != NULL;
            pOption = pOption->pNext)
        {
            VResolveSymbolInvocation(pParserData, &pOption->Invocation);
        }
    }

    for (pJobPatchFile = pParserData->pJobPatchFiles;
        pJobPatchFile != NULL;
        pJobPatchFile = pJobPatchFile->pNext)
    {
        VResolveSymbolInvocation(pParserData, &pJobPatchFile->Invocation);
    }
}



BOOL
BFindUIConstraintFeatureOption(
    PPARSERDATA pParserData,
    PCSTR       pstrKeyword,
    PFEATUREOBJ *ppFeature,
    PDWORD      pdwFeatureIndex,
    PCSTR       pstrOption,
    POPTIONOBJ  *ppOption,
    PDWORD      pdwOptionIndex
    )

/*++

Routine Description:

    Find the feature/option specified in UIConstraints and OrderDependency entries

Arguments:

    pParserData - Points to the parser data structure
    pstrKeyword - Specifies the feature keyword string
    ppFeature - Return a pointer to the feature structure found
    pdwFeatureIndex - Return the index of the feature found
    pstrOption - Specifies the option keyword string
    ppOption - Return a pointer to the option structure found
    pdwOptionIndex - Return the index of the option found

Return Value:

    TRUE if successful, FALSE if the specified feature/option is not found

--*/

{
    if (! (pstrKeyword = PstrStripKeywordChar(pstrKeyword)))
        return FALSE;

    //
    // HACK:
    //  replace *ManualFeed True option with *InputSlot ManualFeed option
    //  replace *CustomPageSize True option with *PageSize CustomPageSize option
    //

    if ((strcmp(pstrKeyword, gstrManualFeedKwd) == EQUAL_STRING) &&
        (*pstrOption == NUL ||
         strcmp(pstrOption, gstrTrueKwd) == EQUAL_STRING ||
         strcmp(pstrOption, gstrOnKwd) == EQUAL_STRING))
    {
        pstrKeyword = gstrInputSlotKwd;
        pstrOption = gstrManualFeedKwd;
    }
    else if ((strcmp(pstrKeyword, gstrCustomSizeKwd) == EQUAL_STRING) &&
             (*pstrOption == NUL || strcmp(pstrOption, gstrTrueKwd) == EQUAL_STRING))
    {
        pstrKeyword = gstrPageSizeKwd;
        pstrOption = gstrCustomSizeKwd;
    }
    else if (strcmp(pstrKeyword, gstrVMOptionKwd) == EQUAL_STRING)
        pstrKeyword = gstrInstallMemKwd;

    //
    // Find the specified feature
    //

    if (! (*ppFeature = PvFindListItem(pParserData->pFeatures, pstrKeyword, pdwFeatureIndex)))
        return FALSE;

    //
    // Find the specified option
    //

    if (*pstrOption)
    {
        return (*ppOption = PvFindListItem((*ppFeature)->pOptions,
                                           pstrOption,
                                           pdwOptionIndex)) != NULL;
    }
    else
    {
        *ppOption = NULL;
        *pdwOptionIndex = OPTION_INDEX_ANY;
        return TRUE;
    }
}



VOID
VPackUIConstraints(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Pack UIConstraints information into binary data

Arguments:

    pParserData - Points to the parser data structure

Return Value:

    NONE

--*/

{
    PUICONSTRAINT   pPackedConstraint;
    PFEATUREOBJ     pFeature;
    POPTIONOBJ      pOption;
    PLISTOBJ        pConstraint;
    DWORD           dwConstraints, dwConstraintBufStart;

    VALIDATE_PARSER_DATA(pParserData);

    //
    // By default, there is no constaint for all features and options
    //

    for (pFeature = pParserData->pFeatures;
        pFeature != NULL;
        pFeature = pFeature->pNext)
    {
        pFeature->dwConstraint = NULL_CONSTRAINT;

        for (pOption = pFeature->pOptions;
            pOption != NULL;
            pOption = pOption->pNext)
        {
            pOption->dwConstraint = NULL_CONSTRAINT;
        }
    }

    //
    // Count the number of *UIConstraints entries
    //

    dwConstraints = DwCountListItem(pParserData->pUIConstraints);

    if (dwConstraints == 0)
        return;
    //
    // Don't yet grow the buffer, we only number the number of constraints after we
    // evaluated the *ManualFeed: False constraints. pPackedConstraint points right
    // after the end of the current buffer
    //
    pPackedConstraint = (PUICONSTRAINT) (pParserData->pubBufStart + pParserData->dwBufSize);
    dwConstraintBufStart = pParserData->dwBufSize;

    //
    // Interpret each *UIConstraints entry
    //

    dwConstraints = 0;

    for (pConstraint = pParserData->pUIConstraints;
        pConstraint != NULL;
        pConstraint = pConstraint->pNext)
    {
        PFEATUREOBJ pFeature2;
        POPTIONOBJ  pOption2;
        DWORD       dwFeatureIndex, dwOptionIndex, dwManFeedFalsePos = 0;
        CHAR        achWord1[MAX_WORD_LEN];
        CHAR        achWord2[MAX_WORD_LEN];
        CHAR        achWord3[MAX_WORD_LEN];
        CHAR        achWord4[MAX_WORD_LEN];
        PSTR        pstr = pConstraint->pstrName;
        BOOL        bSuccess = FALSE;

        //
        // The value for a UIConstraints entry consists of four separate components:
        //  featureName1 [optionName1] featureName2 [optionName2]
        //

        (VOID) BFindNextWord(&pstr, achWord1);

        if (IS_KEYWORD_CHAR(*pstr))
            achWord2[0] = NUL;
        else
            (VOID) BFindNextWord(&pstr, achWord2);

        (VOID) BFindNextWord(&pstr, achWord3);
        (VOID) BFindNextWord(&pstr, achWord4);

        //
        // hack the *ManualFeed False constraints
        //
        if ((IS_KEYWORD_CHAR(achWord1[0])) &&
            (strcmp(&(achWord1[1]), gstrManualFeedKwd) == EQUAL_STRING) &&
            (strcmp(achWord2, gstrFalseKwd) == EQUAL_STRING))
        {
            //
            // check the validity of the constraint feature/option. Fall through if invalid
            //
            if (BFindUIConstraintFeatureOption(pParserData,
                                               achWord3,
                                               &pFeature,
                                               &dwFeatureIndex,
                                               achWord4,
                                               &pOption,
                                               &dwOptionIndex))
                dwManFeedFalsePos = 1;
        }
        else if ((IS_KEYWORD_CHAR(achWord3[0])) &&
                 (strcmp(&(achWord3[1]), gstrManualFeedKwd) == EQUAL_STRING) &&
                 (strcmp(achWord4, gstrFalseKwd) == EQUAL_STRING))
        {
            //
            // check the validity of the constraint feature/option. Fall through if invalid
            //
            if (BFindUIConstraintFeatureOption(pParserData,
                                               achWord1,
                                               &pFeature,
                                               &dwFeatureIndex,
                                               achWord2,
                                               &pOption,
                                               &dwOptionIndex))
                dwManFeedFalsePos = 2;

        }
        if (dwManFeedFalsePos)
        {
            //
            // get the index of the manual feed input slot
            //
            DWORD dwInputSlotFeatIndex, dwManFeedSlotIndex, dwInputSlotCount, dwSlotIndex;
            PFEATUREOBJ pInputSlotFeature;

            if ((pInputSlotFeature = PvFindListItem(pParserData->pFeatures, gstrInputSlotKwd, &dwInputSlotFeatIndex)) == NULL)
            {
                ERR(("Input slot feature not found !!!"));
                continue;
            }

            //
            // get the number of input slots. Note that this includes the dummy "*UseFormTrayTable" slot.
            //
            dwInputSlotCount = DwCountListItem((PVOID) pInputSlotFeature->pOptions);

            if (dwInputSlotCount <= 2) // to make sense there must be at least 3 slot, incl. UseFormTrayTable+ManualFeed
            {
                ERR(("ManualFeed used - internally at least 3 input slots expected !"));
                continue;
            }

            //
            // grow the buffer for constraints. Two less than input slots because
            //      1 input slot is the dummy UseFormTrayTable slot
            //      1 input slot is the ManualFeed slot that I don't want to constraint
            //
            VGrowPackBuffer(pParserData, (dwInputSlotCount -2) * sizeof(UICONSTRAINT));

            if (dwManFeedFalsePos == 1)
            {
                //
                // add constraints to each input slot for the constrained feature
                //
                POPTIONOBJ pNextObj = pInputSlotFeature->pOptions;

                ASSERT(strcmp(pNextObj->pstrName, "*UseFormTrayTable") == EQUAL_STRING); // in case we change the logic some time later...

                //
                // since the UseFormTrayTable is the first option, start with the second
                //
                pNextObj = pNextObj->pNext;
                ASSERT(pNextObj != NULL);

                while (pNextObj)
                {
                    //
                    // skip the manual feed input slot, don't constrain that
                    //
                    if (strcmp(pNextObj->pstrName, gstrManualFeedKwd) == EQUAL_STRING)
                    {
                        pNextObj = pNextObj->pNext;
                        continue;
                    }

                    pPackedConstraint[dwConstraints].dwNextConstraint = pNextObj->dwConstraint;
                    pNextObj->dwConstraint = dwConstraints;

                    pPackedConstraint[dwConstraints].dwFeatureIndex = dwFeatureIndex;
                    pPackedConstraint[dwConstraints].dwOptionIndex = dwOptionIndex;
                    dwConstraints++;

                    pNextObj = pNextObj->pNext;
                }
            }
            else
            {
                //
                // find the option index of the manual feed slot
                //
                if (PvFindListItem(pInputSlotFeature->pOptions, gstrManualFeedKwd, &dwManFeedSlotIndex) == NULL)
                {
                    ERR(("ManualFeed slot not found among InputSlots !!!"));
                    continue;
                }

                //
                // add constraints to the affected feature for all input slots BUT the manual feed slot
                // and the UseFormTrayTable slot
                // start with slot index 1, because the first slot is always *UseFormTrayTable
                //
                for (dwSlotIndex = 1; dwSlotIndex < dwInputSlotCount; dwSlotIndex++)
                {
                    if (dwSlotIndex == dwManFeedSlotIndex)
                        continue;

                    if (pOption == NULL)
                    {
                        //
                        // OptionKeyword1 field is not present
                        //

                        pPackedConstraint[dwConstraints].dwNextConstraint = pFeature->dwConstraint;
                        pFeature->dwConstraint = dwConstraints;
                    }
                    else
                    {
                        //
                        // OptionKeyword1 field is present
                        //

                        pPackedConstraint[dwConstraints].dwNextConstraint = pOption->dwConstraint;
                        pOption->dwConstraint = dwConstraints;
                    }

                    pPackedConstraint[dwConstraints].dwFeatureIndex = dwInputSlotFeatIndex;
                    pPackedConstraint[dwConstraints].dwOptionIndex = dwSlotIndex;
                    dwConstraints++;
                }
            }

            //
            // increase the committed buffer size so additional VGrowPackBuffer calls can allocate
            // additional pages if needed for more *ManualFeed False constraints
            //
            pParserData->dwBufSize += DWORD_ALIGN((dwInputSlotCount -2) * sizeof(UICONSTRAINT));

            continue;
        } // back to the normal course of events.

        if (BFindUIConstraintFeatureOption(pParserData,
                                           achWord1,
                                           &pFeature,
                                           &dwFeatureIndex,
                                           achWord2,
                                           &pOption,
                                           &dwOptionIndex) &&
            BFindUIConstraintFeatureOption(pParserData,
                                           achWord3,
                                           &pFeature2,
                                           &dwFeatureIndex,
                                           achWord4,
                                           &pOption2,
                                           &dwOptionIndex))
        {
            VGrowPackBuffer(pParserData, sizeof(UICONSTRAINT));

            if (pOption == NULL)
            {
                //
                // OptionKeyword1 field is not present
                //

                pPackedConstraint[dwConstraints].dwNextConstraint = pFeature->dwConstraint;
                pFeature->dwConstraint = dwConstraints;
            }
            else
            {
                //
                // OptionKeyword1 field is present
                //

                pPackedConstraint[dwConstraints].dwNextConstraint = pOption->dwConstraint;
                pOption->dwConstraint = dwConstraints;
            }

            pPackedConstraint[dwConstraints].dwFeatureIndex = dwFeatureIndex;
            pPackedConstraint[dwConstraints].dwOptionIndex = dwOptionIndex;

            dwConstraints++;
            bSuccess = TRUE;

            //
            // increase the committed buffer size so additional VGrowPackBuffer calls can allocate
            // additional pages if needed for more *ManualFeed False constraints
            //
            pParserData->dwBufSize += DWORD_ALIGN(sizeof(UICONSTRAINT));

        }

        if (! bSuccess)
            SEMANTIC_ERROR(("Invalid *UIConstraints entry: %s\n", pConstraint->pstrName));
    }

    //
    // Save the packed UIConstraints information in the binary data
    //

    if (dwConstraints == 0)
    {
        pParserData->pUIInfo->UIConstraints.dwCount = 0;
        pParserData->pUIInfo->UIConstraints.loOffset = 0;
    }
    else
    {
        pParserData->pUIInfo->UIConstraints.dwCount = dwConstraints;
        pParserData->pUIInfo->UIConstraints.loOffset = dwConstraintBufStart;
    }
}



VOID
VPackOrderDependency(
    PPARSERDATA pParserData,
    PARRAYREF   parefDest,
    PLISTOBJ    pOrderDep
    )

/*++

Routine Description:

    Pack OrderDependency/QueryOrderDependency information into binary data

Arguments:

    pParserData - Points to the parser data structure
    parefDest - Stores information about where the order dependency info is packed
    pOrderDep - Specifies the list of order dependencies to be packed

Return Value:

    NONE

--*/

{
    static const STRTABLE SectionStrs[] =
    {
        { "DocumentSetup",  SECTION_DOCSETUP},
        { "AnySetup",       SECTION_ANYSETUP},
        { "PageSetup",      SECTION_PAGESETUP},
        { "Prolog",         SECTION_PROLOG},
        { "ExitServer",     SECTION_EXITSERVER},
        { "JCLSetup",       SECTION_JCLSETUP},
        { NULL,             SECTION_UNASSIGNED}
    };

    PORDERDEPEND    pPackedDep;
    PFEATUREOBJ     pFeature;
    POPTIONOBJ      pOption;
    DWORD           dwOrderDep, dwFeatures, dwIndex;
    DWORD           dwFeatureIndex, dwOptionIndex, dwSection;
    LONG            lOrder;

    VALIDATE_PARSER_DATA(pParserData);

    //
    // The maximum number of entries we need is:
    //  number of printer features + number of order dependency entries
    //

    dwFeatures = pParserData->pInfoHdr->RawData.dwDocumentFeatures +
                 pParserData->pInfoHdr->RawData.dwPrinterFeatures;

    dwOrderDep = dwFeatures + DwCountListItem(pOrderDep);
    VGrowPackBuffer(pParserData, dwOrderDep * sizeof(ORDERDEPEND));
    pPackedDep = (PORDERDEPEND) (pParserData->pubBufStart + pParserData->dwBufSize);

    //
    // Create a default order dependency entry for each feature
    //

    for (pFeature = pParserData->pFeatures, dwFeatureIndex = 0;
        pFeature != NULL;
        pFeature = pFeature->pNext, dwFeatureIndex++)
    {
        pPackedDep[dwFeatureIndex].lOrder = MAX_ORDER_VALUE;
        pPackedDep[dwFeatureIndex].dwSection = SECTION_UNASSIGNED;
        pPackedDep[dwFeatureIndex].dwPPDSection = SECTION_UNASSIGNED;
        pPackedDep[dwFeatureIndex].dwFeatureIndex = dwFeatureIndex;
        pPackedDep[dwFeatureIndex].dwOptionIndex = OPTION_INDEX_ANY;
    }

    //
    // Interpret each order dependency entry
    //

    for (dwOrderDep = dwFeatures; pOrderDep != NULL; pOrderDep = pOrderDep->pNext)
    {
        CHAR    achWord1[MAX_WORD_LEN];
        CHAR    achWord2[MAX_WORD_LEN];
        PSTR    pstr = pOrderDep->pstrName;
        BOOL    bSuccess = FALSE;

        //
        // Each order dependency entry has the following components:
        //  order section mainKeyword [optionKeyword]
        //

        if (BGetFloatFromString(&pstr, &lOrder, FLTYPE_INT) &&
            BFindNextWord(&pstr, achWord1) &&
            BSearchStrTable(SectionStrs, achWord1, &dwSection) &&
            BFindNextWord(&pstr, achWord1))
        {
            (VOID) BFindNextWord(&pstr, achWord2);

            if (BFindUIConstraintFeatureOption(pParserData,
                                               achWord1,
                                               &pFeature,
                                               &dwFeatureIndex,
                                               achWord2,
                                               &pOption,
                                               &dwOptionIndex))
            {
                //
                // Check if an OrderDependency for the same feature/option
                // has appeared before.
                //

                for (dwIndex = 0; dwIndex < dwOrderDep; dwIndex++)
                {
                    if (pPackedDep[dwIndex].dwFeatureIndex == dwFeatureIndex &&
                        pPackedDep[dwIndex].dwOptionIndex == dwOptionIndex)
                    {
                        break;
                    }
                }

                if (dwIndex < dwOrderDep && pPackedDep[dwIndex].lOrder < MAX_ORDER_VALUE)
                {
                    TERSE(("Duplicate order dependency entry: %s\n", pOrderDep->pstrName));
                }
                else
                {
                    if (dwIndex >= dwOrderDep)
                        dwIndex = dwOrderDep++;

                    //
                    // Ensure the specified order value is less than MAX_ORDER_VALUE
                    //

                    if (lOrder >= MAX_ORDER_VALUE)
                    {
                        WARNING(("Order dependency value too big: %s\n", pOrderDep->pstrName));
                        lOrder = MAX_ORDER_VALUE - 1;
                    }

                    pPackedDep[dwIndex].dwSection = dwSection;
                    pPackedDep[dwIndex].dwPPDSection = dwSection;
                    pPackedDep[dwIndex].lOrder = lOrder;
                    pPackedDep[dwIndex].dwFeatureIndex = dwFeatureIndex;
                    pPackedDep[dwIndex].dwOptionIndex = dwOptionIndex;
                }

                bSuccess = TRUE;
            }
        }

        if (! bSuccess)
            SEMANTIC_ERROR(("Invalid order dependency: %s\n", pOrderDep->pstrName));
    }

    //
    // Tell the caller where the packed order dependency information is stored
    //

    if (dwOrderDep == 0)
    {
        parefDest->dwCount = 0;
        parefDest->loOffset = 0;
        return;
    }

    parefDest->dwCount = dwOrderDep;
    parefDest->loOffset = pParserData->dwBufSize;
    pParserData->dwBufSize += DWORD_ALIGN(dwOrderDep * sizeof(ORDERDEPEND));

    //
    // Sort order dependency information using the order value
    //

    for (dwIndex = 0; dwIndex+1 < dwOrderDep; dwIndex++)
    {
        DWORD   dwMinIndex, dwLoop;

        //
        // Nothing fancy here - straight-forward selection sort
        //

        dwMinIndex = dwIndex;

        for (dwLoop = dwIndex+1; dwLoop < dwOrderDep; dwLoop++)
        {
            if ((pPackedDep[dwLoop].lOrder < pPackedDep[dwMinIndex].lOrder) ||
                (pPackedDep[dwLoop].lOrder == pPackedDep[dwMinIndex].lOrder &&
                 pPackedDep[dwLoop].dwSection < pPackedDep[dwMinIndex].dwSection))
            {
                dwMinIndex = dwLoop;
            }
        }

        if (dwMinIndex != dwIndex)
        {
            ORDERDEPEND TempDep;

            TempDep = pPackedDep[dwIndex];
            pPackedDep[dwIndex] = pPackedDep[dwMinIndex];
            pPackedDep[dwMinIndex] = TempDep;
        }
    }

    //
    // Resolve AnySetup into either DocumentSetup or PageSetup
    //

    dwSection = SECTION_DOCSETUP;

    for (dwIndex = 0; dwIndex < dwOrderDep; dwIndex++)
    {
        if (pPackedDep[dwIndex].dwSection == SECTION_PAGESETUP)
            dwSection = SECTION_PAGESETUP;
        else if (pPackedDep[dwIndex].dwSection == SECTION_ANYSETUP)
            pPackedDep[dwIndex].dwSection = dwSection;
    }

    //
    // Maintain a linked-list of order dependency entries for each feature
    // starting with the entry whose dwOptionIndex = OPTION_INDEX_ANY.
    //

    for (dwIndex = 0; dwIndex < dwOrderDep; dwIndex++)
        pPackedDep[dwIndex].dwNextOrderDep = NULL_ORDERDEP;

    for (dwIndex = 0; dwIndex < dwOrderDep; dwIndex++)
    {
        DWORD   dwLastIndex, dwLoop;

        if (pPackedDep[dwIndex].dwOptionIndex != OPTION_INDEX_ANY)
            continue;

        dwLastIndex = dwIndex;

        for (dwLoop = 0; dwLoop < dwOrderDep; dwLoop++)
        {
            if (pPackedDep[dwLoop].dwFeatureIndex == pPackedDep[dwIndex].dwFeatureIndex &&
                pPackedDep[dwLoop].dwOptionIndex != OPTION_INDEX_ANY)
            {
                pPackedDep[dwLastIndex].dwNextOrderDep = dwLoop;
                dwLastIndex = dwLoop;
            }
        }

        pPackedDep[dwLastIndex].dwNextOrderDep = NULL_ORDERDEP;
    }

    //
    // !!!CR
    // Needs to flag out-of-order OrderDependency.
    //
}



VOID
VCountAndSortPrinterFeatures(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Count the number of doc- and printer-sticky features
    and sort them into two separate groups

Arguments:

    pParserData - Points to the parser data structure

Return Value:

    NONE

--*/

{
    PFEATUREOBJ pFeature, pNext, pDocFeatures, pPrinterFeatures;
    DWORD       dwDocFeatures, dwPrinterFeatures;

    VALIDATE_PARSER_DATA(pParserData);

    //
    // Count the number of doc- and printer-sticky features
    //

    pDocFeatures = pPrinterFeatures = NULL;
    dwDocFeatures = dwPrinterFeatures = 0;
    pFeature = pParserData->pFeatures;

    while (pFeature != NULL)
    {
        pNext = pFeature->pNext;

        if (pFeature->bInstallable)
        {
            pFeature->pNext = pPrinterFeatures;
            pPrinterFeatures = pFeature;
            dwPrinterFeatures++;
        }
        else
        {
            pFeature->pNext = pDocFeatures;
            pDocFeatures = pFeature;
            dwDocFeatures++;
        }

        pFeature = pNext;
    }

    ASSERTMSG((dwDocFeatures + dwPrinterFeatures <= MAX_PRINTER_OPTIONS),
              ("Too many printer features.\n"));

    //
    // Rearrange the features so that all doc-sticky features
    // are in front of printer-sticky features
    //

    pFeature = NULL;

    while (pPrinterFeatures != NULL)
    {
        pNext = pPrinterFeatures->pNext;
        pPrinterFeatures->pNext = pFeature;
        pFeature = pPrinterFeatures;
        pPrinterFeatures = pNext;
    }

    while (pDocFeatures != NULL)
    {
        pNext = pDocFeatures->pNext;
        pDocFeatures->pNext = pFeature;
        pFeature = pDocFeatures;
        pDocFeatures = pNext;
    }

    pParserData->pFeatures = pFeature;
    pParserData->pInfoHdr->RawData.dwDocumentFeatures = dwDocFeatures;
    pParserData->pInfoHdr->RawData.dwPrinterFeatures = dwPrinterFeatures;
}



VOID
VProcessPrinterFeatures(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Process printer features and handle any special glitches

Arguments:

    pParserData - Points to parser data structure

Return Value:

    NONE

--*/

{
    PFEATUREOBJ pFeature;
    POPTIONOBJ  pOption;

    for (pFeature = pParserData->pFeatures; pFeature; pFeature = pFeature->pNext)
    {
        //
        // If a feature has no option but has a default specified, then
        // synthesize an option with empty invocation string.
        //

        if (pFeature->pstrDefault && pFeature->pOptions == NULL)
        {
            pOption = ALLOC_PARSER_MEM(pParserData, pFeature->dwOptionSize);

            if (pOption == NULL)
            {
                ERR(("Memory allocation failed: %d\n", GetLastError()));
                PACK_BINARY_DATA_EXCEPTION();
            }

            //
            // NOTE: it's ok for both pOption->pstrName and pFeature->pstrDefault
            // to point to the same string here. The memory is deallocated when
            // the parser heap is destroyed.
            //

            pOption->pstrName = pFeature->pstrDefault;
            pFeature->pOptions = pOption;
        }

        //
        // Special handling of *InputSlot feature
        //  Make sure the very first option is always "*UseFormTrayTable"
        //

        if (pFeature->dwFeatureID == GID_INPUTSLOT)
        {
            pOption = ALLOC_PARSER_MEM(pParserData, pFeature->dwOptionSize);

            if (pOption == NULL)
            {
                ERR(("Memory allocation failed: %d\n", GetLastError()));
                PACK_BINARY_DATA_EXCEPTION();
            }

            pOption->pstrName = "*UseFormTrayTable";
            pOption->pNext = pFeature->pOptions;
            pFeature->pOptions = pOption;

            ((PTRAYOBJ) pOption)->dwTrayIndex = DMBIN_FORMSOURCE;
        }
    }
}



VOID
VPackPrinterFeatures(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Pack printer feature and option information into binary data

Arguments:

    pParserData - Points to the parser data structure

Return Value:

    NONE

--*/

{
    PFEATUREOBJ pFeature;
    PFEATURE    pPackedFeature;
    POPTIONOBJ  pOption;
    POPTION     pPackedOption;
    DWORD       dwFeatureIndex, dwOptionIndex, dwCount;

    VALIDATE_PARSER_DATA(pParserData);

    //
    // Reserve space in the binary data for an array of FEATURE structures
    //

    dwCount = pParserData->pInfoHdr->RawData.dwDocumentFeatures +
              pParserData->pInfoHdr->RawData.dwPrinterFeatures;

    VGrowPackBuffer(pParserData, dwCount * sizeof(FEATURE));
    pPackedFeature = (PFEATURE) (pParserData->pubBufStart + pParserData->dwBufSize);
    pParserData->pUIInfo->loFeatureList = pParserData->dwBufSize;
    pParserData->dwBufSize += DWORD_ALIGN(dwCount * sizeof(FEATURE));

    for (pFeature = pParserData->pFeatures, dwFeatureIndex = 0;
        pFeature != NULL;
        pFeature = pFeature->pNext, dwFeatureIndex++, pPackedFeature++)
    {
        PFEATUREDATA    pFeatureData;

        //
        // Pack feature information
        //

        VPackStringAnsi(pParserData, &pPackedFeature->loKeywordName, pFeature->pstrName);

        VPackStringXlation(pParserData,
                           &pPackedFeature->loDisplayName,
                           pFeature->pstrName,
                           &pFeature->Translation);

        VPackInvocation(pParserData, &pPackedFeature->QueryInvocation, &pFeature->QueryInvoc);

        pFeatureData = PGetFeatureData(pFeature->dwFeatureID);
        pPackedFeature->dwFlags = pFeatureData->dwFlags;
        pPackedFeature->dwOptionSize = pFeatureData->dwOptionSize;
        pPackedFeature->dwFeatureID = pFeature->dwFeatureID;
        pPackedFeature->dwUIType = pFeature->dwUIType;
        pPackedFeature->dwUIConstraintList = pFeature->dwConstraint;
        pPackedFeature->dwNoneFalseOptIndex = OPTION_INDEX_ANY;

        if (pFeature->bInstallable)
        {
            pPackedFeature->dwPriority = pFeatureData->dwPriority + PRNPROP_BASE_PRIORITY;
            pPackedFeature->dwFeatureType = FEATURETYPE_PRINTERPROPERTY;
        }
        else
        {
            ASSERT(pFeatureData->dwPriority < PRNPROP_BASE_PRIORITY);
            pPackedFeature->dwPriority = pFeatureData->dwPriority;
            pPackedFeature->dwFeatureType = FEATURETYPE_DOCPROPERTY;
        }

        //
        // For non-PickMany features, use the very first option as the default
        // if none is explicitly specified. Otherwise, default to OPTION_INDEX_ANY.
        //

        pPackedFeature->dwDefaultOptIndex =
        (pFeature->dwUIType == UITYPE_PICKMANY) ? OPTION_INDEX_ANY : 0;

        //
        // If this feature is a predefined feature, save a reference to it
        //

        if (pFeature->dwFeatureID < MAX_GID)
        {
            pParserData->pUIInfo->aloPredefinedFeatures[pFeature->dwFeatureID] =
            pParserData->pUIInfo->loFeatureList + (dwFeatureIndex * sizeof(FEATURE));
        }

        //
        // Reserve space in the binary data for an array of OPTION structures
        //

        if ((dwCount = DwCountListItem(pFeature->pOptions)) == 0)
        {
            TERSE(("No options for feature: %s\n", pFeature->pstrName));
            pPackedFeature->Options.loOffset = 0;
            pPackedFeature->Options.dwCount = 0;
            continue;
        }

        ASSERTMSG((dwCount < OPTION_INDEX_ANY),
                  ("Too many options for feature: %s\n", pFeature->pstrName));

        VGrowPackBuffer(pParserData, dwCount * pFeatureData->dwOptionSize);
        pPackedOption = (POPTION) (pParserData->pubBufStart + pParserData->dwBufSize);
        pPackedFeature->Options.loOffset = pParserData->dwBufSize;
        pPackedFeature->Options.dwCount = dwCount;
        pParserData->dwBufSize += DWORD_ALIGN(dwCount * pFeatureData->dwOptionSize);

        for (pOption = pFeature->pOptions, dwOptionIndex = 0;
            pOption != NULL;
            pOption = pOption->pNext, dwOptionIndex++)
        {
            BOOL bIsDefaultOption = FALSE; // TRUE if current option is default

            //
            // Pack option information
            //

            VPackStringAnsi(pParserData,
                            &pPackedOption->loKeywordName,
                            pOption->pstrName);

            VPackStringXlation(pParserData,
                               &pPackedOption->loDisplayName,
                               pOption->pstrName,
                               &pOption->Translation);

            VPackInvocation(pParserData,
                            &pPackedOption->Invocation,
                            &pOption->Invocation);

            pPackedOption->dwUIConstraintList = pOption->dwConstraint;

            //
            // Check if the current option is the default option
            // or if it's the None/False option
            //

            if (pFeature->pstrDefault &&
                strcmp(pOption->pstrName, pFeature->pstrDefault) == EQUAL_STRING)
            {
                pPackedFeature->dwDefaultOptIndex = dwOptionIndex;
                bIsDefaultOption = TRUE;
            }

            if (strcmp(pOption->pstrName, gstrNoneKwd) == EQUAL_STRING ||
                strcmp(pOption->pstrName, gstrFalseKwd) == EQUAL_STRING)
            {
                pPackedFeature->dwNoneFalseOptIndex = dwOptionIndex;
            }

            //
            // Handle extra fields after the generic OPTION structure
            //

            switch (pFeature->dwFeatureID)
            {
            case GID_PAGESIZE:

                {
                    PPAGESIZE   pPageSize = (PPAGESIZE) pPackedOption;
                    PPAPEROBJ   pPaper = (PPAPEROBJ) pOption;
                    PRECT       prect;
                    PSIZE       psize;

                    if (strcmp(pOption->pstrName, gstrCustomSizeKwd) == EQUAL_STRING)
                    {
                        PPPDDATA    pPpdData;
                        LONG        lMax;

                        //
                        // Special case for CustomPageSize option
                        //

                        pPpdData = pParserData->pPpdData;
                        psize = &pPageSize->szPaperSize;
                        prect = &pPageSize->rcImgArea;

                        pPageSize->szPaperSize = pPaper->szDimension;
                        pPageSize->rcImgArea = pPaper->rcImageArea;
                        pPageSize->dwPaperSizeID = DMPAPER_CUSTOMSIZE;

                        VPackStringRsrc(pParserData,
                                        &pPackedOption->loDisplayName,
                                        IDS_PSCRIPT_CUSTOMSIZE);

                        //
                        // If either MaxMediaWidth or MaxMediaHeight is missing,
                        // we'll use the max width or height values from
                        // ParamCustomPageSize.
                        //

                        if (psize->cx <= 0)
                            psize->cx = MAXCUSTOMPARAM_WIDTH(pPpdData);

                        if (psize->cy <= 0)
                            psize->cy = MAXCUSTOMPARAM_HEIGHT(pPpdData);

                        if (psize->cx > 0 &&
                            psize->cy > 0 &&
                            MINCUSTOMPARAM_ORIENTATION(pPpdData) <= 3)
                        {
                            pParserData->pUIInfo->dwFlags |= FLAG_CUSTOMSIZE_SUPPORT;
                            pParserData->pUIInfo->dwCustomSizeOptIndex = dwOptionIndex;

                            //
                            // Make sure the hardware margins are not larger than
                            // the maximum media width or height.
                            //
                            // This is only significant for cut-sheet device.
                            //

                            if (pParserData->dwCustomSizeFlags & CUSTOMSIZE_CUTSHEET)
                            {
                                lMax = min(psize->cx, psize->cy);

                                if (prect->left < 0 || prect->left >= lMax)
                                    prect->left = 0;

                                if (prect->right < 0 || prect->right >= lMax)
                                    prect->right = 0;

                                if (prect->top < 0 || prect->top >= lMax)
                                    prect->top = 0;

                                if (prect->bottom < 0 || prect->bottom >= lMax)
                                    prect->bottom = 0;
                            }

                            //
                            // Validate custom page size parameters
                            //

                            if (MAXCUSTOMPARAM_WIDTH(pPpdData) > psize->cx)
                                MAXCUSTOMPARAM_WIDTH(pPpdData) = psize->cx;

                            if (MINCUSTOMPARAM_WIDTH(pPpdData) <= MICRONS_PER_INCH)
                                MINCUSTOMPARAM_WIDTH(pPpdData) = MICRONS_PER_INCH;

                            if (MAXCUSTOMPARAM_HEIGHT(pPpdData) > psize->cy)
                                MAXCUSTOMPARAM_HEIGHT(pPpdData) = psize->cy;

                            if (MINCUSTOMPARAM_HEIGHT(pPpdData) <= MICRONS_PER_INCH)
                                MINCUSTOMPARAM_HEIGHT(pPpdData) = MICRONS_PER_INCH;
                        }
                    }
                    else
                    {
                        psize = &pPaper->szDimension;
                        prect = &pPaper->rcImageArea;

                        if (strcmp(pOption->pstrName, gstrLetterSizeKwd) == EQUAL_STRING)
                        {
                            if ((abs(psize->cx - LETTER_PAPER_WIDTH) < 1000) &&
                                (abs(psize->cy - LETTER_PAPER_LENGTH) < 1000))
                            {
                                pParserData->pUIInfo->dwFlags |= FLAG_LETTER_SIZE_EXISTS;
                            }
                        }
                        else if (strcmp(pOption->pstrName, gstrA4SizeKwd) == EQUAL_STRING)
                        {
                            if ((abs(psize->cx - A4_PAPER_WIDTH) < 1000) &&
                                (abs(psize->cy - A4_PAPER_LENGTH) < 1000))
                            {
                                pParserData->pUIInfo->dwFlags |= FLAG_A4_SIZE_EXISTS;
                            }
                        }

                        //
                        // Verify paper dimension
                        //

                        if (psize->cx <= 0 || psize->cy <= 0)
                        {
                            SEMANTIC_ERROR(("Invalid PaperDimension for: %s\n",
                                            pOption->pstrName));

                            psize->cx = DEFAULT_PAPER_WIDTH;
                            psize->cy = DEFAULT_PAPER_LENGTH;
                        }

                        pPageSize->szPaperSize = pPaper->szDimension;

                        //
                        // Verify imageable area
                        //

                        if (prect->left < 0 || prect->left >= prect->right ||
                            prect->bottom < 0|| prect->bottom >= prect->top ||
                            prect->right > psize->cx ||
                            prect->top > psize->cy)
                        {
                            SEMANTIC_ERROR(("Invalid ImageableArea for: %s\n",
                                            pOption->pstrName));

                            prect->left = prect->bottom = 0;
                            prect->right = psize->cx;
                            prect->top = psize->cy;
                        }

                        //
                        // Convert from PS to GDI coordinate system
                        //

                        pPageSize->rcImgArea.left = prect->left;
                        pPageSize->rcImgArea.right = prect->right;
                        pPageSize->rcImgArea.top = psize->cy - prect->top;
                        pPageSize->rcImgArea.bottom = psize->cy - prect->bottom;

                        //
                        // Driver paper size ID starts at DRIVER_PAPERSIZE_ID
                        //

                        pPageSize->dwPaperSizeID = dwOptionIndex + DRIVER_PAPERSIZE_ID;
                    }
                }

                break;

            case GID_RESOLUTION:

                {
                    PRESOLUTION pResolution = (PRESOLUTION) pPackedOption;
                    PRESOBJ     pResObj = (PRESOBJ) pOption;
                    PSTR        pstr = pOption->pstrName;
                    LONG        lXdpi, lYdpi;
                    BOOL        bValid;

                    pResolution->iXdpi = pResolution->iYdpi = DEFAULT_RESOLUTION;
                    pResolution->fxScreenFreq = pResObj->fxScreenFreq;
                    pResolution->fxScreenAngle = pResObj->fxScreenAngle;

                    if (BGetIntegerFromString(&pstr, &lXdpi))
                    {
                        lYdpi = lXdpi;

                        while (*pstr && !IS_DIGIT(*pstr))
                            pstr++;

                        if ((*pstr == NUL || BGetIntegerFromString(&pstr, &lYdpi)) &&
                            (lXdpi > 0 && lXdpi <= MAX_SHORT) &&
                            (lYdpi > 0 && lYdpi <= MAX_SHORT))
                        {
                            pResolution->iXdpi = (INT) lXdpi;
                            pResolution->iYdpi = (INT) lYdpi;
                            bValid = TRUE;
                        }
                    }

                    if (! bValid)
                        SEMANTIC_ERROR(("Invalid resolution option: %s\n", pOption->pstrName));
                }
                break;

            case GID_DUPLEX:

                {
                    PDUPLEX pDuplex = (PDUPLEX) pPackedOption;

                    if (strcmp(pOption->pstrName, gstrDuplexTumble) == EQUAL_STRING)
                    {
                        //
                        // Horizontal == ShortEdge == Tumble
                        //

                        pDuplex->dwDuplexID = DMDUP_HORIZONTAL;
                    }
                    else if (strcmp(pOption->pstrName, gstrDuplexNoTumble) == EQUAL_STRING)
                    {
                        //
                        // Vertical == LongEdge == NoTumble
                        //

                        pDuplex->dwDuplexID = DMDUP_VERTICAL;
                    }
                    else
                        pDuplex->dwDuplexID = DMDUP_SIMPLEX;
                }
                break;

            case GID_COLLATE:

                {
                    PCOLLATE pCollate = (PCOLLATE) pPackedOption;

                    pCollate->dwCollateID =
                    (strcmp(pOption->pstrName, gstrTrueKwd) == EQUAL_STRING ||
                     strcmp(pOption->pstrName, gstrOnKwd) == EQUAL_STRING) ?
                    DMCOLLATE_TRUE :
                    DMCOLLATE_FALSE;
                }
                break;

            case GID_MEDIATYPE:

                ((PMEDIATYPE) pPackedOption)->dwMediaTypeID = dwOptionIndex + DMMEDIA_USER;
                break;

            case GID_INPUTSLOT:

                {
                    PINPUTSLOT  pInputSlot = (PINPUTSLOT) pPackedOption;
                    PTRAYOBJ    pTray = (PTRAYOBJ) pOption;
                    DWORD       dwReqPageRgn;

                    if ((dwReqPageRgn = pTray->dwReqPageRgn) == REQRGN_UNKNOWN)
                        dwReqPageRgn = pParserData->dwReqPageRgn;

                    if (dwReqPageRgn != REQRGN_FALSE)
                        pInputSlot->dwFlags |= INPUTSLOT_REQ_PAGERGN;

                    //
                    // Special handling of predefined input slots:
                    //  ManualFeed and AutoSelect
                    //

                    switch (pTray->dwTrayIndex)
                    {
                    case DMBIN_FORMSOURCE:

                        pInputSlot->dwPaperSourceID = pTray->dwTrayIndex;
                        break;

                    case DMBIN_MANUAL:

                        pInputSlot->dwPaperSourceID = pTray->dwTrayIndex;

                        VPackStringRsrc(pParserData,
                                        &pPackedOption->loDisplayName,
                                        IDS_TRAY_MANUALFEED);
                        break;

                    default:

                        pInputSlot->dwPaperSourceID = dwOptionIndex + DMBIN_USER;
                        break;
                    }
                }
                break;

            case GID_OUTPUTBIN:

                {
                    PBINOBJ pBinObj = (PBINOBJ) pOption;

                    //
                    // if this is the default bin, set the default output order, if specified
                    // by the DefaultOutputOrder entry in the PPD-file
                    //

                    if (bIsDefaultOption && pParserData->bDefOutputOrderSet)
                    {
                        //
                        // If multiple bins: warn if different options specified
                        //

                        if ((dwCount > 1) &&
                            (pBinObj->bReversePrint != pParserData->bDefReversePrint))
                        {
                            TERSE(("Warning: explicit *DefaultPageOrder overwrites PageStackOrder of OutputBin\n"));
                        }


                        ((POUTPUTBIN) pPackedOption)->bOutputOrderReversed = pParserData->bDefReversePrint;
                    }
                    else
                    {
                        //
                        // for non-default bins, the default output order has no effect - the PPD spec says
                        // "*DefaultOutputOrder indicates the default stacking order of the default output bin."
                        //

                        ((POUTPUTBIN) pPackedOption)->bOutputOrderReversed = pBinObj->bReversePrint;
                    }
                }

                break;

            case GID_MEMOPTION:

                {
                    PMEMOPTION  pMemOption = (PMEMOPTION) pPackedOption;
                    PMEMOBJ     pMemObj = (PMEMOBJ) pOption;
                    DWORD       dwMinFreeMem;

                    //
                    // Store PPD's original *VMOption value into dwInstalledMem.
                    // This is only used for the new PPD helper function GetOptionAttribute().
                    // (see comments in inc\parser.h)
                    //

                    pMemOption->dwInstalledMem = pMemObj->dwFreeVM;

                    dwMinFreeMem = pParserData->dwLangLevel <= 1 ? MIN_FREEMEM_L1 : MIN_FREEMEM_L2;
                    if (pMemObj->dwFreeVM < dwMinFreeMem)
                    {
                        SEMANTIC_ERROR(("Invalid memory option: %s\n", pOption->pstrName));
                        pMemObj->dwFreeVM = dwMinFreeMem;
                    }

                    pMemOption->dwFreeMem = pMemObj->dwFreeVM;
                    pMemOption->dwFreeFontMem = pMemObj->dwFontMem;
                }
                break;

            case GID_LEADINGEDGE:

                if (strcmp(pOption->pstrName, gstrLongKwd) == EQUAL_STRING)
                {
                    pParserData->pPpdData->dwLeadingEdgeLong = dwOptionIndex;

                    if (dwOptionIndex == pPackedFeature->dwDefaultOptIndex)
                        pParserData->pPpdData->dwCustomSizeFlags &= ~CUSTOMSIZE_SHORTEDGEFEED;
                }
                else if (strcmp(pOption->pstrName, gstrShortKwd) == EQUAL_STRING)
                {
                    pParserData->pPpdData->dwLeadingEdgeShort = dwOptionIndex;

                    if (dwOptionIndex == pPackedFeature->dwDefaultOptIndex)
                        pParserData->pPpdData->dwCustomSizeFlags |= CUSTOMSIZE_SHORTEDGEFEED;
                }

                break;

            case GID_USEHWMARGINS:

                if (strcmp(pOption->pstrName, gstrTrueKwd) == EQUAL_STRING)
                {
                    pParserData->pPpdData->dwUseHWMarginsTrue = dwOptionIndex;
                    pParserData->pPpdData->dwCustomSizeFlags |= CUSTOMSIZE_CUTSHEET;

                    if (dwOptionIndex == pPackedFeature->dwDefaultOptIndex)
                        pParserData->pPpdData->dwCustomSizeFlags |= CUSTOMSIZE_DEFAULTCUTSHEET;
                }
                else if (strcmp(pOption->pstrName, gstrFalseKwd) == EQUAL_STRING)
                {
                    pParserData->pPpdData->dwUseHWMarginsFalse = dwOptionIndex;
                    pParserData->pPpdData->dwCustomSizeFlags |= CUSTOMSIZE_ROLLFED;

                    if (dwOptionIndex == pPackedFeature->dwDefaultOptIndex)
                        pParserData->pPpdData->dwCustomSizeFlags &= ~CUSTOMSIZE_DEFAULTCUTSHEET;
                }
                break;
            }

            pPackedOption = (POPTION) ((PBYTE) pPackedOption + pFeatureData->dwOptionSize);
        }
    }
}



VOID
VPackNt4Mapping(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Pack NT4 feature index mapping information

Arguments:

    pParserData - Points to the parser data structure

Return Value:

    NONE

--*/

{
    PPPDDATA    pPpdData;
    PFEATURE    pPackedFeatures;
    PBYTE       pubNt4Mapping;
    DWORD       dwCount, dwIndex, dwNt4Index;
    INT         iInputSlotIndex;
    BYTE        ubInputSlotOld, ubInputSlotNew;

    pPpdData = pParserData->pPpdData;
    pPpdData->dwNt4Checksum = pParserData->wNt4Checksum;
    pPpdData->dwNt4DocFeatures = pParserData->pUIInfo->dwDocumentFeatures;
    pPpdData->dwNt4PrnFeatures = pParserData->pUIInfo->dwPrinterFeatures;

    iInputSlotIndex = -1;
    ubInputSlotNew = 0xff;

    if (pParserData->iDefInstallMemIndex >= 0)
        pParserData->iDefInstallMemIndex += pPpdData->dwNt4DocFeatures;

    dwCount = pPpdData->dwNt4DocFeatures + pPpdData->dwNt4PrnFeatures;
    pPpdData->Nt4Mapping.dwCount = dwCount;

    VGrowPackBuffer(pParserData, dwCount * sizeof(BYTE));
    pubNt4Mapping = (PBYTE) (pParserData->pubBufStart + pParserData->dwBufSize);
    pPpdData->Nt4Mapping.loOffset = pParserData->dwBufSize;
    pParserData->dwBufSize += DWORD_ALIGN(dwCount * sizeof(BYTE));

    pPackedFeatures = (PFEATURE) (pParserData->pubBufStart + pParserData->pUIInfo->loFeatureList);

    for (dwIndex=dwNt4Index=0; dwIndex <= dwCount; dwIndex++)
    {
        BOOL    bMapped = TRUE;

        //
        // ManualFeed used to be a feature in NT4,
        // but not anymore in NT5
        //

        if (pParserData->iReqPageRgnIndex == (INT) dwIndex)
            ubInputSlotNew = (BYTE) dwNt4Index;

        if (pParserData->iManualFeedIndex == (INT) dwIndex)
        {
            pPpdData->dwNt4DocFeatures++;
            dwNt4Index++;
        }

        //
        // DefaultInstalledMemory causes a bogus feature to be added on NT4
        //

        if (pParserData->iDefInstallMemIndex == (INT) dwIndex)
        {
            pPpdData->dwNt4PrnFeatures++;
            dwNt4Index++;
        }

        if (dwIndex == dwCount)
            break;

        switch (pPackedFeatures[dwIndex].dwFeatureID)
        {
        case GID_MEDIATYPE:
        case GID_OUTPUTBIN:

            // a feature in NT4 only if within Open/CloseUI

            if (pParserData->aubOpenUIFeature[pPackedFeatures[dwIndex].dwFeatureID])
                break;

            // fall through

        case GID_PAGEREGION:
        case GID_LEADINGEDGE:
        case GID_USEHWMARGINS:

            // not a feature in NT4

            bMapped = FALSE;
            break;

        case GID_INPUTSLOT:

            iInputSlotIndex = dwIndex;
            break;
        }

        if (bMapped)
        {
            pubNt4Mapping[dwIndex] = (BYTE) dwNt4Index;
            dwNt4Index++;
        }
        else
        {
            pPpdData->dwNt4DocFeatures--;
            pubNt4Mapping[dwIndex] = 0xff;
        }
    }

    //
    // RequiresPageRegion causes InputSlot feature to be created on NT4
    //

    if (iInputSlotIndex >= 0 && pParserData->iReqPageRgnIndex >= 0)
    {
        ubInputSlotOld = pubNt4Mapping[iInputSlotIndex];

        if (ubInputSlotOld > ubInputSlotNew)
        {
            for (dwIndex=0; dwIndex < dwCount; dwIndex++)
            {
                if (pubNt4Mapping[dwIndex] >= ubInputSlotNew &&
                    pubNt4Mapping[dwIndex] <  ubInputSlotOld)
                {
                    pubNt4Mapping[dwIndex]++;
                }
            }
        }
        else if (ubInputSlotOld < ubInputSlotNew)
        {
            for (dwIndex=0; dwIndex < dwCount; dwIndex++)
            {
                if (pubNt4Mapping[dwIndex] >  ubInputSlotOld &&
                    pubNt4Mapping[dwIndex] <= ubInputSlotNew)
                {
                    pubNt4Mapping[dwIndex]--;
                }
            }
        }

        pubNt4Mapping[iInputSlotIndex] = ubInputSlotNew;
    }
}



VOID
VPackDeviceFonts(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Pack device font information into binary data

Arguments:

    pParserData - Points to the parser data structure

Return Value:

    NONE

--*/

{
    PDEVFONT    pDevFont;
    PFONTREC    pFontObj;
    DWORD       dwIndex, dwFonts;

    VALIDATE_PARSER_DATA(pParserData);

    //
    // Count the number of device fonts and
    // reserve enough space in the packed binary data
    //

    if ((dwFonts = DwCountListItem(pParserData->pFonts)) == 0)
        return;

    VGrowPackBuffer(pParserData, dwFonts * sizeof(DEVFONT));
    pParserData->pPpdData->DeviceFonts.dwCount = dwFonts;
    pParserData->pPpdData->DeviceFonts.loOffset = pParserData->dwBufSize;

    pDevFont = (PDEVFONT) (pParserData->pubBufStart + pParserData->dwBufSize);
    pParserData->dwBufSize += DWORD_ALIGN(dwFonts * sizeof(DEVFONT));

    //
    // Pack information about each device font
    //

    for (pFontObj = pParserData->pFonts;
        pFontObj != NULL;
        pFontObj = pFontObj->pNext)
    {
        VPackStringAnsi(pParserData, &pDevFont->loFontName, pFontObj->pstrName);

        VPackStringXlation(pParserData,
                           &pDevFont->loDisplayName,
                           pFontObj->pstrName,
                           &pFontObj->Translation);

        VPackStringAnsi(pParserData, &pDevFont->loEncoding, pFontObj->pstrEncoding);
        VPackStringAnsi(pParserData, &pDevFont->loCharset, pFontObj->pstrCharset);
        VPackStringAnsi(pParserData, &pDevFont->loVersion, pFontObj->pstrVersion);

        pDevFont->dwStatus = pFontObj->dwStatus;
        pDevFont++;
    }

    //
    // Calculate the byte-offset to the default DEVFONT structure (if any)
    //

    if (pParserData->pstrDefaultFont &&
        PvFindListItem(pParserData->pFonts, pParserData->pstrDefaultFont, &dwIndex))
    {
        pParserData->pPpdData->loDefaultFont = pParserData->pPpdData->DeviceFonts.loOffset +
                                               (dwIndex * sizeof(DEVFONT));
    }
}



VOID
VPackJobPatchFiles(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Pack *JobPatchFile information into binary data

Arguments:

    pParserData - Points to the parser data structure

Return Value:

    NONE

--*/

{
    PJOBPATCHFILE     pPackedPatch;
    PJOBPATCHFILEOBJ  pJobPatchFile;
    DWORD             dwJobPatchFiles;

    VALIDATE_PARSER_DATA(pParserData);

    //
    // Count the number of *JobPatchFile entries
    //

    dwJobPatchFiles = DwCountListItem((PVOID) pParserData->pJobPatchFiles);

    if (dwJobPatchFiles > 0)
    {
        //
        // Reserve enough space in the packed binary data
        //

        VGrowPackBuffer(pParserData, dwJobPatchFiles * sizeof(JOBPATCHFILE));
        pParserData->pPpdData->JobPatchFiles.dwCount = dwJobPatchFiles;
        pParserData->pPpdData->JobPatchFiles.loOffset = pParserData->dwBufSize;

        pPackedPatch = (PJOBPATCHFILE) (pParserData->pubBufStart + pParserData->dwBufSize);
        pParserData->dwBufSize += DWORD_ALIGN(dwJobPatchFiles * sizeof(JOBPATCHFILE));

        //
        // Pack each *JobPatchFile invocation string
        //

        for (pJobPatchFile = pParserData->pJobPatchFiles;
            pJobPatchFile != NULL;
            pJobPatchFile = pJobPatchFile->pNext)
        {
            VPackPatch(pParserData, pPackedPatch, pJobPatchFile);
            pPackedPatch++;
        }
    }
}



typedef struct _TTFSUBSTRESINFO
{
    BOOL bCJK;
    WORD wIDBegin;
    WORD wIDEnd;
}
TTFSUBSTRESINFO;

static TTFSUBSTRESINFO TTFSubstResInfo[] =
{
    { FALSE, IDS_1252_BEGIN, IDS_1252_END},
    { TRUE,  IDS_932_BEGIN,  IDS_932_END},
    { TRUE,  IDS_936_BEGIN,  IDS_936_END},
    { TRUE,  IDS_949_BEGIN,  IDS_949_END},
};



VOID
VPackDefaultTrueTypeSubstTable(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Pack the default TrueType font substitution table into the binary data

Arguments:

    pParserData - Points to the parser data structure

Return Value:

    NONE

--*/

#define MAX_FONT_NAME   256

{
    INT     iNumInfo, iInfo, iCount, iLenTT, iLenPS, i;
    DWORD   dwSize, dwLeft, dw;
    TCHAR   tchBuf[MAX_FONT_NAME];
    PTSTR   ptstrTable;
    HRSRC   hrRcData;
    HGLOBAL hgRcData;
    PWORD   pwRcData;

    VALIDATE_PARSER_DATA(pParserData);

    //
    // Calculate how much memory we need to hold the default TrueType to
    // PostScript substitution names. The counter is initialized to 1, instead
    // of 0, for the last NUL terminator. Count the names from the STRING
    // resource, then from RCDATA resource.
    //
    //
    dwSize = 1;

    iNumInfo = sizeof(TTFSubstResInfo) / sizeof(TTFSUBSTRESINFO);

    for (iInfo = 0; iInfo < iNumInfo; iInfo++)
    {
        iCount = TTFSubstResInfo[iInfo].wIDEnd - TTFSubstResInfo[iInfo].wIDBegin + 1;

        for (i = 0; i < iCount; i++)
        {
            iLenTT = LoadString(ghInstance,
                                TTFSubstResInfo[iInfo].wIDBegin + i,
                                tchBuf, MAX_FONT_NAME);

            if (iLenTT == 0)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: load TT string failed: %d\n", GetLastError()));
                return;
            }

            iLenPS = LoadString(ghInstance,
                                TTFSubstResInfo[iInfo].wIDBegin + i + TT2PS_INTERVAL,
                                tchBuf, MAX_FONT_NAME);

            if (iLenPS == 0)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: load PS string failed: %d\n", GetLastError()));
                return;
            }

            dwSize += (iLenTT + 1) + (iLenPS + 1);

            if (TTFSubstResInfo[iInfo].bCJK == TRUE)
            {
                // We need names beginning with '@' too for CJK.
                dwSize += (1 + iLenTT + 1) + (1 + iLenPS + 1);
            }
        }

        if (TTFSubstResInfo[iInfo].bCJK == TRUE)
        {
            hrRcData = FindResource(ghInstance, (LPCTSTR)TTFSubstResInfo[iInfo].wIDBegin, RT_RCDATA);
            if (hrRcData == NULL)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: find RCDATA failed: %d\n", GetLastError()));
                return;
            }

            // Load the resource and get its size.
            hgRcData = LoadResource(ghInstance, hrRcData);
            if (hgRcData == NULL)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: load RCDATA failed: %d\n", GetLastError()));
                return;
            }

            // The first WORD of the IDR resource tells the size of the strings.
            pwRcData = (PWORD)LockResource(hgRcData);
            if (pwRcData == NULL)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: lock RCDATA failed: %d\n", GetLastError()));
                return;
            }

            dw = *pwRcData;
            if (dw % 2)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: RCDATA size is odd.\n"));
                return;
            }

            dwSize += dw / 2;
        }
    }

    //
    // Reserve enough space in the packed binary data
    //

    dwSize *= sizeof(TCHAR);

    VGrowPackBuffer(pParserData, dwSize);
    ptstrTable = (PTSTR) (pParserData->pubBufStart + pParserData->dwBufSize);

    pParserData->pUIInfo->loFontSubstTable = pParserData->dwBufSize;
    pParserData->pUIInfo->dwFontSubCount = dwSize;
    pParserData->dwBufSize += DWORD_ALIGN(dwSize);

    //
    // Save the default substitution table in the binary data
    //

    dwLeft = dwSize;

    for (iInfo = 0; iInfo < iNumInfo; iInfo++)
    {
        iCount = TTFSubstResInfo[iInfo].wIDEnd - TTFSubstResInfo[iInfo].wIDBegin + 1;

        for (i = 0; i < iCount; i++)
        {
            iLenTT = LoadString(ghInstance,
                                TTFSubstResInfo[iInfo].wIDBegin + i,
                                ptstrTable, dwLeft);

            if (iLenTT == 0)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: load TT string failed: %d\n", GetLastError()));
                goto fail_cleanup;
            }

            ptstrTable += iLenTT + 1;
            dwLeft -= (iLenTT + 1) * sizeof (TCHAR);

            iLenPS = LoadString(ghInstance,
                                TTFSubstResInfo[iInfo].wIDBegin + i + TT2PS_INTERVAL,
                                ptstrTable, dwLeft);

            if (iLenPS == 0)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: load PS string failed: %d\n", GetLastError()));
                goto fail_cleanup;
            }

            ptstrTable += iLenPS + 1;
            dwLeft -= (iLenPS + 1) * sizeof (TCHAR);

            if (TTFSubstResInfo[iInfo].bCJK == TRUE)
            {
                // We need names beginning with '@' too for CJK.

                *ptstrTable++ = L'@';
                dwLeft -= sizeof (TCHAR);

                if (!LoadString(ghInstance, TTFSubstResInfo[iInfo].wIDBegin + i,
                                ptstrTable, dwLeft))
                {
                    ERR(("VPackDefaultTrueTypeSubstTable: load TT string failed: %d\n", GetLastError()));
                    goto fail_cleanup;
                }

                ptstrTable += iLenTT + 1;
                dwLeft -= (iLenTT + 1) * sizeof (TCHAR);

                *ptstrTable++ = L'@';
                dwLeft -= sizeof (TCHAR);

                if (!LoadString(ghInstance, TTFSubstResInfo[iInfo].wIDBegin + i + TT2PS_INTERVAL,
                                ptstrTable, dwLeft))
                {
                    ERR(("VPackDefaultTrueTypeSubstTable: load PS string failed: %d\n", GetLastError()));
                    goto fail_cleanup;
                }

                ptstrTable += iLenPS + 1;
                dwLeft -= (iLenPS + 1) * sizeof (TCHAR);
            }
        }

        if (TTFSubstResInfo[iInfo].bCJK == TRUE)
        {
            hrRcData = FindResource(ghInstance, (LPCTSTR)TTFSubstResInfo[iInfo].wIDBegin, RT_RCDATA);
            if (hrRcData == NULL)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: find RCDATA failed: %d\n", GetLastError()));
                goto fail_cleanup;
            }

            hgRcData = LoadResource(ghInstance, hrRcData);
            if (hgRcData == NULL)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: load RCDATA failed: %d\n", GetLastError()));
                goto fail_cleanup;
            }

            pwRcData = (PWORD)LockResource(hgRcData);
            if (pwRcData == NULL)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: lock RCDATA failed: %d\n", GetLastError()));
                goto fail_cleanup;
            }

            dw = *pwRcData++;
            if (dw % 2)
            {
                ERR(("VPackDefaultTrueTypeSubstTable: RCDATA size is odd.\n"));
                goto fail_cleanup;
            }

            memcpy(ptstrTable, pwRcData, dw);

            ptstrTable += dw / 2;
            dwLeft -= dw;
        }
    }

    //
    // Succeed
    //

    return;

    //
    // Fail
    //

    fail_cleanup:

    pParserData->pUIInfo->loFontSubstTable = 0;
    pParserData->pUIInfo->dwFontSubCount = 0;
}



VOID
VPackTrueTypeSubstTable(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Pack the TrueType font substitution table into the binary data

Arguments:

    pParserData - Points to the parser data structure

Return Value:

    NONE

--*/

{
    PTTFONTSUB  pTTFontSub;
    DWORD       dwSize;
    PTSTR       ptstrTable, ptstrStart;

    //
    // Figure out how much space we'll need to store the font substitution table.
    // This is an estimate and may be a little higher than what we actually need.
    //

    ASSERT(pParserData->pTTFontSubs != NULL);

    for (pTTFontSub = pParserData->pTTFontSubs, dwSize = 1;
        pTTFontSub != NULL;
        pTTFontSub = pTTFontSub->pNext)
    {
        if (pTTFontSub->Translation.dwLength)
            dwSize += pTTFontSub->Translation.dwLength + 1;
        else
            dwSize += strlen(pTTFontSub->pstrName) + 1;

        dwSize += pTTFontSub->PSName.dwLength + 1;
    }

    //
    // Reserve enough space in the packed binary data
    //

    dwSize *= sizeof(TCHAR);
    VGrowPackBuffer(pParserData, dwSize);
    ptstrStart = ptstrTable = (PTSTR) (pParserData->pubBufStart + pParserData->dwBufSize);
    pParserData->pUIInfo->loFontSubstTable = pParserData->dwBufSize;
    pParserData->dwBufSize += DWORD_ALIGN(dwSize);

    for (pTTFontSub = pParserData->pTTFontSubs;
        pTTFontSub != NULL;
        pTTFontSub = pTTFontSub->pNext)
    {
        INT iChars;

        //
        // TrueType font family name
        //

        if (pTTFontSub->Translation.dwLength)
        {
            iChars = ITranslateToUnicodeString(
                                              ptstrTable,
                                              pTTFontSub->Translation.pvData,
                                              pTTFontSub->Translation.dwLength,
                                              pParserData->uCodePage);
        }
        else
        {
            iChars = ITranslateToUnicodeString(
                                              ptstrTable,
                                              pTTFontSub->pstrName,
                                              strlen(pTTFontSub->pstrName),
                                              1252);

        }

        if (iChars <= 0)
            break;

        ptstrTable += iChars + 1;

        //
        // PS font family name
        //

        iChars = ITranslateToUnicodeString(
                                          ptstrTable,
                                          pTTFontSub->PSName.pvData,
                                          pTTFontSub->PSName.dwLength,
                                          pParserData->uCodePage);

        if (iChars <= 0)
            break;

        ptstrTable += iChars + 1;
    }

    if (pTTFontSub != NULL)
    {
        ERR(("Error packing font substitution table\n"));
        ptstrTable = ptstrStart;
    }

    *ptstrTable++ = NUL;
    pParserData->pUIInfo->dwFontSubCount = (DWORD)(ptstrTable - ptstrStart) * sizeof(TCHAR);
}



VOID
VPackFileDateInfo(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Pack source PPD filenames and dates

Arguments:

    pParserData - Points to parser data structure

Return Value:

    NONE

--*/

{
    PRAWBINARYDATA  pRawData;
    DWORD           dwCount;
    PFILEDATEINFO   pFileDateInfo;
    PTSTR           ptstrFullname;
    PLISTOBJ        pItem;
    HANDLE          hFile;

    pRawData = &pParserData->pInfoHdr->RawData;
    dwCount = DwCountListItem(pParserData->pPpdFileNames);

    if (pRawData->FileDateInfo.dwCount = dwCount)
    {
        VGrowPackBuffer(pParserData, dwCount * sizeof(FILEDATEINFO));
        pRawData->FileDateInfo.loOffset = pParserData->dwBufSize;
        pFileDateInfo = (PFILEDATEINFO) (pParserData->pubBufStart + pParserData->dwBufSize);
        pParserData->dwBufSize += DWORD_ALIGN(dwCount * sizeof(FILEDATEINFO));

        for (pItem = pParserData->pPpdFileNames; pItem; pItem = pItem->pNext)
        {
            dwCount--;
            ptstrFullname = (PTSTR) pItem->pstrName;

            VPackStringUnicode(pParserData,
                               &pFileDateInfo[dwCount].loFileName,
                               ptstrFullname);

            hFile = CreateFile(ptstrFullname,
                               GENERIC_READ,
                               FILE_SHARE_READ,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL | SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS,
                               NULL);

            if ((hFile == INVALID_HANDLE_VALUE) ||
                !GetFileTime(hFile, NULL, NULL, &pFileDateInfo[dwCount].FileTime))
            {
                ERR(("GetFileTime '%ws' failed: %d\n", ptstrFullname, GetLastError()));
                GetSystemTimeAsFileTime(&pFileDateInfo[dwCount].FileTime);
            }

            if (hFile != INVALID_HANDLE_VALUE)
                CloseHandle(hFile);
        }
    }
}



VOID
VMapLangEncodingToCodePage(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Map LanguageEncoding to code page

Arguments:

    pParserData - Points to parser data structure

Return Value:

    NONE

--*/

{
    UINT    uCodePage = CP_ACP;
    CPINFO  cpinfo;

    switch (pParserData->dwLangEncoding)
    {
    case LANGENC_ISOLATIN1:
        uCodePage = 1252;
        break;

    case LANGENC_JIS83_RKSJ:
        uCodePage = 932;
        break;

    case LANGENC_UNICODE:
        uCodePage = CP_UNICODE;
        break;

    case LANGENC_NONE:
        break;

    default:
        RIP(("Unknown language encoding: %d\n", pParserData->dwLangEncoding));
        break;
    }

    //
    // Make sure the requested code page is available
    //

    if (uCodePage != CP_UNICODE &&
        uCodePage != CP_ACP &&
        !GetCPInfo(uCodePage, &cpinfo))
    {
        WARNING(("Code page %d is not available\n", uCodePage));
        uCodePage = CP_ERROR;
    }

    pParserData->uCodePage = uCodePage;
}



BOOL
BPackBinaryData(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Pack the parsed PPD information into binary format

Arguments:

    pParserData - Points to parser data structure

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    DWORD   dwSize;
    DWORD   dwMinFreeMem;
    BOOL    bResult = FALSE;

    VALIDATE_PARSER_DATA(pParserData);

    __try
    {
        //
        // Quick-access pointers to various data structures.
        //

        PINFOHEADER pInfoHdr;
        PUIINFO     pUIInfo;
        PPPDDATA    pPpdData;

        //
        // Pack fixed header data structures
        //

        dwSize = sizeof(INFOHEADER) + sizeof(UIINFO) + sizeof(PPDDATA);
        VGrowPackBuffer(pParserData, dwSize);
        pParserData->dwBufSize = DWORD_ALIGN(dwSize);
        pInfoHdr = pParserData->pInfoHdr;
        pUIInfo = pParserData->pUIInfo;
        pPpdData = pParserData->pPpdData;

        pInfoHdr->RawData.dwParserSignature = PPD_PARSER_SIGNATURE;
        pInfoHdr->RawData.dwParserVersion = PPD_PARSER_VERSION;

        #if 0
        pInfoHdr->RawData.dwChecksum32 = pParserData->dwChecksum32;
        #endif

        pInfoHdr->loUIInfoOffset = sizeof(INFOHEADER);
        pInfoHdr->loDriverOffset = sizeof(INFOHEADER) + sizeof(UIINFO);

        //
        // Pack source PPD filenames and dates
        //

        VPackFileDateInfo(pParserData);

        //
        // Perform a few miscellaneous checks
        //

        if (pParserData->pOpenFeature)
            SEMANTIC_ERROR(("Missing CloseUI for: %s\n", pParserData->pOpenFeature->pstrName));

        if (pParserData->bInstallableGroup)
            SEMANTIC_ERROR(("Missing CloseGroup: InstallableOptions\n"));

        if (pParserData->NickName.dwLength == 0)
            SEMANTIC_ERROR(("Missing *NickName and *ShortNickName entry\n"));

        if (pParserData->Product.dwLength == 0)
            SEMANTIC_ERROR(("Missing *Product entry\n"));

        if (pParserData->dwSpecVersion == 0)
            SEMANTIC_ERROR(("Missing *PPD-Adobe and *FormatVersion entry\n"));

        if (pParserData->dwLangLevel == 0)
        {
            SEMANTIC_ERROR(("Missing *LanguageLevel entry\n"));
            pParserData->dwLangLevel = 1;
        }

        dwMinFreeMem = pParserData->dwLangLevel <= 1 ? MIN_FREEMEM_L1 : MIN_FREEMEM_L2;
        if (pParserData->dwFreeMem < dwMinFreeMem)
        {
            SEMANTIC_ERROR(("Invalid *FreeVM entry\n"));
            pParserData->dwFreeMem = dwMinFreeMem;
        }

        //
        // Map LanguageEncoding to code page
        //

        VMapLangEncodingToCodePage(pParserData);

        //
        // Count the number of doc- and printer-sticky features
        // and sort them into two separate groups
        //

        VCountAndSortPrinterFeatures(pParserData);

        //
        // Fill out fields in the UIINFO structure
        //

        pUIInfo->dwSize = sizeof(UIINFO);
        pUIInfo->dwDocumentFeatures = pInfoHdr->RawData.dwDocumentFeatures;
        pUIInfo->dwPrinterFeatures = pInfoHdr->RawData.dwPrinterFeatures;
        pUIInfo->dwTechnology = DT_RASPRINTER;
        pUIInfo->dwMaxCopies = MAX_COPIES;
        pUIInfo->dwMinScale = MIN_SCALE;
        pUIInfo->dwMaxScale = MAX_SCALE;
        pUIInfo->dwSpecVersion = pParserData->dwSpecVersion;
        pUIInfo->dwLangEncoding = pParserData->dwLangEncoding;
        pUIInfo->dwLangLevel = pParserData->dwLangLevel;
        pUIInfo->dwPrintRate = pUIInfo->dwPrintRatePPM = pParserData->dwThroughput;

        #ifndef WINNT_40
        pUIInfo->dwPrintRateUnit = PRINTRATEUNIT_PPM;
        #endif

        //
        // Note: We assume all printers can support binary protocol
        //

        pUIInfo->dwProtocols = pParserData->dwProtocols | PROTOCOL_BINARY;

        pUIInfo->dwJobTimeout = pParserData->dwJobTimeout;
        pUIInfo->dwWaitTimeout = pParserData->dwWaitTimeout;
        pUIInfo->dwTTRasterizer = pParserData->dwTTRasterizer;
        pUIInfo->dwFreeMem = pParserData->dwFreeMem;
        pUIInfo->fxScreenAngle = pParserData->fxScreenAngle;
        pUIInfo->fxScreenFreq = pParserData->fxScreenFreq;
        pUIInfo->dwCustomSizeOptIndex = OPTION_INDEX_ANY;

        pPpdData->dwPpdFilever = pParserData->dwPpdFilever;
        pPpdData->dwFlags = pParserData->dwPpdFlags;

        //
        // Our internal unit is microns, thus 25400 units per inch.
        //

        pUIInfo->ptMasterUnits.x =
        pUIInfo->ptMasterUnits.y = 25400;

        pUIInfo->dwFlags = FLAG_FONT_DOWNLOADABLE |
                           FLAG_ORIENT_SUPPORT;

        if (pParserData->dwColorDevice)
            pUIInfo->dwFlags |= FLAG_COLOR_DEVICE;

        if (pParserData->dwLSOrientation != LSO_MINUS90)
            pUIInfo->dwFlags |= FLAG_ROTATE90;

        if (PvFindListItem(pParserData->pFeatures, "StapleLocation", NULL) ||
            PvFindListItem(pParserData->pFeatures, "StapleX", NULL) &&
            PvFindListItem(pParserData->pFeatures, "StapleY", NULL))
        {
            pUIInfo->dwFlags |= FLAG_STAPLE_SUPPORT;
        }

        if (pParserData->bDefReversePrint)
            pUIInfo->dwFlags |= FLAG_REVERSE_PRINT;

        if (pParserData->dwLangLevel > 1)
        {
            if (pParserData->bEuroInformationSet)
            {
                if (!pParserData->bHasEuro)
                    pUIInfo->dwFlags |= FLAG_ADD_EURO;
            }
            else if (pParserData->dwPSVersion < 3011)
                    pUIInfo->dwFlags |= FLAG_ADD_EURO;
        }

        if (pParserData->bTrueGray)
            pUIInfo->dwFlags |= FLAG_TRUE_GRAY;

        VPackStringAnsiToUnicode(
                                pParserData,
                                &pUIInfo->loNickName,
                                pParserData->NickName.pvData,
                                pParserData->NickName.dwLength);

        //
        // Pack symbol definitions and resolve symbol references
        //

        VPackSymbolDefinitions(pParserData);
        VResolveSymbolReferences(pParserData);

        VPackInvocation(pParserData, &pUIInfo->Password, &pParserData->Password);
        VPackInvocation(pParserData, &pUIInfo->ExitServer, &pParserData->ExitServer);

        //
        // Copy and validate custom page size parameters
        //

        pPpdData->dwUseHWMarginsTrue =
        pPpdData->dwUseHWMarginsFalse =
        pPpdData->dwLeadingEdgeLong =
        pPpdData->dwLeadingEdgeShort = OPTION_INDEX_ANY;
        pPpdData->dwCustomSizeFlags = pParserData->dwCustomSizeFlags;

        CopyMemory(pPpdData->CustomSizeParams,
                   pParserData->CustomSizeParams,
                   sizeof(pPpdData->CustomSizeParams));

        //
        // Process the printer features and handle any special glitches
        //

        VProcessPrinterFeatures(pParserData);

        //
        // Pack UIConstraints information
        //

        VPackUIConstraints(pParserData);

        //
        // Pack OrderDependency and QueryOrderDependency information
        //

        VPackOrderDependency(pParserData, &pPpdData->OrderDeps, pParserData->pOrderDep);
        VPackOrderDependency(pParserData, &pPpdData->QueryOrderDeps, pParserData->pQueryOrderDep);

        //
        // Pack printer features and options
        //

        VPackPrinterFeatures(pParserData);

        //
        // Fill out fields in PPDDATA structure
        //

        pPpdData->dwSizeOfStruct = sizeof(PPDDATA);
        pPpdData->dwExtensions = pParserData->dwExtensions;
        pPpdData->dwSetResType = pParserData->dwSetResType;
        pPpdData->dwPSVersion = pParserData->dwPSVersion;

        //
        // Scan the document-sticky feature list to check if "OutputOrder" is available.
        // If it is, remember its feature index, which will be used by UI code.
        //

        {
            PFEATURE    pFeature;
            DWORD       dwIndex;
            PCSTR       pstrKeywordName;

            pPpdData->dwOutputOrderIndex = INVALID_FEATURE_INDEX;

            pFeature = OFFSET_TO_POINTER(pInfoHdr, pUIInfo->loFeatureList);

            ASSERT(pFeature != NULL);

            for (dwIndex = 0; dwIndex < pUIInfo->dwDocumentFeatures; dwIndex++, pFeature++)
            {
                if ((pstrKeywordName = OFFSET_TO_POINTER(pInfoHdr, pFeature->loKeywordName)) &&
                    strcmp(pstrKeywordName, "OutputOrder") == EQUAL_STRING)
                {
                    pPpdData->dwOutputOrderIndex = dwIndex;
                    break;
                }
            }
        }

        VPackInvocation(pParserData, &pPpdData->PSVersion, &pParserData->PSVersion);
        VPackInvocation(pParserData, &pPpdData->Product, &pParserData->Product);

        if (SUPPORT_CUSTOMSIZE(pUIInfo))
        {
            //
            // If neither roll-fed nor cut-sheet flag is set, assume to be roll-fed
            //

            if (! (pPpdData->dwCustomSizeFlags & (CUSTOMSIZE_CUTSHEET|CUSTOMSIZE_ROLLFED)))
                pPpdData->dwCustomSizeFlags |= CUSTOMSIZE_ROLLFED;

            //
            // If roll-fed flag is not set, default must be cut-sheet
            //

            if (! (pPpdData->dwCustomSizeFlags & CUSTOMSIZE_ROLLFED))
                pPpdData->dwCustomSizeFlags |= CUSTOMSIZE_DEFAULTCUTSHEET;
        }

        VPackInvocation(pParserData, &pPpdData->PatchFile, &pParserData->PatchFile);
        VPackInvocation(pParserData, &pPpdData->JclBegin, &pParserData->JclBegin);
        VPackInvocation(pParserData, &pPpdData->JclEnterPS, &pParserData->JclEnterPS);
        VPackInvocation(pParserData, &pPpdData->JclEnd, &pParserData->JclEnd);
        VPackInvocation(pParserData, &pPpdData->ManualFeedFalse, &pParserData->ManualFeedFalse);

        //
        // Pack NT4 feature index mapping information
        //

        VPackNt4Mapping(pParserData);

        //
        // Pack device font information
        //

        VPackDeviceFonts(pParserData);

        //
        // Pack JobPatchFile information
        //

        VPackJobPatchFiles(pParserData);

        //
        // Pack default TrueType font substitution table
        //

        if (pParserData->pTTFontSubs == NULL || pParserData->uCodePage == CP_ERROR)
            VPackDefaultTrueTypeSubstTable(pParserData);
        else
            VPackTrueTypeSubstTable(pParserData);

        pInfoHdr->RawData.dwFileSize = pParserData->dwBufSize;
        bResult = TRUE;
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        ERR(("PackBinaryData failed.\n"));
    }

    return bResult;
}



BOOL
BSaveBinaryDataToFile(
    PPARSERDATA pParserData,
    PTSTR       ptstrPpdFilename
    )

/*++

Routine Description:

    Cache the binary PPD data in a file

Arguments:

    pParserData - Points to parser data structure
    ptstrPpdFilename - Specifies the PPD filename

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    PTSTR   ptstrBpdFilename;
    HANDLE  hFile;
    DWORD   dwBytesWritten;
    BOOL    bResult = FALSE;

    VALIDATE_PARSER_DATA(pParserData);

    //
    // Generate a binary file name based the original filename
    // Create a file and write data to it
    //

    if ((ptstrBpdFilename = GenerateBpdFilename(ptstrPpdFilename)) != NULL &&
        (hFile = CreateFile(ptstrBpdFilename,
                            GENERIC_WRITE,
                            0,
                            NULL,
                            CREATE_ALWAYS,
                            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS,
                            NULL)) != INVALID_HANDLE_VALUE)
    {
        bResult = WriteFile(hFile,
                            pParserData->pubBufStart,
                            pParserData->dwBufSize,
                            &dwBytesWritten,
                            NULL) &&
                  (pParserData->dwBufSize == dwBytesWritten);

        CloseHandle(hFile);
    }

    if (! bResult)
        ERR(("Couldn't cache binary PPD data: %d\n", GetLastError()));

    MemFree(ptstrBpdFilename);
    return bResult;
}



VOID
VFreeParserData(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Free up memory used to hold parser data structure

Arguments:

    pParserData - Points to parser data structure

Return Value:

    NONE

--*/

{
    VALIDATE_PARSER_DATA(pParserData);

    if (pParserData->pubBufStart)
        VirtualFree(pParserData->pubBufStart, 0, MEM_RELEASE);

    MemFree(pParserData->Value.pbuf);
    HeapDestroy(pParserData->hHeap);
}



PPARSERDATA
PAllocParserData(
    VOID
    )

/*++

Routine Description:

    Allocate memory to hold PPD parser data

Arguments:

    NONE

Return Value:

    Pointer to allocated parser data structure
    NULL if there is an error

--*/

{
    PPARSERDATA pParserData;
    HANDLE      hHeap;

    //
    // Create a heap and allocate memory space from it
    //

    if (! (hHeap = HeapCreate(0, 16*1024, 0)) ||
        ! (pParserData = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(PARSERDATA))))
    {
        ERR(("Memory allocation failed: %d\n", GetLastError()));

        if (hHeap)
            HeapDestroy(hHeap);

        return NULL;
    }

    pParserData->hHeap = hHeap;
    pParserData->pvStartSig = pParserData->pvEndSig = pParserData;

    //
    // Initialize the parser data structure - we only need to worry
    // about non-zero fields here.
    //

    pParserData->dwChecksum32 = 0xFFFFFFFF;
    pParserData->dwFreeMem = min(MIN_FREEMEM_L1, MIN_FREEMEM_L2);
    pParserData->dwJobTimeout = DEFAULT_JOB_TIMEOUT;
    pParserData->dwWaitTimeout = DEFAULT_WAIT_TIMEOUT;
    pParserData->iManualFeedIndex =
    pParserData->iReqPageRgnIndex =
    pParserData->iDefInstallMemIndex = -1;
    pParserData->wNt4Checksum = 0;
    pParserData->dwPpdFlags = PPDFLAG_PRINTPSERROR;

    //
    // Initialize buffers for storing keyword, option, translation, and value.
    // Build up data structures to speed up keyword lookup
    //

    SET_BUFFER(&pParserData->Keyword, pParserData->achKeyword);
    SET_BUFFER(&pParserData->Option,  pParserData->achOption);
    SET_BUFFER(&pParserData->Xlation, pParserData->achXlation);

    if (IGrowValueBuffer(&pParserData->Value) != PPDERR_NONE ||
        ! BInitKeywordLookup(pParserData))
    {
        VFreeParserData(pParserData);
        return NULL;
    }

    return pParserData;
}



BOOL
BRememberSourceFilename(
    PPARSERDATA pParserData,
    PTSTR       ptstrFilename
    )

/*++

Routine Description:

    Remember the full pathname to the source PPD file

Arguments:

    pParserData - Points to parser data structure
    ptstrFilename - Specifies the source PPD filename

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    PLISTOBJ    pItem;
    TCHAR       ptstrFullname[MAX_PATH];
    PTSTR       ptstrFilePart;
    DWORD       dwSizeChars, dwSizeChars2;
    DWORD       dwSizeBytes;  // size of buffer to hold pathname

    //
    // Get the full pathname to the specified source PPD file
    //

    dwSizeChars = GetFullPathName(ptstrFilename, MAX_PATH, ptstrFullname, &ptstrFilePart);

    if (dwSizeChars == 0)
    {
        ERR(("GetFullPathName failed: %d\n", GetLastError()));
        return FALSE;
    }

    //
    // Remember the source PPD filenames
    //

    dwSizeBytes = (dwSizeChars + 1) * sizeof(TCHAR);

    if (! (pItem = ALLOC_PARSER_MEM(pParserData, sizeof(LISTOBJ) + dwSizeBytes)))
        return FALSE;

    pItem->pstrName = (PSTR) ((PBYTE) pItem + sizeof(LISTOBJ));

    // let GetFullPathName write directly into the real buffer!
    dwSizeChars2 = GetFullPathName(ptstrFilename, dwSizeChars + 1, (PTSTR)pItem->pstrName, &ptstrFilePart);

    if((dwSizeChars2 == 0)  ||  (dwSizeChars2 > dwSizeChars))
    {
        ERR(("GetFullPathName failed: %d\n", GetLastError()));
        return FALSE;       // no need to free pItem since  Heap is destroyed automatically.
    }

    pItem->pNext = pParserData->pPpdFileNames;
    pParserData->pPpdFileNames = pItem;
    return TRUE;
}



// 16-bit crc checksum table - copied from win95

static const WORD Crc16Table[] =
{
    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
    0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
    0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
    0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
    0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
    0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
    0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
    0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
    0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
    0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
    0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
    0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
    0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
    0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
    0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
    0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
    0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
    0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
    0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
    0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
    0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
    0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
    0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
    0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
    0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
    0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
    0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
    0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
    0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
    0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
    0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
    0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};

WORD
WComputeCrc16Checksum(
    IN PBYTE    pbuf,
    IN DWORD    dwCount,
    IN WORD     wChecksum
    )

/*++

Routine Description:

    Compute the 16-bit CRC checksum on a buffer of data

Arguments:

    pbuf - Points to a data buffer
    dwCount - Number of bytes in the data buffer
    wChecksum - Initial checksum value

Return Value:

    Resulting checksum value

--*/

{
    while (dwCount--)
        wChecksum = Crc16Table[(wChecksum >> 8) ^ *pbuf++] ^ (wChecksum << 8);

    return wChecksum;
}



DWORD
dwComputeFeatureOptionChecksum(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Compute checksum for only feature/option keyword strings.

Arguments:

    pParserData - Points to parser data structure

Return Value:

    32bit checksum value

--*/

{
    PINFOHEADER pInfoHdr;
    PUIINFO     pUIInfo;
    PFEATURE    pFeature;
    POPTION     pOption;
    DWORD       dwFeatureCount, dwFeatureIndex, dwOptionCount, dwOptionIndex;
    PBYTE       pBuf;
    DWORD       dwBufSize;

    VALIDATE_PARSER_DATA(pParserData);

    pInfoHdr =  pParserData->pInfoHdr;
    pUIInfo  =  (PUIINFO)((PBYTE)pInfoHdr + sizeof(INFOHEADER));

    dwFeatureCount = pInfoHdr->RawData.dwDocumentFeatures + pInfoHdr->RawData.dwPrinterFeatures;

    pFeature = OFFSET_TO_POINTER(pInfoHdr, pUIInfo->loFeatureList);

    ASSERT(dwFeatureCount == 0 || pFeature != NULL);

    for (dwFeatureIndex = 0; dwFeatureIndex < dwFeatureCount; dwFeatureIndex++, pFeature++)
    {
        pBuf = OFFSET_TO_POINTER(pInfoHdr, pFeature->loKeywordName);

        ASSERT(pBuf != NULL);

        dwBufSize = strlen((PSTR)pBuf) + 1;

        pParserData->dwChecksum32 = ComputeCrc32Checksum(pBuf, dwBufSize, pParserData->dwChecksum32);

        if (dwOptionCount = pFeature->Options.dwCount)
        {
            pOption = OFFSET_TO_POINTER(pInfoHdr, pFeature->Options.loOffset);

            ASSERT(pOption != NULL);

            for (dwOptionIndex = 0; dwOptionIndex < dwOptionCount; dwOptionIndex++)
            {
                pBuf = OFFSET_TO_POINTER(pInfoHdr, pOption->loKeywordName);
                dwBufSize = strlen((PSTR)pBuf) + 1;

                pParserData->dwChecksum32 = ComputeCrc32Checksum(pBuf, dwBufSize, pParserData->dwChecksum32);
                pOption = (POPTION)((PBYTE)pOption + pFeature->dwOptionSize);
            }
        }
    }

    return pParserData->dwChecksum32;
}



DWORD
dwCalcMaxKeywordSize(
    IN PPARSERDATA pParserData,
    IN INT         iMode
    )

/*++

Routine Description:

    Calculate the maximum buffer size for storing feature/option
    keyword pairs in Registry.

Arguments:

    pParserData - Points to parser data structure
    iMode       - For either doc- or printer- sticky features

Return Value:

    The maximum buffer size needed for storing feature/option keyword paris.

--*/

{
    PINFOHEADER pInfoHdr;
    PUIINFO     pUIInfo;
    PFEATURE    pFeature;
    POPTION     pOption;
    DWORD       dwStart, dwFeatureCount, dwFeatureIndex, dwOptionCount, dwOptionIndex;
    PSTR        pBuf;
    DWORD       dwMaxSize, dwOptionSize, dwOptionMax;

    VALIDATE_PARSER_DATA(pParserData);

    dwMaxSize = 0;

    pInfoHdr = pParserData->pInfoHdr;
    pUIInfo  = pParserData->pUIInfo;

    if (iMode == MODE_DOCUMENT_STICKY)
    {
        dwStart = 0;
        dwFeatureCount = pUIInfo->dwDocumentFeatures;
    }
    else
    {
        ASSERT(iMode == MODE_PRINTER_STICKY);

        dwStart = pUIInfo->dwDocumentFeatures;
        dwFeatureCount = pUIInfo->dwPrinterFeatures;
    }

    pFeature = OFFSET_TO_POINTER(pInfoHdr, pUIInfo->loFeatureList);

    ASSERT(dwFeatureCount == 0 || pFeature != NULL);

    pFeature += dwStart;

    for (dwFeatureIndex = 0; dwFeatureIndex < dwFeatureCount; dwFeatureIndex++, pFeature++)
    {
        pBuf = OFFSET_TO_POINTER(pInfoHdr, pFeature->loKeywordName);

        ASSERT(pBuf != NULL);

        dwMaxSize += strlen(pBuf) + 1;

        dwOptionMax = 0;
        if (dwOptionCount = pFeature->Options.dwCount)
        {
            pOption = OFFSET_TO_POINTER(pInfoHdr, pFeature->Options.loOffset);

            ASSERT(pOption != NULL);

            for (dwOptionIndex = 0; dwOptionIndex < dwOptionCount; dwOptionIndex++)
            {
                pBuf = OFFSET_TO_POINTER(pInfoHdr, pOption->loKeywordName);
                dwOptionSize = strlen(pBuf) + 1;

                if (pFeature->dwUIType != UITYPE_PICKMANY)
                {
                    if (dwOptionMax < dwOptionSize)
                        dwOptionMax = dwOptionSize;
                }
                else // count all options for PickMany feature
                    dwMaxSize += dwOptionSize;

                pOption = (POPTION)((PBYTE)pOption + pFeature->dwOptionSize);
            }
        }

        //
        // Add the max option keyword size here for non-PickMany feature
        //

        if (pFeature->dwUIType != UITYPE_PICKMANY)
            dwMaxSize += dwOptionMax;

        //
        // One extra byte for the \0x0A delimiter between features
        //

        dwMaxSize += 1;
    }

    dwMaxSize += KEYWORD_SIZE_EXTRA;

    return dwMaxSize;
}



PPDERROR
IParseFile(
    PPARSERDATA pParserData,
    PTSTR       ptstrFilename
    )

/*++

Routine Description:

    Parse a PPD file

Arguments:

    pParserData - Points to parser data structure
    ptstrFilename - Specifies the name of the file to be parsed

Return Value:

    PPDERR_NONE if successful, error code otherwise

--*/

{
    PPDERROR    iStatus;
    PFILEOBJ    pFile;
    INT         iSyntaxErrors = 0;

    //
    // Map the file into memory for read-only access
    //

    VALIDATE_PARSER_DATA(pParserData);
    ASSERT(ptstrFilename != NULL);

    if (! BRememberSourceFilename(pParserData, ptstrFilename) ||
        ! (pFile = PCreateFileObj(ptstrFilename)))
    {
        return PPDERR_FILE;
    }

    pParserData->pFile = pFile;

    #if 0
    //
    // Compute the 32-bit CRC checksum of the file content
    //

    pParserData->dwChecksum32 =
    ComputeCrc32Checksum(pFile->pubStart, pFile->dwFileSize, pParserData->dwChecksum32);
    #endif

    //
    // Compute the 16-bit CRC checksum as well for PS4 compatibility
    //

    pParserData->wNt4Checksum =
    WComputeCrc16Checksum(pFile->pubStart, pFile->dwFileSize, pParserData->wNt4Checksum);

    //
    // Process entries in the file
    //

    while ((iStatus = IParseEntry(pParserData)) != PPDERR_EOF)
    {
        if (iStatus == PPDERR_SYNTAX)
            iSyntaxErrors++;
        else if (iStatus != PPDERR_NONE)
        {
            VDeleteFileObj(pFile);
            return iStatus;
        }
    }

    if (END_OF_FILE(pFile) && !END_OF_LINE(pFile))
        TERSE(("Incomplete last line ignored.\n"));

    //
    // Unmap the file and return to the caller
    //

    VDeleteFileObj(pFile);

    return (iSyntaxErrors > 0) ? PPDERR_SYNTAX : PPDERR_NONE;
}



PRAWBINARYDATA
PpdParseTextFile(
    PTSTR   ptstrPpdFilename
    )

/*++

Routine Description:

    PPD parser main entry point

Arguments:

    ptstrPpdFilename - Specifies the PPD file to be parsed

Return Value:

    Pointer to parsed binary PPD data, NULL if there is an error

--*/

{
    PPARSERDATA     pParserData;
    PPDERROR        iStatus;
    PRAWBINARYDATA  pRawData = NULL;

    //
    // Allocate parser data structure
    //

    ASSERT(ptstrPpdFilename != NULL);

    if (! (pParserData = PAllocParserData()))
        return NULL;

    //
    // Parse the PPD file
    //

    iStatus = IParseFile(pParserData, ptstrPpdFilename);

    if (iStatus == PPDERR_NONE || iStatus == PPDERR_SYNTAX)
    {
        //
        // Pack the parsed information into binary format
        //

        pParserData->bErrorFlag = FALSE;

        if (BPackBinaryData(pParserData))
        {
            //
            // After binary data is packed, we calculate the 32bit checksum
            // for only feature/option keyword strings (instead of for the
            // whole PPD file). Doing this will enable us to retain option
            // selections when the PPD file is modified without feature/option
            // changes.
            //

            pParserData->pInfoHdr->RawData.dwChecksum32 = dwComputeFeatureOptionChecksum(pParserData);

            //
            // Calculate the maximum buffer sizes for storing feature/option
            // keyword pairs in Registry
            //

            pParserData->pUIInfo->dwMaxDocKeywordSize = dwCalcMaxKeywordSize(pParserData, MODE_DOCUMENT_STICKY);
            pParserData->pUIInfo->dwMaxPrnKeywordSize = dwCalcMaxKeywordSize(pParserData, MODE_PRINTER_STICKY);

            #ifndef WINNT_40

            pParserData->pPpdData->dwUserDefUILangID = (DWORD)GetUserDefaultUILanguage();

            #else

            pParserData->pPpdData->dwUserDefUILangID = 0;

            #endif

            //
            // Save binary data to a file
            //

            (VOID) BSaveBinaryDataToFile(pParserData, ptstrPpdFilename);

            //
            // Here we'll copy the packed binary data to a different buffer.
            // This is necessary because the packed data buffer was allocated
            // using VirtualAlloc. If we return that pointer back to the caller,
            // the caller would need to call VirtualFree to release it.
            //

            if (pRawData = MemAlloc(pParserData->dwBufSize))
            {
                CopyMemory(pRawData, pParserData->pubBufStart, pParserData->dwBufSize);
            }
            else
                ERR(("Memory allocation failed: %d\n", GetLastError()));
        }
    }

    if (iStatus == PPDERR_SYNTAX || pParserData->bErrorFlag)
        WARNING(("Errors found in %ws\n", ptstrPpdFilename));

    VFreeParserData(pParserData);
    return pRawData;
}



PPDERROR
IGrowValueBuffer(
    PBUFOBJ pBufObj
    )

/*++

Routine Description:

    Grow the buffer used for holding the entry value

Arguments:

    pBufObj - Specifies the buffer to be enlarged

Return Value:

    PPDERR_NONE if successful, error code otherwise

--*/

#define VALUE_BUFFER_INCREMENT  (1*KBYTES)

{
    DWORD   dwNewLen = pBufObj->dwMaxLen + VALUE_BUFFER_INCREMENT;
    PBYTE   pbuf;

    if (! IS_BUFFER_FULL(pBufObj))
        WARNING(("Trying to grow buffer while it's not yet full.\n"));

    if (! (pbuf = MemAllocZ(dwNewLen)))
    {
        ERR(("Memory allocation failed: %d\n", GetLastError()));
        return PPDERR_MEMORY;
    }

    if (pBufObj->pbuf)
    {
        CopyMemory(pbuf, pBufObj->pbuf, pBufObj->dwSize);
        MemFree(pBufObj->pbuf);
    }

    pBufObj->pbuf = pbuf;
    pBufObj->dwMaxLen = dwNewLen;
    return PPDERR_NONE;
}