/*++

Copyright (c) 1996-1997  Microsoft Corporation

Module Name:

    ppdkwd.c

Abstract:

    Functions for interpreting the semantics elements of a PPD file

Environment:

    PostScript driver, PPD parser

Revision History:

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

    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 <math.h>

//
// Data structure for representing entries in a keyword table
//

typedef PPDERROR (*KWDPROC)(PPARSERDATA);

typedef struct _KWDENTRY {

    PCSTR   pstrKeyword;        // keyword name
    KWDPROC pfnProc;            // keyword handler function
    DWORD   dwFlags;            // misc. flag bits

} KWDENTRY, *PKWDENTRY;

//
// Constants for KWDENTRY.flags field. The low order byte is used to indicate value types.
//

#define REQ_OPTION      0x0100
#define ALLOW_MULTI     0x0200
#define ALLOW_HEX       0x0400
#define DEFAULT_PROC    0x0800

#define INVOCA_VALUE    (VALUETYPE_QUOTED | VALUETYPE_SYMBOL)
#define QUOTED_VALUE    (VALUETYPE_QUOTED | ALLOW_HEX)
#define QUOTED_NOHEX    VALUETYPE_QUOTED
#define STRING_VALUE    VALUETYPE_STRING

#define GENERIC_ENTRY(featureId)    ((featureId) << 16)

//
// Give a warning when there are duplicate entries for the same option
//

#define WARN_DUPLICATE() \
        TERSE(("%ws: Duplicate entries of '*%s %s' on line %d\n", \
               pParserData->pFile->ptstrFileName, \
               pParserData->achKeyword, \
               pParserData->achOption, \
               pParserData->pFile->iLineNumber))

//
// Default keyword prefix string
//

#define HAS_DEFAULT_PREFIX(pstr) \
        (strncmp((pstr), gstrDefault, strlen(gstrDefault)) == EQUAL_STRING)

//
// Determine whether a string starts with JCL prefix
//

#define HAS_JCL_PREFIX(pstr) (strncmp((pstr), "JCL", 3) == EQUAL_STRING)

//
// Forward declaration of local functions
//

PPDERROR IVerifyValueType(PPARSERDATA, DWORD);
PPDERROR IGenericOptionProc(PPARSERDATA, PFEATUREOBJ);
PPDERROR IGenericDefaultProc(PPARSERDATA, PFEATUREOBJ);
PPDERROR IGenericQueryProc(PPARSERDATA, PFEATUREOBJ);
PFEATUREOBJ PCreateFeatureItem(PPARSERDATA, DWORD);
PKWDENTRY PSearchKeywordTable(PPARSERDATA, PSTR, PINT);



PPDERROR
IInterpretEntry(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Interpret an entry parsed from a printer description file

Arguments:

    pParserData - Points to parser data structure

Return Value:

    Status code

--*/

{
    PSTR        pstrKeyword, pstrRealKeyword;
    PPDERROR    iStatus;
    PKWDENTRY   pKwdEntry;
    INT         iIndex;
    BOOL        bQuery, bDefault;

    //
    // Get a pointer to the keyword string and look for
    // *? and *Default prefix in front of the keyword.
    //

    pstrRealKeyword = pstrKeyword = pParserData->achKeyword;
    bQuery = bDefault = FALSE;

    // NOTE: We don't have any use for query entries so don't parse them here.
    // This helps us somewhat to preserve the feature index from NT4.

    if (FALSE && *pstrKeyword == QUERY_CHAR)
    {
        bQuery = TRUE;
        pstrRealKeyword++;
    }
    else if (HAS_DEFAULT_PREFIX(pstrKeyword))
    {
        bDefault = TRUE;
        pstrRealKeyword += strlen(gstrDefault);
    }

    //
    // Set up a convenient pointer to the entry value
    //

    pParserData->pstrValue = (PSTR) pParserData->Value.pbuf;

    //
    // If we're within an OpenUI/CloseUI pair and the keyword
    // in the current entry matches what's in OpenUI, then we
    // will handle the current entry using a generic procedure.
    //

    if (pParserData->pOpenFeature &&
        pParserData->pOpenFeature->dwFeatureID == GID_UNKNOWN &&
        strcmp(pstrRealKeyword, pParserData->pOpenFeature->pstrName) == EQUAL_STRING)
    {
        pKwdEntry = NULL;
    }
    else
    {
        //
        // Find out if the keyword has built-in support
        //

        pKwdEntry = PSearchKeywordTable(pParserData, pstrRealKeyword, &iIndex);

        //
        // For *Default keywords, if we failed to find the keyword without
        // the prefix, try it again with the prefix.
        //

        if (bDefault &&
            (pKwdEntry == NULL || pKwdEntry->pfnProc != NULL) &&
            (pKwdEntry = PSearchKeywordTable(pParserData, pstrKeyword, &iIndex)))
        {
            bDefault = FALSE;
        }

        //
        // Ignore unsupported keywords
        //

        if ((pKwdEntry == NULL) ||
            ((bQuery || bDefault) && (pKwdEntry->pfnProc != NULL)))
        {
            VERBOSE(("Keyword not supported: *%s\n", pstrKeyword));
            return PPDERR_NONE;
        }
    }

    //
    // Determine if the entry should be handle by the generic procedure
    //

    if (pKwdEntry == NULL || pKwdEntry->pfnProc == NULL)
    {
        PFEATUREOBJ pFeature;
        DWORD       dwValueType;
        PPDERROR    (*pfnGenericProc)(PPARSERDATA, PFEATUREOBJ);

        //
        // Make sure the value type matches what's expected
        //

        if (bQuery)
        {
            pfnGenericProc = IGenericQueryProc;
            dwValueType = INVOCA_VALUE;
        }
        else if (bDefault)
        {
            pfnGenericProc = IGenericDefaultProc;
            dwValueType = STRING_VALUE;
        }
        else
        {
            pfnGenericProc = IGenericOptionProc;
            dwValueType = INVOCA_VALUE | REQ_OPTION;
        }

        if ((iStatus = IVerifyValueType(pParserData, dwValueType)) != PPDERR_NONE)
            return iStatus;

        //
        // Call the appropriate generic procedure
        //

        pFeature = (pKwdEntry == NULL) ?
                        pParserData->pOpenFeature :
                        PCreateFeatureItem(pParserData, HIWORD(pKwdEntry->dwFlags));

        return pfnGenericProc(pParserData, pFeature);
    }
    else
    {
        //
        // Screen out duplicate keyword entries
        //

        if (! (pKwdEntry->dwFlags & (ALLOW_MULTI|REQ_OPTION)))
        {
            if (pParserData->pubKeywordCounts[iIndex])
            {
                WARN_DUPLICATE();
                return PPDERR_NONE;
            }

            pParserData->pubKeywordCounts[iIndex]++;
        }

        //
        // Make sure the value type matches what's expected
        // Take care of embedded hexdecimal strings if necessary
        //

        if ((iStatus = IVerifyValueType(pParserData, pKwdEntry->dwFlags)) != PPDERR_NONE)
            return iStatus;

        //
        // Invoke the specific procedure to handle built-in keywords
        //

        return (pKwdEntry->pfnProc)(pParserData);
    }
}



PPDERROR
IVerifyValueType(
    PPARSERDATA pParserData,
    DWORD       dwExpectedType
    )

/*++

Routine Description:

    Verify the value type of the current entry matches what's expected

Arguments:

    pParserData - Points to parser data structure
    dwExpectedType - Expected value type

Return Value:

    Status code

--*/

{
    DWORD   dwValueType;

    //
    // Check for following syntax error conditions:
    // 1. The entry requires an option keyword but no option keyword is present
    // 2. The entry doesn't require an option keyword but an option keyword is present
    //

    if ((dwExpectedType & REQ_OPTION) &&
        IS_BUFFER_EMPTY(&pParserData->Option))
    {
        return ISyntaxError(pParserData->pFile, "Missing option keyword");
    }

    if (! (dwExpectedType & REQ_OPTION) &&
        ! IS_BUFFER_EMPTY(&pParserData->Option))
    {
        return ISyntaxError(pParserData->pFile, "Extra option keyword");
    }

    //
    // Tolerate the following syntax error conditions:
    // 1. The entry requires a quoted value but a string value is provided.
    // 2. The entry requires a string value but a quoted value is provided.
    //

    switch (dwValueType = pParserData->dwValueType)
    {
    case VALUETYPE_STRING:

        if (dwExpectedType & VALUETYPE_QUOTED)
        {
            TERSE(("%ws: Expect QuotedValue instead of StringValue on line %d\n",
                   pParserData->pFile->ptstrFileName,
                   pParserData->pFile->iLineNumber));

            dwValueType = VALUETYPE_QUOTED;
        }
        break;

    case VALUETYPE_QUOTED:

        if (dwExpectedType & VALUETYPE_STRING)
        {
            TERSE(("%ws: Expect StringValue instead of QuotedValue on line %d\n",
                   pParserData->pFile->ptstrFileName,
                   pParserData->pFile->iLineNumber));

            if (IS_BUFFER_EMPTY(&pParserData->Value))
                return ISyntaxError(pParserData->pFile, "Empty string value");

            dwValueType = VALUETYPE_STRING;
        }
        break;
    }

    //
    // Return syntax error if the provided value type doesn't match what's expected
    //

    if ((dwExpectedType & dwValueType) == 0)
        return ISyntaxError(pParserData->pFile, "Value type mismatch");

    //
    // If the value field is a quoted string and one of the following conditions
    // is true, then we need to process any embedded hexdecimal strings within
    // the quoted string:
    // 1. The entry expects a QuotedValue.
    // 2. The entry expects an InvocationValue and appears inside JCLOpenUI/JCLCloseUI
    //

    if (dwValueType == VALUETYPE_QUOTED)
    {
        if ((dwExpectedType & ALLOW_HEX) ||
            ((dwExpectedType & VALUETYPE_MASK) == INVOCA_VALUE && pParserData->bJclFeature))
        {
            if (! BConvertHexString(&pParserData->Value))
                return ISyntaxError(pParserData->pFile, "Invalid embedded hexdecimal string");
        }
        else if (! BIs7BitAscii(pParserData->pstrValue))
        {
            //
            // Only allow 7-bit ASCII characters inside invocation string
            //

            return ISyntaxError(pParserData->pFile, "Non-printable ASCII character");
        }
    }

    return PPDERR_NONE;
}



BOOL
BGetIntegerFromString(
    PSTR   *ppstr,
    LONG   *plValue
    )

/*++

Routine Description:

    Parse an unsigned decimal integer value from a character string

Arguments:

    ppstr - Points to a string pointer. On entry, it contains a pointer
        to the beginning of the number string. On exit, it points to
        the first non-space character after the number string.
    plValue - Points to a variable for storing parsed number

Return Value:

    TRUE if a number is successfully parsed, FALSE if there is an error

--*/

{
    LONG    lValue;
    PSTR    pstr = *ppstr;
    BOOL    bNegative = FALSE;

    //
    // Skip any leading space characters and
    // look for the sign character (if any)
    //

    while (IS_SPACE(*pstr))
        pstr++;

    if (*pstr == '-')
    {
        bNegative = TRUE;
        pstr++;
    }

    if (! IS_DIGIT(*pstr))
    {
        TERSE(("Invalid integer number: %s\n", pstr));
        return FALSE;
    }

    //
    // NOTE: Overflow conditions are ignored.
    //

    lValue = 0;

    while (IS_DIGIT(*pstr))
        lValue = lValue * 10 + (*pstr++ - '0');

    //
    // Skip any trailing space characters
    //

    while (IS_SPACE(*pstr))
        pstr++;

    *ppstr = pstr;
    *plValue = bNegative ? -lValue : lValue;
    return TRUE;
}



BOOL
BGetFloatFromString(
    PSTR   *ppstr,
    PLONG   plValue,
    INT     iType
    )

/*++

Routine Description:

    Parse an unsigned floating-point number from a character string

Arguments:

    ppstr - Points to a string pointer. On entry, it contains a pointer
        to the beginning of the number string. On exit, it points to
        the first non-space character after the number string.
    plValue - Points to a variable for storing the parsed number
    iType - How to convert the floating-point number before returning
            FLTYPE_FIX - convert it to 24.8 format fixed-point number
            FLTYPE_INT - convert it to integer
            FLTYPE_POINT - convert it from point to micron
            FLTYPE_POINT_ROUNDUP - round it up and convert it from point to micron
            FLTYPE_POINT_ROUNDDOWN - round it down and convert it from point to micron

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    double  value, scale;
    PSTR    pstr = *ppstr;
    BOOL    bNegative = FALSE, bFraction = FALSE;

    //
    // Skip any leading space characters and
    // look for the sign character (if any)
    //

    while (IS_SPACE(*pstr))
        pstr++;

    if (*pstr == '-')
    {
        bNegative = TRUE;
        pstr++;
    }

    if (!IS_DIGIT(*pstr) && *pstr != '.')
    {
        TERSE(("Invalid floating-point number\n"));
        return FALSE;
    }

    //
    // Integer portion
    //

    value = 0.0;

    while (IS_DIGIT(*pstr))
        value = value * 10.0 + (*pstr++ - '0');

    //
    // Fractional portion
    //

    if (*pstr == '.')
    {
        bFraction = TRUE;
        pstr++;

        if (! IS_DIGIT(*pstr))
        {
            TERSE(("Invalid floating-point number\n"));
            return FALSE;
        }

        scale = 0.1;

        while (IS_DIGIT(*pstr))
        {
            value += scale * (*pstr++ - '0');
            scale *= 0.1;
        }
    }

    //
    // Skip any trailing space characters
    //

    while (IS_SPACE(*pstr))
        pstr++;

    //
    // Perform requested round up or round down only if
    // fractional portion is present.
    //

    if (bFraction)
    {
        if (iType == FLTYPE_POINT_ROUNDUP)
            value = ceil(value);
        else if (iType == FLTYPE_POINT_ROUNDDOWN)
            value = (LONG)value;
    }

    //
    // Convert the return value to the specified format
    //

    switch (iType)
    {
    case FLTYPE_POINT:
    case FLTYPE_POINT_ROUNDUP:
    case FLTYPE_POINT_ROUNDDOWN:

        value = (value * 25400.0) / 72.0;
        break;

    case FLTYPE_FIX:

        value *= FIX_24_8_SCALE;
        break;

    default:

        ASSERT(iType == FLTYPE_INT);
        break;
    }

    //
    // Guard against overflow conditions
    //

    if (value >= MAX_LONG)
    {
        TERSE(("Floating-point number overflow\n"));
        return FALSE;
    }

    *ppstr = pstr;
    *plValue = (LONG) (value + 0.5);

    if (bNegative)
    {
        TERSE(("Negative number treated as 0\n"));
        *plValue = 0;
    }

    return TRUE;
}



BOOL
BSearchStrTable(
    PCSTRTABLE  pTable,
    PSTR        pstrKeyword,
    DWORD      *pdwValue
    )

/*++

Routine Description:

    Search for a keyword from a string table

Arguments:

    pTable - Specifies the string table to be search
    pstrKeyword - Specifies the keyword we're interested in
    pdwValue - Points to a variable for storing value corresponding to the given keyword

Return Value:

    TRUE if the given keyword is found in the table, FALSE otherwise

--*/

{
    ASSERT(pstrKeyword != NULL);

    while (pTable->pstrKeyword != NULL &&
           strcmp(pTable->pstrKeyword, pstrKeyword) != EQUAL_STRING)
    {
        pTable++;
    }

    *pdwValue = pTable->dwValue;
    return (pTable->pstrKeyword != NULL);
}



PSTR
PstrParseString(
    PPARSERDATA pParserData,
    PBUFOBJ     pBufObj
    )

/*++

Routine Description:

    Duplicate a character string from a buffer object

Arguments:

    pParserData - Points to parser data structure
    pBufObj - Specifies the buffer object containing the character string to be duplicated

Return Value:

    Pointer to a copy of the specified string
    NULL if there is an error

--*/

{
    PSTR    pstr;

    ASSERT(! IS_BUFFER_EMPTY(pBufObj));

    if (pstr = ALLOC_PARSER_MEM(pParserData, pBufObj->dwSize + 1))
        CopyMemory(pstr, pBufObj->pbuf, pBufObj->dwSize + 1);
    else
        ERR(("Memory allocation failed: %d\n", GetLastError()));

    return pstr;
}



PPDERROR
IParseInvocation(
    PPARSERDATA pParserData,
    PINVOCOBJ   pInvocation
    )

/*++

Routine Description:

    Parse the content of value buffer to an invocation string

Arguments:

    pParserData - Points to parser data structure
    pInvocation - Specifies a buffer for storing the parsed invocation string

Return Value:

    Status code

--*/

{
    ASSERT(pInvocation->pvData == NULL);

    //
    // Determine if the invocation is a quoted string or a symbol reference.
    // In case of symbol reference, we save the name of the symbol in pvData
    // field (including the leading ^ character).
    //

    if (pParserData->dwValueType == VALUETYPE_SYMBOL)
    {
        PSTR    pstrSymbolName;

        if (! (pstrSymbolName = PstrParseString(pParserData, &pParserData->Value)))
            return PPDERR_MEMORY;

        pInvocation->pvData = pstrSymbolName;
        MARK_SYMBOL_INVOC(pInvocation);
    }
    else
    {
        PVOID   pvData;

        if (! (pvData = ALLOC_PARSER_MEM(pParserData, pParserData->Value.dwSize + 1)))
        {
            ERR(("Memory allocation failed\n"));
            return PPDERR_MEMORY;
        }

        pInvocation->pvData = pvData;
        pInvocation->dwLength = pParserData->Value.dwSize;
        ASSERT(! IS_SYMBOL_INVOC(pInvocation));

        CopyMemory(pvData, pParserData->Value.pbuf, pInvocation->dwLength + 1);
    }

    return PPDERR_NONE;
}



PPDERROR
IParseInteger(
    PPARSERDATA pParserData,
    PDWORD      pdwValue
    )

/*++

Routine Description:

    Intrepret the value field of an entry as unsigned integer

Arguments:

    pParserData - Points to parser data structure
    pdwValue - Points to a variable for storing parsed integer value

Return Value:

    Status code

--*/

{
    PSTR    pstr = pParserData->pstrValue;
    LONG    lValue;

    if (BGetIntegerFromString(&pstr, &lValue))
    {
        if (lValue >= 0)
        {
            *pdwValue = lValue;
            return PPDERR_NONE;

        } else
            TERSE(("Negative integer value not allowed: %s.\n", pParserData->pstrValue));
    }

    *pdwValue = 0;
    return PPDERR_SYNTAX;
}



PPDERROR
IParseBoolean(
    PPARSERDATA pParserData,
    DWORD      *pdwValue
    )

/*++

Routine Description:

    Interpret the value of an entry as a boolen, i.e. True or False

Arguments:

    pParserData - Points to parser data structure
    pdwValue - Points to a variable for storing the parsed boolean value

Return Value:

    Status code

--*/

{
    static const STRTABLE BooleanStrs[] =
    {
        gstrTrueKwd,    TRUE,
        gstrFalseKwd,   FALSE,
        NULL,           FALSE
    };

    if (! BSearchStrTable(BooleanStrs, pParserData->pstrValue, pdwValue))
        return ISyntaxError(pParserData->pFile, "Invalid boolean constant");

    return PPDERR_NONE;
}



BOOL
BFindNextWord(
    PSTR   *ppstr,
    PSTR    pstrWord
    )

/*++

Routine Description:

    Find the next word in a character string. Words are separated by spaces.

Arguments:

    ppstr - Points to a string pointer. On entry, it contains a pointer
        to the beginning of the word string. On exit, it points to
        the first non-space character after the word string.
    pstrWord - Points to a buffer for storing characters from the next word
        The size of this buffer must be at least MAX_WORD_LEN characters

Return Value:

    TRUE if next word is found, FALSE otherwise

--*/

{
    PSTR    pstr = *ppstr;

    pstrWord[0] = NUL;

    //
    // Skip any leading spaces
    //

    while (IS_SPACE(*pstr))
        pstr++;

    if (*pstr != NUL)
    {
        PSTR    pstrStart;
        INT     iWordLen;

        //
        // Go to the end of the word
        //

        pstrStart = pstr;

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

        //
        // Copy the word into the specified buffer
        //

        if ((iWordLen = (INT)(pstr - pstrStart)) < MAX_WORD_LEN)
        {
            CopyMemory(pstrWord, pstrStart, iWordLen);
            pstrWord[iWordLen] = NUL;
        }

        //
        // Skip to the next non-space character
        //

        while (IS_SPACE(*pstr))
            pstr++;
    }

    *ppstr = pstr;
    return (*pstrWord != NUL);
}



PPDERROR
IParseVersionNumber(
    PPARSERDATA pParserData,
    PDWORD      pdwVersion
    )

/*++

Routine Description:

    Parse a version number. The format of a version number is Version[.Revision]
    where both Version and Revision are integers.

Arguments:

    pParserData - Points to parser data structure
    pdwVersion - Points to a variable for storing the parsed version number

Return Value:

    Status code

--*/

{
    PSTR        pstr;
    LONG        lVersion, lRevision = 0;

    //
    // Parse the major version number followed by minor revision number
    //

    pstr = pParserData->pstrValue;

    if (! BGetIntegerFromString(&pstr, &lVersion))
        return ISyntaxError(pParserData->pFile, "Invalid version number");

    if (*pstr == '.')
    {
        pstr++;

        if (! BGetIntegerFromString(&pstr, &lRevision))
            return ISyntaxError(pParserData->pFile, "Invalid revision number");
    }

    //
    // High-order word contains version number and
    // low-order word contains revision number
    //

    if (lVersion < 0  || lVersion > MAX_WORD ||
        lRevision < 0 || lRevision > MAX_WORD)
    {
        return ISyntaxError(pParserData->pFile, "Version number out-of-range");
    }

    *pdwVersion = (lVersion << 16) | lRevision;
    return PPDERR_NONE;
}



PPDERROR
IParseXlation(
    PPARSERDATA pParserData,
    PINVOCOBJ   pXlation
    )

/*++

Routine Description:

    Parse the information in the translation string field to an INVOCOBJ

Arguments:

    pParserData - Points to parser data structure
    pXlation - Returns information about parsed translation string

Return Value:

    Status code

--*/

{
    PBUFOBJ pBufObj = &pParserData->Xlation;

    //
    // Allocate memory to hold the translation string (plus a null terminator)
    //

    pXlation->pvData = ALLOC_PARSER_MEM(pParserData, pBufObj->dwSize + 1);

    if (pXlation->pvData == NULL)
    {
        ERR(("Memory allocation failed\n"));
        return PPDERR_MEMORY;
    }

    pXlation->dwLength = pBufObj->dwSize;
    ASSERT(! IS_SYMBOL_INVOC(pXlation));
    CopyMemory(pXlation->pvData, pBufObj->pbuf, pBufObj->dwSize);

    return PPDERR_NONE;
}



PCSTR
PstrStripKeywordChar(
    PCSTR   pstrKeyword
    )

/*++

Routine Description:

    Strip off the keyword prefix character from the input string

Arguments:

    pstrKeyword - Points to a string prefixed by the keyword character

Return Value:

    Pointer to the keyword string after the keyword character is stripped
    NULL if the keyword string is empty

--*/

{
    if (IS_KEYWORD_CHAR(*pstrKeyword))
        pstrKeyword++;

    return *pstrKeyword ? pstrKeyword : NULL;
}



PVOID
PvCreateListItem(
    PPARSERDATA pParserData,
    PLISTOBJ   *ppList,
    DWORD       dwItemSize,
    PSTR        pstrListTag
    )

/*++

Routine Description:

    Create a new item in the specified linked-list
    Make sure no existing item has the same name

Arguments:

    pParserData - Points to parser data structure
    ppList - Specifies the linked-list
    dwItemSize - Linked-list item size
    pListTag - Specifies the name of the linked-list (for debugging purpose)

Return Value:

    Pointer to newly created linked-list item
    NULL if there is an error

--*/

{
    PLISTOBJ    pItem;
    PSTR        pstrItemName;

    //
    // Check if the item appeared in the list already
    // Create a new item data structure if not
    //

    pItem = PvFindListItem(*ppList, pParserData->Option.pbuf, NULL);

    if (pItem != NULL)
    {
        if (pstrListTag)
            TERSE(("%s %s redefined\n", pstrListTag, pItem->pstrName));
    }
    else
    {
        if (! (pItem = ALLOC_PARSER_MEM(pParserData, dwItemSize)) ||
            ! (pstrItemName = PstrParseString(pParserData, &pParserData->Option)))
        {
            ERR(("Memory allocation failed: %d\n", GetLastError()));
            return NULL;
        }

        pItem->pstrName = pstrItemName;
        pItem->pNext = NULL;

        //
        // Put the newly created item at the end of the linked-list
        //

        while (*ppList != NULL)
            ppList = (PLISTOBJ *) &((*ppList)->pNext);

        *ppList = pItem;
    }

    return pItem;
}



PFEATUREOBJ
PCreateFeatureItem(
    PPARSERDATA pParserData,
    DWORD       dwFeatureID
    )

/*++

Routine Description:

    Create a new printer feature structure or find an existing one

Arguments:

    pParserData - Points to parser data structure
    dwFeatureID - Printer feature identifier

Return Value:

    Pointer to a newly created or an existing printer feature structure
    NULL if there is an error

--*/

{
    static struct {

        PCSTR   pstrKeyword;
        DWORD   dwFeatureID;
        DWORD   dwOptionSize;

    } FeatureInfo[] = {

        gstrPageSizeKwd,  GID_PAGESIZE,     sizeof(PAPEROBJ),
        "PageRegion",     GID_PAGEREGION,   sizeof(OPTIONOBJ),
        "Duplex",         GID_DUPLEX,       sizeof(OPTIONOBJ),
        gstrInputSlotKwd, GID_INPUTSLOT,    sizeof(TRAYOBJ),
        "Resolution",     GID_RESOLUTION,   sizeof(RESOBJ),
        "JCLResolution",  GID_RESOLUTION,   sizeof(RESOBJ),
        "OutputBin",      GID_OUTPUTBIN,    sizeof(BINOBJ),
        "MediaType",      GID_MEDIATYPE,    sizeof(OPTIONOBJ),
        "Collate",        GID_COLLATE,      sizeof(OPTIONOBJ),
        "InstalledMemory",GID_MEMOPTION,    sizeof(MEMOBJ),
        "LeadingEdge",    GID_LEADINGEDGE,  sizeof(OPTIONOBJ),
        "UseHWMargins",   GID_USEHWMARGINS, sizeof(OPTIONOBJ),
        NULL,             GID_UNKNOWN,      sizeof(OPTIONOBJ)
    };

    PFEATUREOBJ pFeature;
    PCSTR       pstrKeyword;
    BUFOBJ      SavedBuffer;
    INT         iIndex = 0;

    if (dwFeatureID == GID_UNKNOWN)
    {
        //
        // Given a feature name, first find out if it refers to
        // one of the predefined features
        //

        pstrKeyword = PstrStripKeywordChar(pParserData->achOption);
        ASSERT(pstrKeyword != NULL);

        while (FeatureInfo[iIndex].pstrKeyword &&
               strcmp(FeatureInfo[iIndex].pstrKeyword, pstrKeyword) != EQUAL_STRING)
        {
            iIndex++;
        }

        if (FeatureInfo[iIndex].pstrKeyword)
            pParserData->aubOpenUIFeature[FeatureInfo[iIndex].dwFeatureID] = 1;
    }
    else
    {
        //
        // We're given a predefined feature identifier.
        // Map to its corresponding feature name.
        //

        while (FeatureInfo[iIndex].pstrKeyword &&
               dwFeatureID != FeatureInfo[iIndex].dwFeatureID)
        {
            iIndex++;
        }

        pstrKeyword = FeatureInfo[iIndex].pstrKeyword;
        ASSERT(pstrKeyword != NULL);
    }

    //
    // If we're dealing with a predefined feature, the first search the current
    // list of printer features based on the predefined feature identifier.
    //

    pFeature = NULL;

    if (FeatureInfo[iIndex].dwFeatureID != GID_UNKNOWN)
    {
        for (pFeature = pParserData->pFeatures;
             pFeature && pFeature->dwFeatureID != FeatureInfo[iIndex].dwFeatureID;
             pFeature = pFeature->pNext)
        {
        }
    }

    //
    // Create a new printer feature item or find an existing printer feature item
    // based on the feature name.
    //

    if (pFeature == NULL)
    {
        SavedBuffer = pParserData->Option;
        pParserData->Option.pbuf = (PBYTE) pstrKeyword;
        pParserData->Option.dwSize = strlen(pstrKeyword);

        pFeature = PvCreateListItem(pParserData,
                                    (PLISTOBJ *) &pParserData->pFeatures,
                                    sizeof(FEATUREOBJ),
                                    NULL);

        pParserData->Option = SavedBuffer;
    }

    if (pFeature)
    {
        //
        // Parse the translation string for the feature name
        //

        if (dwFeatureID == GID_UNKNOWN &&
            ! IS_BUFFER_EMPTY(&pParserData->Xlation) &&
            ! pFeature->Translation.pvData &&
            IParseXlation(pParserData, &pFeature->Translation) != PPDERR_NONE)
        {
            ERR(("Failed to parse feature name translation string\n"));
            return NULL;
        }

        if (pFeature->dwOptionSize == 0)
        {
            //
            // Store information about newly created feature item
            //

            pFeature->dwOptionSize = FeatureInfo[iIndex].dwOptionSize;
            pFeature->dwFeatureID = FeatureInfo[iIndex].dwFeatureID;

            //
            // All predefined features are doc-sticky except for InstalledMemory/VMOption
            //

            if (pFeature->dwFeatureID == GID_MEMOPTION ||
                pFeature->dwFeatureID == GID_UNKNOWN && pParserData->bInstallableGroup)
            {
                pFeature->bInstallable = TRUE;
            }
        }
    }
    else
    {
        ERR(("Couldn't create printer feature item for: %s\n", pstrKeyword));
    }

    return pFeature;
}



PVOID
PvCreateXlatedItem(
    PPARSERDATA pParserData,
    PVOID       ppList,
    DWORD       dwItemSize
    )

/*++

Routine Description:

    Create a feature option item and parse the associated translation string

Arguments:

    pParserData - Points to parser data structure
    ppList - Points to the list of feature option items
    dwItemSize - Size of a feature option item

Return Value:

    Pointer to newly created feature option item
    NULL if there is an error

--*/

{
    POPTIONOBJ  pOption;

    if (! (pOption = PvCreateListItem(pParserData, ppList, dwItemSize, NULL)) ||
        (! IS_BUFFER_EMPTY(&pParserData->Xlation) &&
         ! pOption->Translation.pvData &&
         IParseXlation(pParserData, &pOption->Translation) != PPDERR_NONE))
    {
        ERR(("Couldn't process entry: *%s %s\n",
             pParserData->achKeyword,
             pParserData->achOption));

        return NULL;
    }

    return pOption;
}



PVOID
PvCreateOptionItem(
    PPARSERDATA pParserData,
    DWORD       dwFeatureID
    )

/*++

Routine Description:

    Create a feature option item for a predefined printer feature

Arguments:

    pParserData - Points to parser data structure
    dwFeatureID - Specifies a predefined feature identifier

Return Value:

    Pointer to an existing feature option item or a newly created one if none exists
    NULL if there is an error

--*/

{
    PFEATUREOBJ pFeature;

    ASSERT(dwFeatureID != GID_UNKNOWN);

    if (! (pFeature = PCreateFeatureItem(pParserData, dwFeatureID)))
        return NULL;

    return PvCreateXlatedItem(pParserData, &pFeature->pOptions, pFeature->dwOptionSize);
}



INT
ICountFeatureList(
    PFEATUREOBJ pFeature,
    BOOL        bInstallable
    )

{
    INT i = 0;

    //
    // Count the number of features of the specified type
    //

    while (pFeature != NULL)
    {
        if (pFeature->bInstallable == bInstallable)
            i++;

        pFeature = pFeature->pNext;
    }

    return i;
}



PPDERROR
IGenericOptionProc(
    PPARSERDATA pParserData,
    PFEATUREOBJ pFeature
    )

/*++

Routine Description:

    Function for handling a generic feature option entry

Arguments:

    pParserData - Points to parser data structure
    pFeature - Points to feature data structure

Return Value:

    Status code

--*/

{
    POPTIONOBJ  pOption;

    //
    // Handle special case
    //

    if (pFeature == NULL)
        return PPDERR_MEMORY;

    //
    // Create a feature option item and parse the option name and translation string
    //

    if (! (pOption = PvCreateXlatedItem(pParserData, &pFeature->pOptions, pFeature->dwOptionSize)))
        return PPDERR_MEMORY;

    if (pOption->Invocation.pvData)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    //
    // Parse the invocation string
    //

    return IParseInvocation(pParserData, &pOption->Invocation);
}



PPDERROR
IGenericDefaultProc(
    PPARSERDATA pParserData,
    PFEATUREOBJ pFeature
    )

/*++

Routine Description:

    Function for handling a generic default option entry

Arguments:

    pParserData - Points to parser data structure
    pFeature - Points to feature data structure

Return Value:

    Status code

--*/

{
    //
    // Check if there is a memory error before this function is called
    //

    if (pFeature == NULL)
        return PPDERR_MEMORY;

    //
    // Watch out for duplicate *Default entries for the same feature
    //

    if (pFeature->pstrDefault)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    //
    // NOTE: Hack to take in account of a bug in NT4 driver.
    // This is used to build up NT4-NT5 feature index mapping.
    //

    if (pFeature->dwFeatureID == GID_MEMOPTION &&
        pParserData->iDefInstallMemIndex < 0)
    {
        pParserData->iDefInstallMemIndex = ICountFeatureList(pParserData->pFeatures, TRUE);
    }

    //
    // Remember the default option keyword
    //

    if (pFeature->pstrDefault = PstrParseString(pParserData, &pParserData->Value))
        return PPDERR_NONE;
    else
        return PPDERR_MEMORY;
}



PPDERROR
IGenericQueryProc(
    PPARSERDATA pParserData,
    PFEATUREOBJ pFeature
    )

/*++

Routine Description:

    Function for handling a generic query invocation entry

Arguments:

    pParserData - Points to parser data structure
    pFeature - Points to feature data structure

Return Value:

    Status code

--*/

{
    //
    // Check if there is a memory error before this function is called
    //

    if (pFeature == NULL)
        return PPDERR_MEMORY;

    //
    // Watch out for duplicate *Default entries for the same feature
    //

    if (pFeature->QueryInvoc.pvData)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    //
    // Parse the query invocation string
    //

    return IParseInvocation(pParserData, &pFeature->QueryInvoc);
}



//
// Functions for handling predefined PPD keywords
//

//
// Specifies the imageable area of a media option
//

PPDERROR
IImageAreaProc(
    PPARSERDATA pParserData
    )

{
    PPAPEROBJ   pOption;
    PSTR        pstr;
    RECT        *pRect;

    if (! (pOption = PvCreateOptionItem(pParserData, GID_PAGESIZE)))
        return PPDERR_MEMORY;

    pRect = &pOption->rcImageArea;

    if (pRect->top > 0 || pRect->right > 0)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    //
    // Parse imageable area: left, bottom, right, top
    //

    pstr = pParserData->pstrValue;

    if (! BGetFloatFromString(&pstr, &pRect->left, FLTYPE_POINT_ROUNDUP) ||
        ! BGetFloatFromString(&pstr, &pRect->bottom, FLTYPE_POINT_ROUNDUP) ||
        ! BGetFloatFromString(&pstr, &pRect->right, FLTYPE_POINT_ROUNDDOWN) ||
        ! BGetFloatFromString(&pstr, &pRect->top, FLTYPE_POINT_ROUNDDOWN) ||
        pRect->left >= pRect->right || pRect->bottom >= pRect->top)
    {
        return ISyntaxError(pParserData->pFile, "Invalid imageable area");
    }

    return PPDERR_NONE;
}

//
// Specifies the paper dimension of a media option
//

PPDERROR
IPaperDimProc(
    PPARSERDATA pParserData
    )

{
    PPAPEROBJ   pOption;
    PSTR        pstr;

    if (! (pOption = PvCreateOptionItem(pParserData, GID_PAGESIZE)))
        return PPDERR_MEMORY;

    if (pOption->szDimension.cx > 0 || pOption->szDimension.cy > 0)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    //
    // Parse paper width and height
    //

    pstr = pParserData->pstrValue;

    if (! BGetFloatFromString(&pstr, &pOption->szDimension.cx, FLTYPE_POINT) ||
        ! BGetFloatFromString(&pstr, &pOption->szDimension.cy, FLTYPE_POINT))
    {
        return ISyntaxError(pParserData->pFile, "Invalid paper dimension");
    }

    return PPDERR_NONE;
}

//
// Interpret PageStackOrder and OutputOrder options
//

BOOL
BParseOutputOrder(
    PSTR    pstr,
    PBOOL   pbValue
    )

{
    static const STRTABLE OutputOrderStrs[] =
    {
        "Normal",   FALSE,
        "Reverse",  TRUE,
        NULL,       FALSE
    };

    DWORD   dwValue;
    BOOL    bResult;

    bResult = BSearchStrTable(OutputOrderStrs, pstr, &dwValue);

    *pbValue = dwValue;
    return bResult;
}

//
// Specifies the page stack order for an output bin
//

PPDERROR
IPageStackOrderProc(
    PPARSERDATA pParserData
    )

{
    PFEATUREOBJ pFeature;
    PBINOBJ     pOutputBin;

    //
    // Have we seen the OutputBin feature yet?
    //

    for (pFeature = pParserData->pFeatures;
         pFeature && pFeature->dwFeatureID != GID_OUTPUTBIN;
         pFeature = pFeature->pNext)
    {
    }

    //
    // If PageStackOrder entry appears before OutputBin feature, ignore it
    //

    if (pFeature == NULL)
    {
        BOOL bReverse;
        if (!BParseOutputOrder(pParserData->pstrValue, &bReverse))
            return ISyntaxError(pParserData->pFile, "Invalid PageStackOrder option");
        if (bReverse)
            TERSE(("%ws: Ignored *PageStackOrder: Reverse on line %d because OutputBin not yet defined\n",
                       pParserData->pFile->ptstrFileName,
                       pParserData->pFile->iLineNumber));

        return PPDERR_NONE;
    }

    //
    // Add an option for OutputBin feature
    //

    pOutputBin = PvCreateXlatedItem(pParserData, &pFeature->pOptions, pFeature->dwOptionSize);

    if (pOutputBin == NULL)
        return PPDERR_MEMORY;

    return BParseOutputOrder(pParserData->pstrValue, &pOutputBin->bReversePrint) ?
                PPDERR_NONE :
                ISyntaxError(pParserData->pFile, "Invalid PageStackOrder option");
}

//
// Specifies the default page output order
// NOTE: This function gets called only if *DefaultOutputOrder
// entry appears outside of OpenUI/CloseUI.
//

PPDERROR
IDefOutputOrderProc(
    PPARSERDATA pParserData
    )

{
    pParserData->bDefOutputOrderSet = BParseOutputOrder(pParserData->pstrValue, &pParserData->bDefReversePrint);

    return pParserData->bDefOutputOrderSet ?
                PPDERR_NONE :
                ISyntaxError(pParserData->pFile, "Invalid DefaultOutputOrder option");
}

//
// Specifies whether an input slot requires page region specification
//

PPDERROR
IReqPageRgnProc(
    PPARSERDATA pParserData
    )

{
    PTRAYOBJ    pOption;
    DWORD       dwValue;

    //
    // NOTE: Hack for doing NT4-NT5 feature index conversion
    //

    if (pParserData->iReqPageRgnIndex < 0)
    {
        PFEATUREOBJ pFeature = pParserData->pFeatures;

        while (pFeature && pFeature->dwFeatureID != GID_INPUTSLOT)
            pFeature = pFeature->pNext;

        if (pFeature == NULL)
            pParserData->iReqPageRgnIndex = ICountFeatureList(pParserData->pFeatures, FALSE);
    }

    //
    // The value should be either True or False
    //

    if (IParseBoolean(pParserData, &dwValue) != PPDERR_NONE)
        return PPDERR_SYNTAX;

    dwValue = dwValue ? REQRGN_TRUE : REQRGN_FALSE;

    //
    // *RequiresPageRegion All: entry has special meaning
    //

    if (strcmp(pParserData->achOption, "All") == EQUAL_STRING)
    {
        if (pParserData->dwReqPageRgn == REQRGN_UNKNOWN)
            pParserData->dwReqPageRgn = dwValue;
        else
            WARN_DUPLICATE();
    }
    else
    {
        if (! (pOption = PvCreateOptionItem(pParserData, GID_INPUTSLOT)))
            return PPDERR_MEMORY;

        if (pOption->dwReqPageRgn == REQRGN_UNKNOWN)
            pOption->dwReqPageRgn = dwValue;
        else
            WARN_DUPLICATE();
    }

    return PPDERR_NONE;
}

//
// Specifies Duplex feature options
//

PPDERROR
IDefaultDuplexProc(
    PPARSERDATA pParserData
    )

{
    return IGenericDefaultProc(pParserData,
                               PCreateFeatureItem(pParserData, GID_DUPLEX));
}

PPDERROR
IDuplexProc(
    PPARSERDATA pParserData
    )

{
    if (strcmp(pParserData->achOption, gstrNoneKwd) != EQUAL_STRING &&
        strcmp(pParserData->achOption, gstrDuplexTumble) != EQUAL_STRING &&
        strcmp(pParserData->achOption, gstrDuplexNoTumble) != EQUAL_STRING)
    {
        return ISyntaxError(pParserData->pFile, "Invalid Duplex option");
    }

    return IGenericOptionProc(pParserData,
                              PCreateFeatureItem(pParserData, GID_DUPLEX));
}

//
// Specifies ManualFeed True/False invocation strings
//

PPDERROR
IDefManualFeedProc(
    PPARSERDATA pParserData
    )

{
    //
    // NOTE: Hack for doing NT4-NT5 feature index conversion
    //

    if (pParserData->iManualFeedIndex < 0)
        pParserData->iManualFeedIndex = ICountFeatureList(pParserData->pFeatures, FALSE);

    return PPDERR_NONE;
}

PPDERROR
IManualFeedProc(
    PPARSERDATA pParserData
    )

{
    POPTIONOBJ  pOption;
    INT         iResult = PPDERR_NONE;

    //
    // NOTE: Hack for doing NT4-NT5 feature index conversion
    //

    if (pParserData->iManualFeedIndex < 0)
        pParserData->iManualFeedIndex = ICountFeatureList(pParserData->pFeatures, FALSE);

    if (strcmp(pParserData->achOption, gstrTrueKwd) == EQUAL_STRING ||
        strcmp(pParserData->achOption, gstrOnKwd) == EQUAL_STRING)
    {
        //
        // The way manual feed is handled in PPD spec is incredibly klugy.
        // Hack here to treat *ManualFeed True as one of the input slot
        // selections so that downstream component can handle it uniformly.
        //

        StringCchCopyA(pParserData->achOption, CCHOF(pParserData->achOption), gstrManualFeedKwd);

        pParserData->Option.dwSize = strlen(gstrManualFeedKwd);

        StringCchCopyA(pParserData->achXlation, CCHOF(pParserData->achXlation), "");

        pParserData->Xlation.dwSize = 0;

        if (! (pOption = PvCreateOptionItem(pParserData, GID_INPUTSLOT)))
        {
            iResult = PPDERR_MEMORY;
        }
        else if (pOption->Invocation.pvData)
        {
            TERSE(("%ws: Duplicate entries of '*ManualFeed True' on line %d\n",
                   pParserData->pFile->ptstrFileName,
                   pParserData->pFile->iLineNumber));
        }
        else
        {
            ((PTRAYOBJ) pOption)->dwTrayIndex = DMBIN_MANUAL;
            iResult = IParseInvocation(pParserData, &pOption->Invocation);
        }
    }
    else if (strcmp(pParserData->achOption, gstrFalseKwd) == EQUAL_STRING ||
             strcmp(pParserData->achOption, gstrNoneKwd) == EQUAL_STRING ||
             strcmp(pParserData->achOption, gstrOffKwd) == EQUAL_STRING)
    {
        //
        // Save *ManualFeed False invocation string separately.
        // It's always emitted before any tray invocation string.
        //

        if (pParserData->ManualFeedFalse.pvData)
        {
            WARN_DUPLICATE();
        }
        else
        {
            iResult = IParseInvocation(pParserData, &pParserData->ManualFeedFalse);
        }
    }
    else
    {
        iResult = ISyntaxError(pParserData->pFile, "Unrecognized ManualFeed option");
    }

    return iResult;
}

//
// Specifies JCLResolution invocation string
//

PPDERROR
IJCLResProc(
    PPARSERDATA pParserData
    )

{
    POPTIONOBJ  pOption;

    if (! (pOption = PvCreateOptionItem(pParserData, GID_RESOLUTION)))
        return PPDERR_MEMORY;

    if (pOption->Invocation.pvData)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    pParserData->dwSetResType = RESTYPE_JCL;
    return IParseInvocation(pParserData, &pOption->Invocation);
}

//
// Specifies the default JCLResolution option
//

PPDERROR
IDefaultJCLResProc(
    PPARSERDATA pParserData
    )

{
    return IGenericDefaultProc(pParserData,
                               PCreateFeatureItem(pParserData, GID_RESOLUTION));
}

//
// Specifies SetResolution invocation string
//

PPDERROR
ISetResProc(
    PPARSERDATA pParserData
    )

{
    POPTIONOBJ  pOption;

    if (! (pOption = PvCreateOptionItem(pParserData, GID_RESOLUTION)))
        return PPDERR_MEMORY;

    if (pOption->Invocation.pvData)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    pParserData->dwSetResType = RESTYPE_EXITSERVER;
    return IParseInvocation(pParserData, &pOption->Invocation);
}

//
// Specifies default halftone screen angle
//

PPDERROR
IScreenAngleProc(
    PPARSERDATA pParserData
    )

{
    PSTR    pstr = pParserData->pstrValue;

    if (! BGetFloatFromString(&pstr, &pParserData->fxScreenAngle, FLTYPE_FIX))
        return ISyntaxError(pParserData->pFile, "Invalid screen angle");

    return PPDERR_NONE;
}

//
// Specifies default halftone screen frequency
//

PPDERROR
IScreenFreqProc(
    PPARSERDATA pParserData
    )

{
    PSTR    pstr = pParserData->pstrValue;

    if (! BGetFloatFromString(&pstr, &pParserData->fxScreenFreq, FLTYPE_FIX) ||
        pParserData->fxScreenFreq <= 0)
    {
        return ISyntaxError(pParserData->pFile, "Invalid screen frequency");
    }
    else
        return PPDERR_NONE;
}

//
// Specifies default halftone screen angle for a resolution option
//

PPDERROR
IResScreenAngleProc(
    PPARSERDATA pParserData
    )

{
    PRESOBJ pOption;
    PSTR    pstr = pParserData->pstrValue;

    if (! (pOption = PvCreateOptionItem(pParserData, GID_RESOLUTION)))
        return PPDERR_MEMORY;

    if (! BGetFloatFromString(&pstr, &pOption->fxScreenAngle, FLTYPE_FIX))
        return ISyntaxError(pParserData->pFile, "Invalid screen angle");

    return PPDERR_NONE;
}

//
// Specifies default halftone screen frequency for a resolution option
//

PPDERROR
IResScreenFreqProc(
    PPARSERDATA pParserData
    )

{
    PRESOBJ pOption;
    PSTR    pstr = pParserData->pstrValue;

    if (! (pOption = PvCreateOptionItem(pParserData, GID_RESOLUTION)))
        return PPDERR_MEMORY;

    if (! BGetFloatFromString(&pstr, &pOption->fxScreenFreq, FLTYPE_FIX) ||
        pOption->fxScreenFreq <= 0)
    {
        return ISyntaxError(pParserData->pFile, "Invalid screen frequency");
    }
    else
        return PPDERR_NONE;
}

//
// Specifies device font information
//

PPDERROR
IFontProc(
    PPARSERDATA pParserData
    )

{
    static const STRTABLE FontStatusStrs[] =
    {
        "ROM",      FONTSTATUS_ROM,
        "Disk",     FONTSTATUS_DISK,
        NULL,       FONTSTATUS_UNKNOWN
    };

    PFONTREC    pFont;
    PSTR        pstr;
    CHAR        achWord[MAX_WORD_LEN];
    DWORD       cbSize;

    //
    // Create a new device font item
    //

    if (! (pFont = PvCreateXlatedItem(pParserData, &pParserData->pFonts, sizeof(FONTREC))))
        return PPDERR_MEMORY;

    if (pFont->pstrEncoding != NULL)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    //
    // encoding
    //

    pstr = pParserData->pstrValue;

    if (! BFindNextWord(&pstr, achWord))
        return ISyntaxError(pParserData->pFile, "Invalid *Font entry");

    cbSize = strlen(achWord) + 1;
    if (! (pFont->pstrEncoding = ALLOC_PARSER_MEM(pParserData, cbSize)))
        return PPDERR_MEMORY;

    StringCchCopyA(pFont->pstrEncoding, cbSize / sizeof(char), achWord);

    //
    // version
    //

    (VOID) BFindNextWord(&pstr, achWord);

    {
        PSTR    pstrStart, pstrEnd;

        if (pstrStart = strchr(achWord, '('))
            pstrStart++;
        else
            pstrStart = achWord;

        if (pstrEnd = strrchr(pstrStart, ')'))
            *pstrEnd = NUL;

        cbSize = strlen(pstrStart) + 1;
        if (! (pFont->pstrVersion = ALLOC_PARSER_MEM(pParserData, cbSize)))
            return PPDERR_MEMORY;

        StringCchCopyA(pFont->pstrVersion, cbSize / sizeof(char), pstrStart);
    }

    //
    // charset
    //

    (VOID) BFindNextWord(&pstr, achWord);

    cbSize = strlen(achWord) + 1;
    if (! (pFont->pstrCharset = ALLOC_PARSER_MEM(pParserData, cbSize)))
        return PPDERR_MEMORY;

    StringCchCopyA(pFont->pstrCharset, cbSize / sizeof(char), achWord);

    //
    // status
    //

    (VOID) BFindNextWord(&pstr, achWord);
    (VOID) BSearchStrTable(FontStatusStrs, achWord, &pFont->dwStatus);

    return PPDERR_NONE;
}

//
// Specifies the default device font
//

PPDERROR
IDefaultFontProc(
    PPARSERDATA pParserData
    )

{
    if (strcmp(pParserData->pstrValue, "Error") == EQUAL_STRING)
        pParserData->pstrDefaultFont = NULL;
    else if (! (pParserData->pstrDefaultFont = PstrParseString(pParserData, &pParserData->Value)))
        return PPDERR_MEMORY;

    return PPDERR_NONE;
}

//
// Mark the beginning of a new printer feature section
//

PPDERROR
IOpenUIProc(
    PPARSERDATA pParserData
    )

{
    static const STRTABLE UITypeStrs[] =
    {
        "PickOne",  UITYPE_PICKONE,
        "PickMany", UITYPE_PICKMANY,
        "Boolean",  UITYPE_BOOLEAN,
        NULL,       UITYPE_PICKONE
    };

    PCSTR   pstrKeyword;

    //
    // Guard against nested or unbalanced OpenUI
    //

    if (pParserData->pOpenFeature != NULL)
    {
        TERSE(("Missing CloseUI for *%s\n", pParserData->pOpenFeature->pstrName));
        pParserData->pOpenFeature = NULL;
    }

    //
    // Make sure the keyword is well-formed
    //

    if (! (pstrKeyword = PstrStripKeywordChar(pParserData->achOption)))
        return ISyntaxError(pParserData->pFile, "Empty keyword");

    //
    // HACK: special-case handling of "*OpenUI: *ManualFeed" entry
    //

    if (strcmp(pstrKeyword, gstrManualFeedKwd) == EQUAL_STRING)
        return PPDERR_NONE;

    if (! (pParserData->pOpenFeature = PCreateFeatureItem(pParserData, GID_UNKNOWN)))
        return PPDERR_MEMORY;

    //
    // Determine the type of feature option list
    //

    if (! BSearchStrTable(UITypeStrs,
                          pParserData->pstrValue,
                          &pParserData->pOpenFeature->dwUIType))
    {
        ISyntaxError(pParserData->pFile, "Unrecognized UI type");
    }

    //
    // Are we dealing with JCLOpenUI?
    //

    pParserData->bJclFeature = HAS_JCL_PREFIX(pstrKeyword);
    return PPDERR_NONE;
}

//
// Mark the end of a new printer feature section
//

PPDERROR
ICloseUIProc(
    PPARSERDATA pParserData
    )

{
    PCSTR       pstrKeyword;
    PFEATUREOBJ pOpenFeature;

    //
    // Make sure the CloseUI entry is balanced with a previous OpenUI entry
    //

    pOpenFeature = pParserData->pOpenFeature;
    pParserData->pOpenFeature = NULL;
    pstrKeyword = PstrStripKeywordChar(pParserData->pstrValue);

    //
    // HACK: special-case handling of "*CloseUI: *ManualFeed" entry
    //

    if (pstrKeyword && strcmp(pstrKeyword, gstrManualFeedKwd) == EQUAL_STRING)
        return PPDERR_NONE;

    if (pOpenFeature == NULL ||
        pstrKeyword == NULL ||
        strcmp(pstrKeyword, pOpenFeature->pstrName) != EQUAL_STRING ||
        pParserData->bJclFeature != HAS_JCL_PREFIX(pstrKeyword))
    {
        return ISyntaxError(pParserData->pFile, "Invalid CloseUI entry");
    }

    return PPDERR_NONE;
}

//
// Process OpenGroup and CloseGroup entries
//
// !!! OpenGroup, CloseGroup, OpenSubGroup, and CloseSubGroup
// keywords are not completely supported. Currently, we
// only pay specific attention to the InstallableOptions group.
//
// If the group information is needed in the future by the
// user interface, the following functions should be beefed up.
//

PPDERROR
IOpenCloseGroupProc(
    PPARSERDATA pParserData,
    BOOL        bOpenGroup
    )

{
    PSTR    pstrGroupName = pParserData->pstrValue;

    //
    // We're only interested in the InstallableOptions group
    //

    if (strcmp(pstrGroupName, "InstallableOptions") == EQUAL_STRING)
    {
        if (pParserData->bInstallableGroup == bOpenGroup)
            return ISyntaxError(pParserData->pFile, "Unbalanced OpenGroup/CloseGroup");

        pParserData->bInstallableGroup = bOpenGroup;
    }
    else
    {
        VERBOSE(("Group %s ignored\n", pstrGroupName));
    }

    return PPDERR_NONE;
}

//
// Process OpenGroup entries
//

PPDERROR
IOpenGroupProc(
    PPARSERDATA pParserData
    )

{
    return IOpenCloseGroupProc(pParserData, TRUE);
}

//
// Process CloseGroup entries
//

PPDERROR
ICloseGroupProc(
    PPARSERDATA pParserData
    )

{
    return IOpenCloseGroupProc(pParserData, FALSE);
}

//
// Handle OpenSubGroup entries
//

PPDERROR
IOpenSubGroupProc(
    PPARSERDATA pParserData
    )

{
    return PPDERR_NONE;
}

//
// Handle CloseSubGroup entries
//

PPDERROR
ICloseSubGroupProc(
    PPARSERDATA pParserData
    )

{
    return PPDERR_NONE;
}

//
// Parse a UIConstraints entry
//

PPDERROR
IUIConstraintsProc(
    PPARSERDATA pParserData
    )

{
    PLISTOBJ    pItem;

    if (! (pItem = ALLOC_PARSER_MEM(pParserData, sizeof(LISTOBJ))) ||
        ! (pItem->pstrName = PstrParseString(pParserData, &pParserData->Value)))
    {
        ERR(("Memory allocation failed\n"));
        return PPDERR_MEMORY;
    }

    pItem->pNext = pParserData->pUIConstraints;
    pParserData->pUIConstraints = pItem;
    return PPDERR_NONE;
}

//
// Parse an OrderDependency entry
//

PPDERROR
IOrderDepProc(
    PPARSERDATA pParserData
    )

{
    PLISTOBJ    pItem;

    if (! (pItem = ALLOC_PARSER_MEM(pParserData, sizeof(LISTOBJ))) ||
        ! (pItem->pstrName = PstrParseString(pParserData, &pParserData->Value)))
    {
        ERR(("Memory allocation failed\n"));
        return PPDERR_MEMORY;
    }

    pItem->pNext = pParserData->pOrderDep;
    pParserData->pOrderDep = pItem;
    return PPDERR_NONE;
}

//
// Parse QueryOrderDependency entries
//

PPDERROR
IQueryOrderDepProc(
    PPARSERDATA pParserData
    )

{
    PLISTOBJ    pItem;

    if (! (pItem = ALLOC_PARSER_MEM(pParserData, sizeof(LISTOBJ))) ||
        ! (pItem->pstrName = PstrParseString(pParserData, &pParserData->Value)))
    {
        ERR(("Memory allocation failed\n"));
        return PPDERR_MEMORY;
    }

    pItem->pNext = pParserData->pQueryOrderDep;
    pParserData->pQueryOrderDep = pItem;
    return PPDERR_NONE;
}

//
// Specifies memory configuration information
//

PPDERROR
IVMOptionProc(
    PPARSERDATA pParserData
    )

{
    PMEMOBJ pOption;

    if (! (pOption = PvCreateOptionItem(pParserData, GID_MEMOPTION)))
        return PPDERR_MEMORY;

    if (pOption->dwFreeVM)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    return IParseInteger(pParserData, &pOption->dwFreeVM);
}

//
// Specifies font cache size information
//

PPDERROR
IFCacheSizeProc(
    PPARSERDATA pParserData
    )

{
    PMEMOBJ pOption;

    if (! (pOption = PvCreateOptionItem(pParserData, GID_MEMOPTION)))
        return PPDERR_MEMORY;

    if (pOption->dwFontMem)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    return IParseInteger(pParserData, &pOption->dwFontMem);
}

//
// Specifies the minimum amount of free VM
//

PPDERROR
IFreeVMProc(
    PPARSERDATA pParserData
    )

{
    return IParseInteger(pParserData, &pParserData->dwFreeMem);
}

//
// Include another file
//

PPDERROR
IIncludeProc(
    PPARSERDATA pParserData
    )

#define MAX_INCLUDE_LEVEL   10

{
    WCHAR       awchFilename[MAX_PATH];
    PFILEOBJ    pPreviousFile;
    PPDERROR    iStatus;

    if (pParserData->iIncludeLevel >= MAX_INCLUDE_LEVEL)
    {
        ERR(("There appears to be recursive *Include.\n"));
        return PPDERR_FILE;
    }

    if (! MultiByteToWideChar(CP_ACP, 0, pParserData->pstrValue, -1, awchFilename,  MAX_PATH))
        return ISyntaxError(pParserData->pFile, "Invalid include filename");

    VERBOSE(("Including file %ws ...\n", awchFilename));

    pPreviousFile = pParserData->pFile;
    pParserData->iIncludeLevel++;

    iStatus = IParseFile(pParserData, awchFilename);

    pParserData->iIncludeLevel--;
    pParserData->pFile = pPreviousFile;

    return iStatus;
}

//
// Specifies the printer description file format version number
//

PPDERROR
IPPDAdobeProc(
    PPARSERDATA pParserData
    )

{
    return IParseVersionNumber(pParserData, &pParserData->dwSpecVersion);
}

//
// Specifies the printer description file format version number
//

PPDERROR
IFormatVersionProc(
    PPARSERDATA pParserData
    )

{
    if (pParserData->dwSpecVersion != 0)
        return PPDERR_NONE;

    return IParseVersionNumber(pParserData, &pParserData->dwSpecVersion);
}

//
// Specifies the PPD file version number
//

PPDERROR
IFileVersionProc(
    PPARSERDATA pParserData
    )

{
    return IParseVersionNumber(pParserData, &pParserData->dwPpdFilever);
}

//
// Specifies the protocols supported by the device
//

PPDERROR
IProtocolsProc(
    PPARSERDATA pParserData
    )

{
    static const STRTABLE ProtocolStrs[] =
    {
        "PJL",  PROTOCOL_PJL,
        "BCP",  PROTOCOL_BCP,
        "TBCP", PROTOCOL_TBCP,
        "SIC",  PROTOCOL_SIC,
        NULL,   0
    };

    CHAR    achWord[MAX_WORD_LEN];
    DWORD   dwProtocol;
    PSTR    pstr = pParserData->pstrValue;

    while (BFindNextWord(&pstr, achWord))
    {
        if (BSearchStrTable(ProtocolStrs, achWord, &dwProtocol))
            pParserData->dwProtocols |= dwProtocol;
        else
            TERSE(("Unknown protocol: %s\n", achWord));
    }

    return PPDERR_NONE;
}

//
// Specifies whether the device supports color output
//

PPDERROR
IColorDeviceProc(
    PPARSERDATA pParserData
    )

{
    return IParseBoolean(pParserData, &pParserData->dwColorDevice);
}

//
// Specifies whether the device fonts already have the Euro
//

PPDERROR
IHasEuroProc(
    PPARSERDATA pParserData
    )

{
    PPDERROR rc;

    if (rc = IParseBoolean(pParserData, &pParserData->bHasEuro) != PPDERR_NONE)
        return rc;

    pParserData->bEuroInformationSet = TRUE;

    return PPDERR_NONE;
}

//
// Specifies whether the device fonts already have the Euro
//

PPDERROR
ITrueGrayProc(
    PPARSERDATA pParserData
    )

{
    return IParseBoolean(pParserData, &pParserData->bTrueGray);
}

//
// Specifies the language extensions supported by the device
//

PPDERROR
IExtensionsProc(
    PPARSERDATA pParserData
    )

{
    static const STRTABLE ExtensionStrs[] =
    {
        "DPS",          LANGEXT_DPS,
        "CMYK",         LANGEXT_CMYK,
        "Composite",    LANGEXT_COMPOSITE,
        "FileSystem",   LANGEXT_FILESYSTEM,
        NULL,           0
    };

    CHAR    achWord[MAX_WORD_LEN];
    INT     dwExtension;
    PSTR    pstr = pParserData->pstrValue;

    while (BFindNextWord(&pstr, achWord))
    {
        if (BSearchStrTable(ExtensionStrs, achWord, &dwExtension))
            pParserData->dwExtensions |= dwExtension;
        else
            TERSE(("Unknown extension: %s\n", achWord));
    }

    return PPDERR_NONE;
}

//
// Specifies whether the device has a file system on disk
//

PPDERROR
IFileSystemProc(
    PPARSERDATA pParserData
    )

{
    DWORD       dwFileSystem;
    PPDERROR    iStatus;

    if ((iStatus = IParseBoolean(pParserData, &dwFileSystem)) == PPDERR_NONE)
    {
        if (dwFileSystem)
            pParserData->dwExtensions |= LANGEXT_FILESYSTEM;
        else
            pParserData->dwExtensions &= ~LANGEXT_FILESYSTEM;
    }

    return iStatus;
}

//
// Specifies the device name
//

PPDERROR
INickNameProc(
    PPARSERDATA pParserData
    )

{
    //
    // Use NickName only if ShortNickName entry is not present
    //

    if (pParserData->NickName.pvData == NULL)
        return IParseInvocation(pParserData, &pParserData->NickName);
    else
        return PPDERR_NONE;
}

//
// Specifies the short device name
//

PPDERROR
IShortNameProc(
    PPARSERDATA pParserData
    )

{
    pParserData->NickName.dwLength = 0;
    pParserData->NickName.pvData = NULL;

    return IParseInvocation(pParserData, &pParserData->NickName);
}

//
// Specifies the PostScript language level
//

PPDERROR
ILangLevelProc(
    PPARSERDATA pParserData
    )

{
    return IParseInteger(pParserData, &pParserData->dwLangLevel);
}

//
// Specifies PPD language encoding options
//

PPDERROR
ILangEncProc(
    PPARSERDATA pParserData
    )

{
    //
    // NOTE: Only the following two language encodings are supported because
    // the rest of them are not used (according to our discussions with Adobe).
    // In any case, we don't have any direct way of translating ANSI strings
    // in other encodings to Unicode.
    //
    // A possible future PPD extension is to allow Unicode encoding directly
    // in translation strings.
    //

    static const STRTABLE LangEncStrs[] =
    {
        "ISOLatin1",    LANGENC_ISOLATIN1,
        "WindowsANSI",  LANGENC_ISOLATIN1, // WindowsANSI means CharSet=0, which is now page 1252->ISO Latin1
        "None",         LANGENC_NONE,
        "Unicode",      LANGENC_UNICODE,
        "JIS83-RKSJ",   LANGENC_JIS83_RKSJ,
        NULL,           LANGENC_NONE
    };

    if (! BSearchStrTable(LangEncStrs, pParserData->pstrValue, &pParserData->dwLangEncoding))
        return ISyntaxError(pParserData->pFile, "Unsupported LanguageEncoding keyword");
    else
        return PPDERR_NONE;
}

//
// Identifies the natural language used in the PPD file
//

PPDERROR
ILangVersProc(
    PPARSERDATA pParserData
    )

{
    static const STRTABLE LangVersionStrs[] = {

        "English",        LANGENC_ISOLATIN1,
        "Danish",         LANGENC_ISOLATIN1,
        "Dutch",          LANGENC_ISOLATIN1,
        "Finnish",        LANGENC_ISOLATIN1,
        "French",         LANGENC_ISOLATIN1,
        "German",         LANGENC_ISOLATIN1,
        "Italian",        LANGENC_ISOLATIN1,
        "Norwegian",      LANGENC_ISOLATIN1,
        "Portuguese",     LANGENC_ISOLATIN1,
        "Spanish",        LANGENC_ISOLATIN1,
        "Swedish",        LANGENC_ISOLATIN1,
        "Japanese",       LANGENC_JIS83_RKSJ,
        "Chinese",        LANGENC_NONE,
        "Russian",        LANGENC_NONE,

        NULL,             LANGENC_NONE
    };

    if (pParserData->dwLangEncoding == LANGENC_NONE &&
        ! BSearchStrTable(LangVersionStrs, pParserData->pstrValue, &pParserData->dwLangEncoding))
    {
        return ISyntaxError(pParserData->pFile, "Unsupported LanguageVersion keyword");
    }

    return PPDERR_NONE;
}

//
// Specifies the available TrueType rasterizer options
//

PPDERROR
ITTRasterizerProc(
    PPARSERDATA pParserData
    )

{
    static const STRTABLE RasterizerStrs[] =
    {
        "None",         TTRAS_NONE,
        "Accept68K",    TTRAS_ACCEPT68K,
        "Type42",       TTRAS_TYPE42,
        "TrueImage",    TTRAS_TRUEIMAGE,
        NULL,           TTRAS_NONE
    };

    if (! BSearchStrTable(RasterizerStrs, pParserData->pstrValue, &pParserData->dwTTRasterizer))
        return ISyntaxError(pParserData->pFile, "Unknown TTRasterizer option");
    else
        return PPDERR_NONE;
}

//
// Specifies the exitserver invocation string
//

PPDERROR
IExitServerProc(
    PPARSERDATA pParserData
    )

{
    return IParseInvocation(pParserData, &pParserData->ExitServer);
}

//
// Specifies the password string
//

PPDERROR
IPasswordProc(
    PPARSERDATA pParserData
    )

{
    return IParseInvocation(pParserData, &pParserData->Password);
}

//
// Specifies the PatchFile invocation string
//

PPDERROR
IPatchFileProc(
    PPARSERDATA pParserData
    )

{
    return IParseInvocation(pParserData, &pParserData->PatchFile);
}

//
// Specifies JobPatchFile invocation strings
//

PPDERROR
IJobPatchFileProc(
    PPARSERDATA pParserData
    )

{
    PJOBPATCHFILEOBJ  pItem;
    PSTR              pTmp;

    //
    // Create a new job patch file item
    //

    if (! (pItem = PvCreateListItem(pParserData,
                                    (PLISTOBJ *) &pParserData->pJobPatchFiles,
                                    sizeof(JOBPATCHFILEOBJ),
                                    "JobPatchFile")))
    {
        return PPDERR_MEMORY;
    }

    //
    // Parse the job patch file invocation string
    //

    if (pItem->Invocation.pvData)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    //
    // warn if number of patch file is invalid
    //

    pTmp = pItem->pstrName;

    if (!BGetIntegerFromString(&pTmp, &pItem->lPatchNo))
    {
        TERSE(("Warning: invalid JobPatchFile number '%s' on line %d\n",
               pParserData->achOption, pParserData->pFile->iLineNumber));
        pItem->lPatchNo = 0;
    }

    return IParseInvocation(pParserData, &pItem->Invocation);
}

//
// Specifies PostScript interpreter version and revision number
//

PPDERROR
IPSVersionProc(
    PPARSERDATA pParserData
    )

{
    PSTR        pstr = pParserData->Value.pbuf;
    DWORD       dwVersion;
    PPDERROR    status;

    //
    // Save the first PSVersion string
    //

    if ((pParserData->PSVersion.pvData == NULL) &&
        ((status = IParseInvocation(pParserData, &pParserData->PSVersion)) != PPDERR_NONE))
    {
        return status;
    }

    //
    // Skip non-digit characters
    //

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

    //
    // Extract the PS interpreter version number
    //

    dwVersion = 0;

    while (*pstr && IS_DIGIT(*pstr))
        dwVersion = dwVersion * 10 + (*pstr++ - '0');

    if (dwVersion > 0)
    {
        //
        // Remember the lowest PSVersion number
        //

        if (pParserData->dwPSVersion == 0 || pParserData->dwPSVersion > dwVersion)
            pParserData->dwPSVersion = dwVersion;

        return PPDERR_NONE;
    }
    else
        return ISyntaxError(pParserData->pFile, "Invalid PSVersion entry");
}

//
// Specifies the Product string
//

PPDERROR
IProductProc(
    PPARSERDATA pParserData
    )

{
        //
        // only store the first *Product entry, though there may be multiple
        //

        if (pParserData->Product.dwLength != 0)
                return PPDERR_NONE;

    return IParseInvocation(pParserData, &pParserData->Product);
}

//
// Specifies the default job timeout value
//

PPDERROR
IJobTimeoutProc(
    PPARSERDATA pParserData
    )

{
    return IParseInteger(pParserData, &pParserData->dwJobTimeout);
}

//
// Specifies the default wait timeout value
//

PPDERROR
IWaitTimeoutProc(
    PPARSERDATA pParserData
    )

{
    return IParseInteger(pParserData, &pParserData->dwWaitTimeout);
}

//
// Specifies whether error handler should be enabled by default
//

PPDERROR
IPrintPSErrProc(
    PPARSERDATA pParserData
    )

{
    DWORD   dwValue;

    if (IParseBoolean(pParserData, &dwValue) != PPDERR_NONE)
        return PPDERR_SYNTAX;

    if (dwValue)
        pParserData->dwPpdFlags |= PPDFLAG_PRINTPSERROR;
    else
        pParserData->dwPpdFlags &= ~PPDFLAG_PRINTPSERROR;

    return PPDERR_NONE;
}

//
// Specifies PJL commands to start a job
//

PPDERROR
IJCLBeginProc(
    PPARSERDATA pParserData
    )

{
    pParserData->dwPpdFlags |= PPDFLAG_HAS_JCLBEGIN;
    return IParseInvocation(pParserData, &pParserData->JclBegin);
}

//
// Specifies PJL commands to switch into PostScript language
//

PPDERROR
IJCLToPSProc(
    PPARSERDATA pParserData
    )

{
    pParserData->dwPpdFlags |= PPDFLAG_HAS_JCLENTERPS;
    return IParseInvocation(pParserData, &pParserData->JclEnterPS);
}

//
// Specifies PJL commands to end a job
//

PPDERROR
IJCLEndProc(
    PPARSERDATA pParserData
    )

{
    pParserData->dwPpdFlags |= PPDFLAG_HAS_JCLEND;
    return IParseInvocation(pParserData, &pParserData->JclEnd);
}

//
// Specifies the default landscape orientation mode
//

PPDERROR
ILSOrientProc(
    PPARSERDATA pParserData
    )

{
    static const STRTABLE LsoStrs[] =
    {
        "Any",      LSO_ANY,
        "Plus90",   LSO_PLUS90,
        "Minus90",  LSO_MINUS90,
        NULL,       LSO_ANY
    };

    if (! BSearchStrTable(LsoStrs, pParserData->pstrValue, &pParserData->dwLSOrientation))
        return ISyntaxError(pParserData->pFile, "Unrecognized landscape orientation");
    else
        return PPDERR_NONE;
}



PPAPEROBJ
PCreateCustomSizeOption(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Create a CustomPageSize option for PageSize feature (if necessary)

Arguments:

    pParserData - Points to parser data structure

Return Value:

    Pointer to newly created CustomPageSize option item or
    the existing CustomPageSize option item if it already exists.

    NULL if there is an error.

--*/

{
    PPAPEROBJ   pCustomSize;
    BUFOBJ      SavedBuffer;

    //
    // Create an item corresponding to *PageSize feature if needed
    //

    SavedBuffer = pParserData->Option;
    pParserData->Option.pbuf = (PBYTE) gstrCustomSizeKwd;
    pParserData->Option.dwSize = strlen(gstrCustomSizeKwd);

    pCustomSize = PvCreateOptionItem(pParserData, GID_PAGESIZE);

    pParserData->Option = SavedBuffer;

    return pCustomSize;;
}

//
// Specifies custom paper size invocation string
//

PPDERROR
ICustomSizeProc(
    PPARSERDATA pParserData
    )

{
    PPAPEROBJ   pCustomSize;

    if (strcmp(pParserData->achOption, gstrTrueKwd) != EQUAL_STRING)
    {
        ISyntaxError(pParserData->pFile, "Invalid *CustomPageSize option");
        return PPDERR_NONE;
    }

    if (! (pCustomSize = PCreateCustomSizeOption(pParserData)))
        return PPDERR_MEMORY;

    if (pCustomSize->Option.Invocation.pvData)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    return IParseInvocation(pParserData, &pCustomSize->Option.Invocation);
}

//
// Specifies custom paper size parameters
//

PPDERROR
IParamCustomProc(
    PPARSERDATA pParserData
    )

{
    static const STRTABLE CustomParamStrs[] =
    {
        "Width",        CUSTOMPARAM_WIDTH,
        "Height",       CUSTOMPARAM_HEIGHT,
        "WidthOffset",  CUSTOMPARAM_WIDTHOFFSET,
        "HeightOffset", CUSTOMPARAM_HEIGHTOFFSET,
        "Orientation",  CUSTOMPARAM_ORIENTATION,
        NULL,           0
    };

    CHAR    achWord[MAX_WORD_LEN];
    LONG    lMinVal, lMaxVal;
    INT     iType;
    DWORD   dwParam;
    LONG    lOrder;
    PSTR    pstr = pParserData->pstrValue;

    //
    // The format for a ParamCustomPageSize entry:
    //  ParameterName Order Type MinVal MaxVal
    //

    if (! BSearchStrTable(CustomParamStrs, pParserData->achOption, &dwParam) ||
        ! BGetIntegerFromString(&pstr, &lOrder) ||
        ! BFindNextWord(&pstr, achWord) ||
        lOrder <= 0 || lOrder > CUSTOMPARAM_MAX)
    {
        return ISyntaxError(pParserData->pFile, "Bad *ParamCustomPageSize entry");
    }

    //
    // Expected type is "int" for Orientation parameter and "points" for other parameters
    //

    iType = (dwParam == CUSTOMPARAM_ORIENTATION) ?
                ((strcmp(achWord, "int") == EQUAL_STRING) ? FLTYPE_INT : FLTYPE_ERROR) :
                ((strcmp(achWord, "points") == EQUAL_STRING) ? FLTYPE_POINT : FLTYPE_ERROR);

    if (iType == FLTYPE_ERROR ||
        ! BGetFloatFromString(&pstr, &lMinVal, iType) ||
        ! BGetFloatFromString(&pstr, &lMaxVal, iType) ||
        lMinVal > lMaxVal)
    {
        return ISyntaxError(pParserData->pFile, "Bad *ParamCustomPageSize entry");
    }

    pParserData->CustomSizeParams[dwParam].dwOrder = lOrder;
    pParserData->CustomSizeParams[dwParam].lMinVal = lMinVal;
    pParserData->CustomSizeParams[dwParam].lMaxVal = lMaxVal;

    return PPDERR_NONE;
}

//
// Specifies the maximum height of custom paper size
//

PPDERROR
IMaxWidthProc(
    PPARSERDATA pParserData
    )

{
    PPAPEROBJ   pCustomSize;
    LONG        lValue;
    PSTR        pstr = pParserData->pstrValue;

    if (! BGetFloatFromString(&pstr, &lValue, FLTYPE_POINT) || lValue <= 0)
        return ISyntaxError(pParserData->pFile, "Invalid media width");

    if (! (pCustomSize = PCreateCustomSizeOption(pParserData)))
        return PPDERR_MEMORY;

    pCustomSize->szDimension.cx = lValue;
    return PPDERR_NONE;
}

//
// Specifies the maximum height of custom paper size
//

PPDERROR
IMaxHeightProc(
    PPARSERDATA pParserData
    )

{
    PPAPEROBJ   pCustomSize;
    LONG        lValue;
    PSTR        pstr = pParserData->pstrValue;

    if (! BGetFloatFromString(&pstr, &lValue, FLTYPE_POINT) || lValue <= 0)
        return ISyntaxError(pParserData->pFile, "Invalid media height");

    if (! (pCustomSize = PCreateCustomSizeOption(pParserData)))
        return PPDERR_MEMORY;

    pCustomSize->szDimension.cy = lValue;
    return PPDERR_NONE;
}

//
// Specifies the hardware margins on cut-sheet devices
//

PPDERROR
IHWMarginsProc(
    PPARSERDATA pParserData
    )

{
    PPAPEROBJ   pCustomSize;
    RECT        rc;
    PSTR        pstr = pParserData->pstrValue;

    //
    // Parse hardware margins: left, bottom, right, top
    //

    if (! BGetFloatFromString(&pstr, &rc.left, FLTYPE_POINT) ||
        ! BGetFloatFromString(&pstr, &rc.bottom, FLTYPE_POINT) ||
        ! BGetFloatFromString(&pstr, &rc.right, FLTYPE_POINT) ||
        ! BGetFloatFromString(&pstr, &rc.top, FLTYPE_POINT))
    {
        return ISyntaxError(pParserData->pFile, "Invalid HWMargins");
    }

    if (! (pCustomSize = PCreateCustomSizeOption(pParserData)))
        return PPDERR_MEMORY;

    pCustomSize->rcImageArea = rc;

    //
    // The presence of HWMargins entry indicates the device supports cut-sheet
    //

    pParserData->dwCustomSizeFlags |= CUSTOMSIZE_CUTSHEET;
    return PPDERR_NONE;
}

//
// Function to process *CenterRegistered entry
//

PPDERROR
ICenterRegProc(
    PPARSERDATA pParserData
    )

{
    DWORD   dwValue;

    if (IParseBoolean(pParserData, &dwValue) != PPDERR_NONE)
        return PPDERR_SYNTAX;

    if (dwValue)
        pParserData->dwCustomSizeFlags |= CUSTOMSIZE_CENTERREG;
    else
        pParserData->dwCustomSizeFlags &= ~CUSTOMSIZE_CENTERREG;

    return PPDERR_NONE;
}

//
// Function to process *ADORequiresEExec entry
//

PPDERROR
IReqEExecProc(
    PPARSERDATA pParserData
    )

{
    DWORD   dwValue;

    if (IParseBoolean(pParserData, &dwValue) != PPDERR_NONE)
        return PPDERR_SYNTAX;

    if (dwValue)
        pParserData->dwPpdFlags |= PPDFLAG_REQEEXEC;
    else
        pParserData->dwPpdFlags &= ~PPDFLAG_REQEEXEC;

    return PPDERR_NONE;
}

//
// Function to process *ADOTTFontSub entry
//

PPDERROR
ITTFontSubProc(
    PPARSERDATA pParserData
    )

{
    PTTFONTSUB pTTFontSub;

    //
    // Create a new font substitution item
    //

    if (! (pTTFontSub = PvCreateXlatedItem(
                                pParserData,
                                &pParserData->pTTFontSubs,
                                sizeof(TTFONTSUB))))
    {
        return PPDERR_MEMORY;
    }

    //
    // Parse the PS family name
    //

    if (pTTFontSub->PSName.pvData)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    if (*pParserData->pstrValue == NUL)
        return ISyntaxError(pParserData->pFile, "Missing TrueType font family name");

    return IParseInvocation(pParserData, &pTTFontSub->PSName);
}

//
// Function to process *Throughput entry
//

PPDERROR
IThroughputProc(
    PPARSERDATA pParserData
    )

{
    return IParseInteger(pParserData, &pParserData->dwThroughput);
}

//
// Function to ignore the current entry
//

PPDERROR
INullProc(
    PPARSERDATA pParserData
    )

{
    return PPDERR_NONE;
}

//
// Define a named symbol
//

PPDERROR
ISymbolValueProc(
    PPARSERDATA pParserData
    )

{
    PSYMBOLOBJ  pSymbol;

    if (pParserData->dwValueType == VALUETYPE_SYMBOL)
        return ISyntaxError(pParserData->pFile, "Symbol value cannot be another symbol");

    //
    // Create a new symbol item
    //

    if (! (pSymbol = PvCreateListItem(pParserData,
                                      (PLISTOBJ *) &pParserData->pSymbols,
                                      sizeof(SYMBOLOBJ),
                                      "Symbol")))
    {
        return PPDERR_MEMORY;
    }

    //
    // Parse the symbol value
    //

    if (pSymbol->Invocation.pvData)
    {
        WARN_DUPLICATE();
        return PPDERR_NONE;
    }

    return IParseInvocation(pParserData, &pSymbol->Invocation);
}



//
// Built-in keyword table
//

const CHAR gstrDefault[]        = "Default";
const CHAR gstrPageSizeKwd[]    = "PageSize";
const CHAR gstrInputSlotKwd[]   = "InputSlot";
const CHAR gstrManualFeedKwd[]  = "ManualFeed";
const CHAR gstrCustomSizeKwd[]  = "CustomPageSize";
const CHAR gstrLetterSizeKwd[]  = "Letter";
const CHAR gstrA4SizeKwd[]      = "A4";
const CHAR gstrLongKwd[]        = "Long";
const CHAR gstrShortKwd[]       = "Short";
const CHAR gstrTrueKwd[]        = "True";
const CHAR gstrFalseKwd[]       = "False";
const CHAR gstrOnKwd[]          = "On";
const CHAR gstrOffKwd[]         = "Off";
const CHAR gstrNoneKwd[]        = "None";
const CHAR gstrVMOptionKwd[]    = "VMOption";
const CHAR gstrInstallMemKwd[]  = "InstalledMemory";
const CHAR gstrDuplexTumble[]   = "DuplexTumble";
const CHAR gstrDuplexNoTumble[] = "DuplexNoTumble";

const KWDENTRY gPpdBuiltInKeywordTable[] =
{
    { gstrPageSizeKwd,          NULL,                GENERIC_ENTRY(GID_PAGESIZE) },
    { "PageRegion",             NULL,                GENERIC_ENTRY(GID_PAGEREGION) },
    { gstrInputSlotKwd,         NULL,                GENERIC_ENTRY(GID_INPUTSLOT) },
    { "MediaType",              NULL,                GENERIC_ENTRY(GID_MEDIATYPE) },
    { "OutputBin",              NULL,                GENERIC_ENTRY(GID_OUTPUTBIN) },
    { "Collate",                NULL,                GENERIC_ENTRY(GID_COLLATE) },
    { "Resolution",             NULL,                GENERIC_ENTRY(GID_RESOLUTION) },
    { "InstalledMemory",        NULL,                GENERIC_ENTRY(GID_MEMOPTION) },
    { "LeadingEdge",            NULL,                GENERIC_ENTRY(GID_LEADINGEDGE) },
    { "UseHWMargins",           NULL,                GENERIC_ENTRY(GID_USEHWMARGINS) },

    { "Duplex",                 IDuplexProc,         INVOCA_VALUE | REQ_OPTION },
    { "DefaultDuplex",          IDefaultDuplexProc,  STRING_VALUE },
    { "PaperDimension",         IPaperDimProc,       QUOTED_NOHEX | REQ_OPTION },
    { "ImageableArea",          IImageAreaProc,      QUOTED_NOHEX | REQ_OPTION },
    { "RequiresPageRegion",     IReqPageRgnProc,     STRING_VALUE | REQ_OPTION },
    { gstrManualFeedKwd,        IManualFeedProc,     INVOCA_VALUE | REQ_OPTION },
    { "DefaultManualFeed",      IDefManualFeedProc,  STRING_VALUE },
    { "PageStackOrder",         IPageStackOrderProc, STRING_VALUE | REQ_OPTION },
    { "DefaultOutputOrder",     IDefOutputOrderProc, STRING_VALUE },
    { "JCLResolution",          IJCLResProc,         INVOCA_VALUE | REQ_OPTION | ALLOW_HEX },
    { "DefaultJCLResolution",   IDefaultJCLResProc,  STRING_VALUE },
    { "SetResolution",          ISetResProc,         INVOCA_VALUE | REQ_OPTION },
    { "ScreenAngle",            IScreenAngleProc,    QUOTED_VALUE },
    { "ScreenFreq",             IScreenFreqProc,     QUOTED_VALUE },
    { "ResScreenAngle",         IResScreenAngleProc, QUOTED_NOHEX | REQ_OPTION },
    { "ResScreenFreq",          IResScreenFreqProc,  QUOTED_NOHEX | REQ_OPTION },
    { "Font",                   IFontProc,           STRING_VALUE | REQ_OPTION },
    { "DefaultFont",            IDefaultFontProc,    STRING_VALUE },
    { "OpenUI",                 IOpenUIProc,         STRING_VALUE | REQ_OPTION },
    { "CloseUI",                ICloseUIProc,        STRING_VALUE | ALLOW_MULTI },
    { "JCLOpenUI",              IOpenUIProc,         STRING_VALUE | REQ_OPTION },
    { "JCLCloseUI",             ICloseUIProc,        STRING_VALUE | ALLOW_MULTI },
    { "OrderDependency",        IOrderDepProc,       STRING_VALUE | ALLOW_MULTI },
    { "UIConstraints",          IUIConstraintsProc,  STRING_VALUE | ALLOW_MULTI },
    { "QueryOrderDependency",   IQueryOrderDepProc,  STRING_VALUE | ALLOW_MULTI },
    { "NonUIOrderDependency",   IOrderDepProc,       STRING_VALUE | ALLOW_MULTI },
    { "NonUIConstraints",       IUIConstraintsProc,  STRING_VALUE | ALLOW_MULTI },
    { "VMOption",               IVMOptionProc,       QUOTED_NOHEX | REQ_OPTION },
    { "FCacheSize",             IFCacheSizeProc,     STRING_VALUE | REQ_OPTION },
    { "FreeVM",                 IFreeVMProc,         QUOTED_VALUE },
    { "OpenGroup",              IOpenGroupProc,      STRING_VALUE | ALLOW_MULTI },
    { "CloseGroup",             ICloseGroupProc,     STRING_VALUE | ALLOW_MULTI },
    { "OpenSubGroup",           IOpenSubGroupProc,   STRING_VALUE | ALLOW_MULTI },
    { "CloseSubGroup",          ICloseSubGroupProc,  STRING_VALUE | ALLOW_MULTI },
    { "Include",                IIncludeProc,        QUOTED_VALUE | ALLOW_MULTI },
    { "PPD-Adobe",              IPPDAdobeProc,       QUOTED_VALUE },
    { "FormatVersion",          IFormatVersionProc,  QUOTED_VALUE },
    { "FileVersion",            IFileVersionProc,    QUOTED_VALUE },
    { "ColorDevice",            IColorDeviceProc,    STRING_VALUE },
    { "Protocols",              IProtocolsProc,      STRING_VALUE | ALLOW_MULTI },
    { "Extensions",             IExtensionsProc,     STRING_VALUE | ALLOW_MULTI },
    { "FileSystem",             IFileSystemProc,     STRING_VALUE },
    { "NickName",               INickNameProc,       QUOTED_VALUE },
    { "ShortNickName",          IShortNameProc,      QUOTED_VALUE },
    { "LanguageLevel",          ILangLevelProc,      QUOTED_NOHEX },
    { "LanguageEncoding",       ILangEncProc,        STRING_VALUE },
    { "LanguageVersion",        ILangVersProc,       STRING_VALUE },
    { "TTRasterizer",           ITTRasterizerProc,   STRING_VALUE },
    { "ExitServer",             IExitServerProc,     INVOCA_VALUE },
    { "Password",               IPasswordProc,       INVOCA_VALUE },
    { "PatchFile",              IPatchFileProc,      INVOCA_VALUE },
    { "JobPatchFile",           IJobPatchFileProc,   INVOCA_VALUE | REQ_OPTION },
    { "PSVersion",              IPSVersionProc,      QUOTED_NOHEX | ALLOW_MULTI },
    { "ModelName",              INullProc,                       QUOTED_VALUE },
    { "Product",                IProductProc,        QUOTED_NOHEX | ALLOW_MULTI },
    { "SuggestedJobTimeout",    IJobTimeoutProc,     QUOTED_VALUE },
    { "SuggestedWaitTimeout",   IWaitTimeoutProc,    QUOTED_VALUE },
    { "PrintPSErrors",          IPrintPSErrProc,     STRING_VALUE },
    { "JCLBegin",               IJCLBeginProc,       QUOTED_VALUE },
    { "JCLToPSInterpreter",     IJCLToPSProc,        QUOTED_VALUE },
    { "JCLEnd",                 IJCLEndProc,         QUOTED_VALUE },
    { "LandscapeOrientation",   ILSOrientProc,       STRING_VALUE },
    { gstrCustomSizeKwd,        ICustomSizeProc,     INVOCA_VALUE | REQ_OPTION },
    { "ParamCustomPageSize",    IParamCustomProc,    STRING_VALUE | REQ_OPTION },
    { "MaxMediaWidth",          IMaxWidthProc,       QUOTED_VALUE },
    { "MaxMediaHeight",         IMaxHeightProc,      QUOTED_VALUE },
    { "HWMargins",              IHWMarginsProc,      STRING_VALUE },
    { "CenterRegistered",       ICenterRegProc,      STRING_VALUE },
    { "ADORequiresEExec",        IReqEExecProc,       STRING_VALUE },
    { "ADOTTFontSub",            ITTFontSubProc,      QUOTED_VALUE | REQ_OPTION },
    { "ADTrueGray",             ITrueGrayProc,       STRING_VALUE },
    { "ADHasEuro",              IHasEuroProc,        STRING_VALUE },
    { "Throughput",             IThroughputProc,     QUOTED_NOHEX },
    { "SymbolValue",            ISymbolValueProc,    INVOCA_VALUE | REQ_OPTION },
    { "Status",                 INullProc,           QUOTED_VALUE | ALLOW_MULTI },
    { "PrinterError",           INullProc,           QUOTED_VALUE | ALLOW_MULTI },
    { "SymbolLength",           INullProc,           STRING_VALUE | REQ_OPTION },
    { "SymbolEnd",              INullProc,           STRING_VALUE | ALLOW_MULTI },
    { "End",                    INullProc,           VALUETYPE_NONE | ALLOW_MULTI },
};

#define NUM_BUILTIN_KEYWORDS (sizeof(gPpdBuiltInKeywordTable) / sizeof(KWDENTRY))



DWORD
DwHashKeyword(
    PSTR    pstrKeyword
    )

/*++

Routine Description:

    Compute the hash value for the specified keyword string

Arguments:

    pstrKeyword - Pointer to the keyword string to be hashed

Return Value:

    Hash value computed using the specified keyword string

--*/

{
    PBYTE   pub = (PBYTE) pstrKeyword;
    DWORD   dwHashValue = 0;

    while (*pub)
        dwHashValue = (dwHashValue << 1) ^ *pub++;

    return dwHashValue;
}



PKWDENTRY
PSearchKeywordTable(
    PPARSERDATA pParserData,
    PSTR        pstrKeyword,
    INT        *piIndex
    )

/*++

Routine Description:

    Check if a keyword appears in the built-in keyword table

Arguments:

    pParserData - Points to parser data structure
    pstrKeyword - Specifies the keyword to be searched
    piIndex - Returns the index of the entry in the built-in keyword
        table corresponding to the specified keyword.

Return Value:

    Pointer to the entry in the built-in table corresponding to the
    specified keyword. NULL if the specified keyword is not supported.

--*/

{
    DWORD   dwHashValue;
    INT     iIndex;

    ASSERT(pstrKeyword != NULL);
    dwHashValue = DwHashKeyword(pstrKeyword);

    for (iIndex = 0; iIndex < NUM_BUILTIN_KEYWORDS; iIndex++)
    {
        if (pParserData->pdwKeywordHashs[iIndex] == dwHashValue &&
            strcmp(gPpdBuiltInKeywordTable[iIndex].pstrKeyword, pstrKeyword) == EQUAL_STRING)
        {
            *piIndex = iIndex;
            return (PKWDENTRY) &gPpdBuiltInKeywordTable[iIndex];
        }
    }

    return NULL;
}



BOOL
BInitKeywordLookup(
    PPARSERDATA pParserData
    )

/*++

Routine Description:

    Build up data structures to speed up keyword lookup

Arguments:

    pParserData - Points to parser data structure

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    DWORD   iIndex, iCount;

    //
    // Allocate memory to hold extra data structures
    //

    iCount = NUM_BUILTIN_KEYWORDS;
    pParserData->pdwKeywordHashs = ALLOC_PARSER_MEM(pParserData, iCount * sizeof(DWORD));
    pParserData->pubKeywordCounts = ALLOC_PARSER_MEM(pParserData, iCount * sizeof(BYTE));

    if (!pParserData->pdwKeywordHashs || !pParserData->pubKeywordCounts)
    {
        ERR(("Memory allocation failed: %d\n", GetLastError()));
        return FALSE;
    }

    //
    // Precompute the hash values for built-in keywords
    //

    for (iIndex = 0; iIndex < iCount; iIndex++)
    {
        pParserData->pdwKeywordHashs[iIndex] =
            DwHashKeyword((PSTR) gPpdBuiltInKeywordTable[iIndex].pstrKeyword);
    }

    return TRUE;
}