/*DDK*************************************************************************/
/*                                                                           */
/* COPYRIGHT (C) Microsoft Corporation, 1989                                 */
/* COPYRIGHT (C) Microsoft Corporation, 1989                                 */
/* COPYRIGHT    Copyright (C) 1995 IBM Corporation                           */
/*                                                                           */
/*    The following IBM OS/2 WARP source code is provided to you solely for  */
/*    the purpose of assisting you in your development of OS/2 WARP device   */
/*    drivers. You may use this code in accordance with the IBM License      */
/*    Agreement provided in the IBM Device Driver Source Kit for OS/2. This  */
/*    Copyright statement may not be removed.                                */
/*                                                                           */
/*****************************************************************************/
/**************************************************************************
 *
 * SOURCE FILE NAME = DEVBLOCK.C
 *
 * DESCRIPTIVE NAME = Contains Device Block related routines.
 *
 *
 * VERSION = V2.0
 *
 * DATE
 *
 * DESCRIPTION : This file contains prde_FillPdb, prde_FillLdb, prde_DisablePdb
 *               and related routines.
 *
 *
 * FUNCTIONS :         ClearMem
 *                     MatchHWFontName
 *                     PaperDimensions
 *                     ImageCoords
 *                     ScanString
 *                     pszStrChr
 *                     szDCopy
 *                     BuildKeyName
 *                     CmndPaper
 *                     CmndInpTray
 *                     LoadInfoSegment
 *                     FreeMemory
 *                     EnableDebug
 *                     SetCanvasMetrics
 *                     PpdDefaults
 *                     prde_FillPdb
 *                     GetFirstPrinterMatch
 *                     prde_FillLdb
 *                     CleanUpDDC
 *                     prde_DisablePdb
 *                     Exitpdd
 *                     ValidateDriveData
 *                     LoadProfile
 *                     CheckSegName
 *                     ProfileAllocStringQuery
 *
 *
 * NOTES
 *
 *
 * STRUCTURES
 *
 * EXTERNAL REFERENCES
 *
 * EXTERNAL FUNCTIONS
 *
*/

/*
**
** Include GENPLIB error handling.
*/
#define  INCL_ERRORS
#define  INCL_PM
#define  INCL_DOS
#define  INCL_DOSINFOSEG
#define  INCL_DOSDEVICES
#define  INCL_DEV
#include "inc\prdinclt.h"
#include "inc\prdgextf.h"
#include "inc\prdeextf.h"
#include "inc\prdmath.h"
#include "inc\prderre.h"
#include "inc\utl.h"
#define  INCL_WINSHELLDATA
#define  INCL_INCL_WINSHELLDATA
#define  OD_MEMORY     8L
#define  NGREMAX  222
#include <string.h>
#include <pmshl.h>
#define  INCL_SPL
#define  INCL_SPLDOSPRINT
#define  INCL_SPLERRORS
#include <pmspl.h>
#include <pmerr.h>
#include "inc\config.h"
#include "inc\pspagtun.h"             /* V2.174057  Page Tuning */
#define  INCL_GENPLIB_ERROR
#define  INCL_GENPLIB_MEMORY
#define  INCL_GENPLIB_HANDLER
#define  INCL_GENPLIB_GAMMA  /* @V3.0GAMMA1 */
#include <genplib.h>

typedef struct
    {                   /* kran */
    PFNL pfnl;
    int nParams;
    } VCTD;
extern VCTD od_memory_vect[NGREMAX+1];
extern PBYTE StringTable[MAX_STRINGS]; /* devmode.c                         */
extern PVOID pProcessHeap;
extern Semaphore lpPCThreadDataSem;
extern DeviceSemaphoreTable lpDevSemTable;
extern DDTType DDT;
extern VOID FillSFonts( PDV, PVOID,SHORT,USHORT );   /*            */
extern SHORT SFQueryFonts( PDV );
extern LONG DefaultFontCount( PDESPPD );        /*            */
extern PSZ GetDefaultPageSize( PDESPPD, PBYTE );
extern PFORMSTRUCT GetImageableArea( SHORT, PFORMSTRUCT, PDESPPD, PBYTE );
extern INT  _Optlink CompareRealNames( PSZ, PSZ );  /*            */
extern enum DDState prd_DDState;
extern HMTX tsemCOM1;
extern HMTX tsemCOM2;
extern HMTX tsemCOM3;
extern HMTX tsemLPT1;
extern HMTX tsemLPT2;
extern HMTX tsemLPT3;
extern HMTX tsemDriver; /* Global Driver semaphore           */
extern HMODULE pscript_module;

ARGERRRET  *aAER = (ARGERRRET  *) -1;


VOID  Exitpdd(SHORT usTermCode);
BOOL  ValidateDriveData(PLDRIVDATA,PCNFDATA);
VOID  CheckSegName(PCNFDATA,PB  *);
VOID  LoadProfile(PB  *);
/* extern HMODULE hmodDisplay; */
extern VOID   SetLDBFlag();
extern PSZ FontFullName[];
extern PSZ FontResourceName[];
/*
**            Need a full DevOpenStruct as shown in the PDR under ENABLE 02 function
*/
typedef struct _xDEVOPENSTRUC {
    DEVOPENSTRUC dos;
    ULONG      ulStateInfo;
    ULONG      ulType;      /* Need this */
    ULONG      ulHDC;
} xDEVOPENSTRUC;
typedef xDEVOPENSTRUC FAR *xPDEVOPENSTRUC;
#define OD_QUEUED 2L
#define OD_DIRECT 5L


/*
** Keywords defined for retrieving information from os2.ini
** and writing information to os2.ini
*/

CHAR szPMSPOOLERP[] = "PM_SPOOLER_PRINTER";/* The segment name under which
                                          device name is stored             */
CHAR szPMSPOOL[] = "PM_SPOOLER";
CHAR szPMPRINTER[] = "PRINTER";        /* The minor key name under which
                                          device name is stored             */

#if      DEBUG
BOOL fRipOnEnter = FALSE;
#endif                                 /* DEBUG                             */
void   szCopy(PSZ,PSZ,int);

/*
** Function to allocate a buffer and store a profile string.
*/
/*            */
LONG ProfileAllocStringQuery( HINI, PSZ, PSZ, PSZ, PPVOID );

/*
**
*/
extern INT _Optlink CopyStr( PSZ, PSZ );

/*            */
extern ULONG ULGreVersion;
VOID   Hook22Calls( PFNL * );

extern GLOBALDATA globals;
extern ULONG APIENTRY ExceptHandler( );

/***************************************************************************
 *
 * FUNCTION NAME =  ClearMem
 *
 * DESCRIPTION   =  Set cBytes to 0, starting at pByte.
 *
 *
 * INPUT         = PB      pByte
 *                 SHORT  cByte
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID ClearMem( PB pByte, SHORT cByte )
{
  SHORT i;

  for (i = 0; i < cByte; i++)
  {
    *pByte++ = 0;
  }
  return ;
}

/***************************************************************************
 *
 * FUNCTION NAME =  MatchHWFontName
 *
 * DESCRIPTION   =
 *
 * Returns the index number (1-n), in the current printer's list of supported
 * fonts, of the font that has the specified face name, or 0 if not found.
 *
 * Effects: Scans the list of all fonts offered by the current printer until
 *          one is found whose font name is the same as that requested. The
 *          index of this matching font is then returned. If no match is
 *          found, 0 is returned.
 *
 * Warnings: Fonts have two different names.  The FullName is the most
 *           complete; an example is "ITC Avant Garde Gothic Book Oblique".
 *           However, the font names used in the printer font list have an
 *           abbreviated form like "AvantGarde-BookOblique".
 *
 * INPUT         = PSZ     pszFontName
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = Index number of desired font, or 0 if it wasn't found.
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

SHORT MatchHWFontName( PSZ pszFontName )
{
  SHORT i;

  /*
  ** Find the resource number corresponding to this fontfullname
  */
  for (i = 0; FontResourceName[i]; i++)
  {
    if (szIsEqual(pszFontName,
                  (PSZ)FontResourceName[i]) )
    {
      return  i;
    }
  }

  return 0;
}

/***************************************************************************
 *
 * FUNCTION NAME =  PaperDimensions
 *
 * DESCRIPTION   =
 *
 *           Returns pointer to page dimensions in terms of picture cells .
 *
 * INPUT         = 1.Paper name , & 2.array of pointer to printer resources
 *
 *                 CHAR  *szPaperName    pointer to paper name
 *                 PB   * apResources    array of pointer to
 *                                          printer resources
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL =
 *           Returns Null if paper name not found else pointer to array of
 *           paper dimensions.
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

SHORT *PaperDimensions( CHAR *szPaperName, PB  *apResources )
{
  SHORT   i, j, k, l, m;                  /* general scratch index variables   */
/*CHAR    abBuffer[MAX_PSIZE];            +* byte buffer                       */
  PDESPPD pdesPpd;                        /* pointer to print segment
                                             descriptor                        */
  PSZ   *ppFormIndex;

  pdesPpd = (PDESPPD) apResources[PPDRES];
  j = pdesPpd->desPage.ofsDimxyPgsz;   /* offset to paper name and
                                          dimension value pairs             */
  k = pdesPpd->desPage.iDmpgpairs;     /* count of such name,dimension sets */
  i = 0;

  /* point to name table */
  ppFormIndex = (PSZ *)(apResources[RESBUF] + pdesPpd->desForms.ofsFormIndex);

  /*
  **  The command buffer contains paper name followed by paper dimensions .
  **  Scan for paper name and return the paper dimensions.
  */
  while (i < k)
  {
    l = *((CHAR *) apResources[RESBUF] + j);
    j += sizeof( SHORT );                       /* move to command */

#if 0
//  for (m = 0; m < l; m++)
//  {
//    *(abBuffer + m) = *((CHAR *)apResources[RESBUF]+j+m+1);
//  }
//  *(abBuffer + l) = '\0';              /* complete the string               */
//  j += l+1;
#endif
    /*
    ** If match occurs the search complete.
    */

    /*            */
    if ( ! CompareRealNames( ppFormIndex[ l ],
                  szPaperName))
    {
      return ((SHORT *) (apResources[RESBUF] + j));
    }
    j += 4;                            /* skip two integers                 */
    i++;
  }
  return (NULL);
}

/***************************************************************************
 *
 * FUNCTION NAME =  ImageCoords
 *
 * DESCRIPTION   =
 *
 *    Returns pointer to image coordinates in terms of picture cells
 *
 * INPUT         = 1.Paper name , & 2.array of pointer to printer resources
 *
 *                 CHAR  *szPaperName    pointer to paper name for which
 *                                          image coords reqd
 *                 PB    *apResources    array of pointers to printer
 *                                          resources
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL =
 *           Returns Null if paper name not found else pointer to array of
 *           paper imageable coordinates.
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

SHORT *ImageCoords( CHAR *szPaperName, PB *apResources )
{
  SHORT   i, j, k, l, m;        /* scratch index variables               */
  CHAR    abBuffer[MAX_PSIZE];  /* scratch byte array buffer             */
  PDESPPD pdesPpd;              /* pointer to printer descriptor segment */
  FORMSTRUCT FormStruct;

  pdesPpd = (PDESPPD) apResources[PPDRES];

  k = pdesPpd->desPage.iImgpgpairs;    /* count of such pairs               */

  /*
  ** Get each Imageable area - if it matches paper name return coords
  */
  for ( i = 0; i < k; i++ )
  {
    GetImageableArea( i, (PFORMSTRUCT)&FormStruct, pdesPpd,
                      (PBYTE)apResources[RESBUF] );

    if (! strcmp( FormStruct.FormName, szPaperName))
      return (PSHORT)FormStruct.Data;
  }

  /*
  **  The command buffer contains paper name followed by imageable coordinates .
  **  Scan for paper name and return the imageable coordinates.
  */
#if 0
//j = pdesPpd->desPage.ofsImgblPgsz;   /* offset pointer to name coord
//i = 0;
//while (i < k)
//{
//  l = *((CHAR *) apResources[RESBUF] + j);
//
//  for (m = 0; m < l; m++)
//  {
//    *(abBuffer + m) = *((CHAR *)apResources[RESBUF] + j + m + 1);
//  }
//  *(abBuffer + l) = '\0';              /* complete the string               */
//  j += l+1;
//
//  if (szIsEqual(abBuffer,
//                szPaperName))        /* search complete when name found   */
//  {
//    return ((SHORT *)(apResources[RESBUF]+j));
//  }
//  j += 8;                            /* four coords of 2 bytes each       */
//  i++;
//}
#endif

  return (NULL);
}

#if      0

/*
**     This function is not called so don't compile but keep for record
*/
/***************************************************************************
 *
 * FUNCTION NAME =  ScanString
 *
 *
 * DESCRIPTION   = Scan the presence of string szScan in szSource
 *                 and return TRUE if found.
 *
 * INPUT         = PSZ   szSource
 *                 PSZ   szScan
 *
 * OUTPUT        = NONE
 *
 *
 * RETURN-NORMAL = TRUE
 *
 *
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

BOOL ScanString( PSZ szSource, PSZ szScan )
{
  SHORT i, j, k, l;
  PSZ   pszb;

  pszb = szScan;
  i = 0;                               /* compute length of search string   */

  while (*pszb++ != '\0')
  {
    i++;
  }
  pszb = szSource;
  j = 0;

  while (*pszb++ != '\0')
  {
    j++;
  }

  /*
  ** Only search till end - length of search string
  */
  j -= i;

  if (j < i)
  {
    return( FALSE );
  }

  /*
  ** Start searching the hard way from the ist char of scan string.
  */
  for (k = 0; k < j; k++)
  {
    pszb = szSource+k;
    l = 0;

    while (l < i && *(pszb + l) == *(szScan + l))
    {
      l++;
    }

    if (l < i)
    {
    }
    else
    {
      return( TRUE );
    }
  }
  return( FALSE );
}

/*
**      End of not compiled
*/
#endif

/***************************************************************************
 *
 * FUNCTION NAME =  pszStrChr
 *
 * DESCRIPTION   = Looks for chTarget in pszSource - if found returns
 *                 pointer to location else returns NULL (like runtime strchr
 *
 * INPUT         = PSZ  pszSource
 *                 CHAR chTarget
 *
 * OUTPUT        = NONE
 *
 *
 * RETURN-NORMAL = pointer to location chTarget
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

PSZ pszStrChr( PSZ pszSource, CHAR chTarget )
{

  while (*pszSource && *pszSource != chTarget)
  {
    pszSource++;
  }

  if (*pszSource)
  {
    return( pszSource );
  }
  else
  {
    return( NULL );
  }
}

/***************************************************************************
 *
 * FUNCTION NAME =  szDCopy
 *
 * DESCRIPTION   = Duplicates an null terminated or delimiter terminated
 *                 string with bounds
 *                 checking to prevent overflow of the destination buffer.
 *
 * INPUT         = PSZ pszDst     Ptr to the destination buffer
 *                 PSZ pszSrc     Ptr to the source string
 *                 SHORT nbDst    Size of the destination buffer
 *
 * OUTPUT        = NONE
 *
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

SHORT szDCopy( PSZ pszDst, PSZ pszSrc, SHORT nbDst )
{
  SHORT i;
  i = 0;

  while (*pszDst++ = *pszSrc++)
  {
    i++;

    if (nbDst-- <= 0 || *(pszSrc-1) == ';' || *(pszSrc-1) == ',')
    {
      i--;
      *--pszDst = 0;
      break;
    }
  }
  return( i );
}

/*
**            This is a new version using SPL calls as described in the
** Device Driver Conference in San Jose 7/93
*/
/***************************************************************************
 *
 * FUNCTION NAME = BuildKeyName
 *
 * DESCRIPTION   = Builds the key name in form of:
 *                 PM_DD_<printerName>,<deviceDriver>.<deviceName>
 *                 The informaton is pulled out of ini file under key of
 *                 PM_SPOOLER_PRINTER.  If the information can't be found
 *                 we can use defaults as long as the type of device is known.
 *
 * INPUT         = PDEVOPENSTRUC   pdop      Pointer to devopen structure
 *                 PCNFDATA        pcnfData  Pointer to printer
 *                                           configuration data
 *
 * OUTPUT        = NONE
 *
 *
 * RETURN-NORMAL = BOOL: TRUE if port
 *                       FALSE if not
 *                       Note - to FILE is not a port
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/
BOOL BuildKeyName( PDEVOPENSTRUC pdop, PCNFDATA pcnfData )
{
  BOOL      fUseDefaultDev = TRUE;
  CHAR      chDriverDevice[ 64 ];
  PPRDINFO3 pbuf = NULL;
  PPRQINFO3 qbuf = NULL;
  PBYTE     MemPbuf = NULL;
  PBYTE     MemQbuf = NULL;
  PSZ       pszComma;
  PSZ       pszDeviceName  = "";
  PSZ       pszKeyApp;
  PSZ       pszPrinterName = NULL;
  SHORT     i;
  ULONG     count = 0;
  ULONG     returned, total, needed;
  USHORT    rc;
  BOOL      fIsPort = FALSE;  /*            */

  chDriverDevice[0] = 0;

  /* Check user supplied printer name */
  if ( pdop         &&
       pdop->pdriv  &&
       ( pdop->pdriv->cb >= ( sizeof( DRIVDATA ) - 1 ) ) )
  {
    if ( strlen( pdop->pdriv->szDeviceName ) )
    { /*
      ** Build the Hardcopy_driver.Device_Name part of key
      */
      i = CopyStr( chDriverDevice, pdop->pszDriverName );
      i += CopyStr( chDriverDevice + i, "." );
      CopyStr( chDriverDevice + i, pdop->pdriv->szDeviceName );
      fUseDefaultDev = FALSE;
    }
  }

  /* Check Logical address */
  if( (! pdop->pszLogAddress) ||
      (! *pdop->pszLogAddress)   )
  { /*
    ** No Destination logical address.  Enumerate the queues and get default
    */

    SplEnumQueue( NULL, 3L, qbuf, 0L, &returned, &total, &needed, 0L );
    MemQbuf = GplMemoryAlloc( pProcessHeap, needed ); /*            */
    qbuf = (PPRQINFO3)MemQbuf;
    count = needed;
    SplEnumQueue( NULL, 3L, qbuf, count, &returned, &total, &needed,
                  0L );
    /* Check each Queue for default */
    for ( i = 0; i < returned; i++ )
    {
      /* Is it a PostScript printer? */
      if ( !strncmp( qbuf->pszDriverName, "PSCRIPT", 7 ) )
      {
        pszPrinterName = qbuf->pszPrinters;
        if ( fUseDefaultDev )
          pszDeviceName  = qbuf->pszDriverName;

        if ( qbuf->fsType & PRQ3_TYPE_APPDEFAULT )  /* Is it the default */
          break;
      }
      qbuf++;
    }
  }
  else
  /*
  ** Lets check if pszLogAddress is a Queue
  */
  if ( SplQueryQueue( NULL, pdop->pszLogAddress, 3, qbuf, 0L, &needed ) ==
       NERR_BufTooSmall )
  { /* Yes - a queue exists with that name */
    MemQbuf = GplMemoryAlloc( pProcessHeap, needed );  /*            */
    qbuf = (PPRQINFO3)MemQbuf;
    count = needed;
    SplQueryQueue( NULL, pdop->pszLogAddress, 3, qbuf, count, &needed );
    pszPrinterName = qbuf->pszPrinters;
    if ( fUseDefaultDev )
      pszDeviceName  = qbuf->pszDriverName;
  }
  else
  { /*
    ** pszLogAddress is not a queue - must be a port or file name
    --
    ** Get list of all printers
    */
    SplEnumDevice( NULL, 3L, pbuf, 0L, &returned, &total, &needed, 0L );
    MemPbuf = GplMemoryAlloc( pProcessHeap, needed ); /*            */
    pbuf = (PPRDINFO3)MemPbuf;
    count = needed;
    SplEnumDevice( NULL, 3L, pbuf, count, &returned, &total, &needed, 0L );

    /*
    ** Since passed in file names are fully qualified look for ':'
    */
    for ( i = 0; i < returned; i++ )
    {
      if ( ! strcmp( pdop->pszLogAddress, pbuf->pszLogAddr ) )
      {
        pszPrinterName = pbuf->pszPrinterName;
        if ( fUseDefaultDev )
          pszDeviceName  = pbuf->pszDrivers;
        fIsPort = TRUE;   /*            is a port */
        break;
      }
      else
      if ( ( ! strcmp( pbuf->pszLogAddr, "FILE" ) ) &&
           pdop->pszLogAddress[1] == ':'             )
      { /* File name */
        if ( fUseDefaultDev )
        { /* No device supplied, use the first one */
          pszPrinterName = pbuf->pszPrinterName;
          pszDeviceName  = pbuf->pszDrivers;
          break;
        }
        else
        do
        {
          if ( pszComma = strchr( pbuf->pszDrivers, ',' ) )
            *pszComma = 0;    /* break up line */

          if ( ! strcmp ( pbuf->pszDrivers, chDriverDevice ) )
          {
            pszPrinterName = pbuf->pszPrinterName;
            break;
          }
          pbuf->pszDrivers = pszComma+1;

        } while ( pszComma );

        if ( pszPrinterName )
          break;
      }
      pbuf++;
    }
  }

  if ( ! pszPrinterName )
    pszPrinterName = " ";

  if ( pszComma = strchr( pszPrinterName, ',' ) )
    *pszComma = 0;

  if ( fUseDefaultDev &&
       ( pszComma = strchr( pszDeviceName, ',' ) ) )
    *pszComma = 0;

  pszKeyApp = pcnfData->szKeyApp;      /* Make local copy of pointer        */
  /*
  ** Now build the key
  */
  CopyStr( pszKeyApp, (PSZ) "PM_DD_" );
  strcat( pszKeyApp, pszPrinterName );
  strcat( pszKeyApp, "," );
  if ( fUseDefaultDev )
    strcat( pszKeyApp, pszDeviceName );
  else
    strcat( pszKeyApp, chDriverDevice );

  /*
  ** If no device use generic
  */
  if ( pszDeviceName = strchr( pszKeyApp, '.' ) )
    pszDeviceName++;
  else
    pszDeviceName = (PSZ) GetR3String( IDS_Generic );

  /*
  ** Copy just the device name to other Vars
  */
  CopyStr( (PSZ) pcnfData->szPrtName, pszDeviceName );
  CopyStr( (PSZ) pcnfData->szSegName, pszDeviceName );

  /*
  ** Free bufs if allocated
  */
  if ( MemPbuf )
    GplMemoryFree( MemPbuf );
  if ( MemQbuf )
    GplMemoryFree( MemQbuf );

  return fIsPort;   /*            */

}

#if 0    /* The old way - saved for posterity */
//VOID BuildKeyName( PDEVOPENSTRUC pdop, PCNFDATA pcnfData )
//{
//  #define  NAMELEN       32
//
//  CHAR       chEnumBuffer[256];
//  /* CHAR       chParBuffer[256]; */
//  PSZ        chParBuffer;             /* change from CHAR to PSZ            */
//  CHAR       chDevBuffer[NAMELEN];
//  SHORT      iScan, jScan;
//  PSZ        pszDeviceName;
//  PSZ        pszScratch;
//  PSZ        pszQueueName;
//  BOOL       fMatchLogAddress;
//  PLDRIVDATA pLDriv;
//  PSZ        pszKeyApp;
//  PSZ        pszNullBuffer = "";
//
//
//  if (pdop->pszLogAddress)
//  {
//    /*
//    **Get a list of all the logical  printers in the system
//    */
//    PrfQueryProfileString( HINI_SYSTEMPROFILE, (PVOID) szPMSPOOLERP,
//                          (PVOID) NULL, (PVOID)"",
//                          (PVOID) chEnumBuffer,
//                          (ULONG) sizeof( chEnumBuffer ) );
//  }
//  else
//  {
//    /*
//    ** The user supplied logical address is gone go for defaults
//    ** Get default printer only
//    */
//    PrfQueryProfileString( HINI_PROFILE, (PSZ) szPMSPOOL, (PSZ) szPMPRINTER,
//                          (PSZ) ";", (PSZ) chEnumBuffer,
//                          (ULONG) sizeof( chEnumBuffer ) );
//
//    if (pszScratch = pszStrChr( (PSZ)chEnumBuffer, ';'))
//    {
//      *pszScratch = 0;
//    }
//  }
//  fMatchLogAddress = FALSE;
//  pszDeviceName = pszNullBuffer;
//  iScan = 0;
//
//  while (iScan < sizeof( chEnumBuffer ) && chEnumBuffer[iScan] != '\0')
//  {
//    /*
//    ** Note the format of each entry of PM_SPOOLER_PRINTER is:
//    ** (Brackets indicate none or more)
//    ** "port;device_name1[,device_nameN];queue_name1[,queue_nameN];;"
//    **
//    ** Read the details of each printer
//    */
//    /*
//    ** PrfQueryProfileString( HINI_SYSTEMPROFILE, (PSZ) szPMSPOOLERP,
//    **                       (PSZ) &chEnumBuffer[iScan], (PSZ) ";;;;",
//    **                       (PSZ) chParBuffer, (ULONG) sizeof(chParBuffer) );
//    */
//    /*
//    ** Since the format for PM_SPOOLER_PRINTER can be of any length,
//    ** allocate the right amount of memory for the buffer and store the
//    ** string in it.
//    */
//    /*            */
//    ProfileAllocStringQuery( HINI_SYSTEMPROFILE, (PSZ) szPMSPOOLERP,
//                             (PSZ) &chEnumBuffer[iScan], (PSZ) ";;;;",
//                             (PPVOID) &chParBuffer );
//
//    /*
//    ** Point to start of device names
//    */
//    pszDeviceName = pszStrChr( (PSZ)chParBuffer, ';' );
//
//    /*
//    ** Set delim to null, bump pointer
//    */
//    *pszDeviceName++ = '\0';
//
//    /*
//    ** Find the Qs
//    */
//    pszQueueName = pszStrChr( pszDeviceName, ';' );
//
//    /*
//    ** Set delim to null, bump pointer
//    */
//    *pszQueueName++ = '\0';
//
//    /*
//    ** Force a match to default then break out if pszLogAddress is null
//    */
//    if (!pdop->pszLogAddress)
//    {
//      /*
//      ** Make sure the is a . in default driver name
//      */
//      if (pszStrChr(pszDeviceName, '.'))
//      {
//        fMatchLogAddress = TRUE;
//      }
//      break;
//    }
//
//    /*
//    ** See if LogAddress is a port
//    */
//    if (szIsEqual( pdop->pszLogAddress, (PSZ) chParBuffer ))
//    {
//      fMatchLogAddress = TRUE;
//    }
//    else
//    {
//      /*
//      ** Check if LogAddress is a queue - Note there can be multiple queues
//      ** listed here hence the loop
//      */
//      jScan = 0;
//
//      while (jScan = szDCopy((PSZ) chDevBuffer, pszQueueName,
//                             sizeof( chDevBuffer )))
//      {
//        if (szIsEqual( pdop->pszLogAddress, (PSZ) chDevBuffer ))
//        {
//          fMatchLogAddress = TRUE;
//          break;                       /* The queue check                   */
//        }
//        pszQueueName += jScan+1;
//      }
//    }
//
//
//    if (fMatchLogAddress)
//    {
//      /*
//      ** The search loop
//      */
//      break;
//    }
//
//    /*
//    **skip the current enumerated name and come to next name
//    */
//    while (chEnumBuffer[iScan] != '\0')
//    {
//      iScan++;
//    }
//
//    /*
//    ** skip the null also.Another null means enumeration over
//    */
//    iScan++;
//  }
//
//  /*
//  ** Get the device name - if there is a match and the device names
//  ** has a comma in it OR no match the device name comes from user
//  ** supplied driver data
//  */
//  if ((fMatchLogAddress && (pszScratch = pszStrChr( pszDeviceName, ','))) ||
//       (fMatchLogAddress == FALSE))
//  {
//    if (pdop->pdriv != NULL)
//    {
//      pLDriv = (PLDRIVDATA) pdop->pdriv;
//
//      /*
//      ** If caller provided device name use it
//      */
//      if ((pLDriv->cb >= (LONG) sizeof(DRIVDATA) - 1) && *pLDriv->szDeviceName !=
//          0)
//      {
//        pszDeviceName = (PSZ)pLDriv->szDeviceName;
//      }
//      else if (pLDriv->cb >= (LONG)sizeof(LDRIVDATA))
//      {
//        /*
//        ** See if our cnf data was passed in
//        */
//        pszDeviceName = pLDriv->cnfData.szSegName;
//      }
//      else
//      {
//        pszDeviceName = pszNullBuffer;
//      }
//    }
//    else
//    {
//      pszDeviceName = pszNullBuffer;
//    }
//  }
//
//  /*
//  ** If device name has a '.' in it go past it
//  */
//  if (pszScratch = pszStrChr( pszDeviceName, '.' ))
//  {
//    *pszScratch = '\0';
//
//    /*
//    ** Make sure the device driver is pscript
//    */
//    if (szIsEqual( pszDeviceName, (PSZ)"PSCRIPT" ))
//    {
//      pszDeviceName = pszScratch + 1;
//    }
//    else
//    {
//      pszDeviceName = pszNullBuffer;
//    }
//  }
//
//  /*
//  ** After all this work if no device use generic
//  */
//  if (*pszDeviceName == '\0')
//  {
//    pszDeviceName = (PSZ) GetR3String( IDS_Generic );
//  }
//
//  /*
//  ** Build keyapp form of PM_DD_<printerName>,<deviceDriver>.<deviceName>
//  */
//  pszKeyApp = pcnfData->szKeyApp;      /* Make local copy of pointer        */
//
//  if (fMatchLogAddress)
//  {
//    /*
//    ** Copy prefix
//    */
//    szCopy( pszKeyApp, (PSZ) "PM_DD_", 7 );
//
//    /*
//    ** Add printer logical name
//    */
//    szCopy( pszKeyApp+6, (PSZ) &chEnumBuffer[iScan], NAMELEN );
//
//    /*
//    ** Add driver name
//    */
//    szCopy( pszKeyApp + strlen( pszKeyApp ), (PSZ)",PSCRIPT.", 10 );
//
//    /*
//    ** Add printer (device) name
//    */
//    szCopy( pszKeyApp + strlen( pszKeyApp ), pszDeviceName, NAMELEN );
//  }
//  else
//  {
//    /*
//    ** No match use blank dot device name
//    */
//    szCopy( pszKeyApp, (PSZ)" .", 3 );
//    szCopy(pszKeyApp + 2, pszDeviceName, NAMELEN );
//  }
//
//  /*
//  ** Copy the printer name to vars
//  */
//  szCopy( (PSZ) pcnfData->szPrtName, pszDeviceName, NAMELEN );
//  szCopy( (PSZ) pcnfData->szSegName, pszDeviceName, NAMELEN );
//
//  /*
//  ** Free the profile string buffer used by chParBuffer.
//  */
//  /*             */
//  GplMemoryFree( chParBuffer, NULL );
//
//  return ;
//}
#endif

/***************************************************************************
 *
 * FUNCTION NAME = CmndPaper
 *
 * DESCRIPTION   = Returns paper select command
 *
 * Input argument : 1.Buffer to contain command string & 2.array of pointer
 *                  to printer resources.
 *                  3. Pointer to paper name for which command required
 *
 * INPUT         = PB   szCmndString        pointer to paper select
 *                                          command buffer
 *                 PB    * apResources   array of pointer to printer
 *                                          resources
 *                 PSZ  pszPaperName        pointer to paper name for
 *                                          which command required
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = Returns FALSE if no input tray selected .
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/
/*
**
*/
BOOL CmndPaper( PSZ *szCmndString, PB *apResources, PSZ pszPaperName,
                PDDC pddc )
{
  SHORT    i, j, k, l, m;              /* scratch index variables           */
/*CHAR     abBuffer[MAX_PSIZE];        +* scratch byte array buffer         */
  PCNFDATA pcnfData;                   /* pointer to printer configuration
                                          data                              */
  PDESPPD  pdesPpd;                    /* pointer to printer descriptor
                                          segment                           */
  PSZ     *ppFormIndex;

  pcnfData = (PCNFDATA) apResources[CNFRES];
  pdesPpd = (PDESPPD) apResources[PPDRES];
  j = pdesPpd->desPage.ofsLspgCmnds;   /* offset to paper name, command
                                          pairs                             */
  k = pdesPpd->desPage.iCmpgpairs;     /* count of such pairs               */
  i = 0;

  /*
  **
  */
  *szCmndString = NULL;

  /*
  **  The command buffer contains paper name followed by command to
  **  select the paper. Scan for paper name ,copy the command to supplied
  **  buffer.If paper size custom specified ,check whether printer supports
  **   this feature .
  */
  if (szIsEqual( (PSZ) "custom", (PSZ) pszPaperName ))
  {
    if (pdesPpd->desPage.fIsVariablePaper == TRUE)
    {
      if ((*szCmndString = (PSZ) GplMemoryAlloc( pddc->pdv->pDCHeap, 7 )) != 0)
      {
        /*
        **
        */
        CopyStr( (PSZ) *szCmndString, (PSZ) "custom" );
        return( TRUE );
      }
    }
  }
  else
  /*
  ** a type of paper supported by
  ** printer
  */
  {
    ppFormIndex = (PSZ *)(apResources[RESBUF] + pdesPpd->desForms.ofsFormIndex);
    while (i < k)
    {
      l = *((CHAR *) apResources[RESBUF] + j );
      j += sizeof( SHORT );                       /* move to command */

      /*
      ** if paper name found ,search over ;return the command
      ** in supplied buffer
      */
      /*            */
      if ( ! CompareRealNames( ppFormIndex[ l ],
                     (PSZ) pszPaperName ))
      {
        if ((*szCmndString = (PSZ) GplMemoryAlloc( pddc->pdv->pDCHeap,
                           strlen( apResources [ RESBUF ] + j ) + 2 )) != 0)
        {
          /*
          **
          */
          i = CopyStr( *szCmndString, apResources[ RESBUF ] + j );
          strcat( *szCmndString, "\n" );
          return( TRUE );
        }
      }
      else
      /*
      ** name doesn't match ;skip this
      ** pair
      */
      {
        j += strlen( apResources[ RESBUF ] + j ) + 1;
      }
      i++;
    }
  }
  return( FALSE );
}

/***************************************************************************
 *
 * FUNCTION NAME = CmndInpTray
 *
 * DESCRIPTION   = Returns pointer to Input tray select command
 *
 * Input argument : 1.Buffer to contain command string & 2.array of pointers
 *                  to printer resources.
 *                  3. Tray index for which command string required.
 *
 * INPUT         = PB   szCmndString        pointer to paper select
 *                                          command buffer
 *                 PB    * apResources   array of pointer to printer
 *                                          resources
 *                 SHORT      iTrayIndex    tray index UPPER, MIDDLE,
 *                                          LOWER, MANUAL
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = Returns FALSE if no input tray selected .
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

/*
**
*/
BOOL CmndInpTray( PSZ *szCmndString, PB *apResources, SHORT iTrayIndex,
                  PDDC pddc )
{
  USHORT   usLoop;               /* Loop count                               */
  USHORT   usIBDispLen;          /* Input bin display string length          */
  USHORT   usIBCmndLen;          /* Input bin PosScript command string len   */
  UINT     uiMemSize = 0;        /* Contains size of memory or string        */
  USHORT   ofsInpBinCmnd = 0;    /* Input bin command offset                 */
  USHORT   ofsManCmnd = 0;       /* Manual command offset                    */
  PCNFDATA pcnfData = (PCNFDATA) apResources[CNFRES];
                                 /* Printer configuration data pointer       */
  PDESPPD  pdesPpd = (PDESPPD) apResources[PPDRES];
                                 /* Printer descriptor segment pointer       */
  BOOL     bReturnCode = TRUE;   /* Function return code                     */

  /*
  ** Initialize the pointer to NULL.
  */
  *szCmndString = (PSZ) 0;

  /*
  ** Commands are stored in the format :
  **     <Bin name> <Command> ,where bin name can be Upper ,Middle ,Lower
  ** or Manual .
  ** Corresponding to command index,scan for bin name an copy the command
  ** to supplied buffer.
  ** If the following is TRUE, then manual feed is enabled and selected.
  */
  /*
  ** If manual feed is enabled, iManualfeed is set to either TRUE or FALSE.
  */
  if (pdesPpd->desInpbins.iManualfeed != NONE)
  {
    /*
    ** Manual feed is set to TRUE (yes this is correct).
    */
    if (iTrayIndex == MANUAL)
    {
      ofsManCmnd = pdesPpd->desInpbins.ofsManualtrue;
    }
    else
    {
      ofsManCmnd = pdesPpd->desInpbins.ofsManualfalse;
    }

    /*
    ** Add command string length (including 0 terminator and newline
    ** character) to the total memory buffer size.
    */
    uiMemSize = strlen( apResources[ RESBUF ] + ofsManCmnd ) + 2;
  }

  /*
  ** Set the output bin if manual feed is not selected.
  */
  if (iTrayIndex != MANUAL)
  {
    /*
    ** Retrieve the input bin commands offset.
    */
    ofsInpBinCmnd = pdesPpd->desInpbins.ofsCmInpbins;

    for (usLoop = 0 ; usLoop < pdesPpd->desInpbins.iInpbinpairs ; usLoop++)
    {
      /*
      ** Retrieve the size (in bytes) of the current input bin display
      ** string.  Include the 0 terminator in storing the size.
      */
      usIBDispLen = strlen( apResources[ RESBUF ] + ofsInpBinCmnd ) + 1;

      /*
      ** Retrieve the size (in bytes) of the current input bin PostScript
      ** command string.  Include the 0 terminator in the size.
      */
      usIBCmndLen = strlen( apResources[ RESBUF ] + ofsInpBinCmnd +
                            usIBDispLen ) + 1;

      /*
      ** comparing first bytes is enough to locate the command
      */
      if (usLoop == iTrayIndex)
      {
        /*
        ** Store the input bin memory requirements.  Include the newline
        ** character ( + 1) in storing the line size.  Then, get out of
        ** this loop.
        */
        uiMemSize += usIBCmndLen + 1;
        break;
      }

      /*
      ** Go to the next input bin.
      */
      ofsInpBinCmnd += usIBDispLen + usIBCmndLen;
    }
  }

  /*
  ** Allocate memory for the manual feed/input bin string.
  */
  if (uiMemSize > 0)
  {
    if ((*szCmndString = (PSZ) GplMemoryAlloc( pddc->pdv->pDCHeap, uiMemSize )) != 0)
    {
      uiMemSize = 0;

      /*
      ** This is not zero if a manual command string exists.
      */
      if (ofsManCmnd > 0)
      {
        uiMemSize = CopyStr( (PSZ) *szCmndString, (PSZ) (apResources[ RESBUF ] +
                                                  ofsManCmnd) );
        *(*szCmndString + uiMemSize++) = '\n';
      }

      /*
      ** If this is FALSE, the loop will terminate when it is equal (not
      ** less than) pdesPpd->desInpbins.iInpbinpairs.
      */
      if (usLoop < pdesPpd->desInpbins.iInpbinpairs)
      {
        uiMemSize += CopyStr( (PSZ) (*szCmndString + uiMemSize), (PSZ)
                              (apResources[ RESBUF ] + ofsInpBinCmnd +
                               usIBDispLen) );

        *(*szCmndString + uiMemSize++) = '\n';
      }

      /*
      ** Zero-terminate the string.
      */
      *(*szCmndString + uiMemSize) = 0;
    }
    else
    {
      bReturnCode = FALSE;
    }
  }

  return( bReturnCode );
}

/***************************************************************************
 *
 * FUNCTION NAME = LoadInfoSegment
 *
 * DESCRIPTION   =
 *
 *    The printer info segname lies in the variable SegName.
 *    This routines locates the segment and loads it into memory
 *    resident tables.If load successful it returns TRUE else FALSE.
 *
 * Input argument : Array of pointer to printer resources.
 *
 * INPUT         =
 *                 PB    * apResources   array of pointer to printer
 *                                          resources
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

BOOL LoadInfoSegment(PB  *apResources)
{
  HMODULE    hmodule;                  /* Scratch variable to get driver
                                          module handle                     */
  PSIGNATURE pSignature;               /* Pointer to signature              */
  SHORT      i, j;
  SHORT      iCntFiles;                /* Stack var                         */
  SHORT      usDirSize;
  PDRENTRY   pdrTable;                 /* pointer to printer segments
                                          directory table                   */
  PCNFDATA   pcnfData;                 /* pointer to printer configuration
                                          data                              */
  PDESPPD    pdesPpd;                  /* pointer to printer descriptor
                                          segment                           */
  BYTE      *pvPtr;                    /* local pointer to resource         */
  BYTE      *pPpd;                     /* local pointer to PPD              */
  PBYTE      pbScratch;
  CHAR      *pc;
  PLONG      plOffsets;                /* Ptr to offsets                    */
  PSZ       *ppFormIndex;              /* Ptr to ptr table                  */

  pcnfData = (PCNFDATA) apResources[CNFRES];
  hmodule = pscript_module;

  /*
  ** Get the directory of PPBs resource
  */
  if (DosGetResource( hmodule, ALLPPDS,
                      DIRECTORY_PPB, (PPVOID) &pSignature ))
  {
    RIP( "LoadinfoSeg: DosGetResource failed" );
    return( FALSE );
  }

  /*
  **
  ** Copy the number of printers/PPBs
  */
  iCntFiles = pcnfData->iCntFiles = pSignature->cntEntries;

  /*
  ** Make sure there are some printers
  */
  if (iCntFiles == 0)
  {
    DosFreeResource( pSignature );
    return( FALSE );
  }

  /*
  **If pdrTable is null then allocate storage and copy over directory
  **...NOTE... It is the callers job to release the directory memory!!!
  */
  if (apResources[DIRRES] == NULL)
  {
    usDirSize = iCntFiles * sizeof( DRENTRY );

    /*
    ** Allocate storage needed for directory
    */
    if ((apResources[DIRRES] = (PCHAR) GplMemoryAlloc( pProcessHeap,
                                                       usDirSize )) == NULL)
    {
      PrintLog( (PSZ)"Can't allocate DIRECTORY instance\n" );
      DosFreeResource( pSignature );
      return( FALSE );
    }
    pdrTable = (PDRENTRY) apResources[DIRRES];
    pbScratch = (PBYTE) pdrTable;

    /*
    ** Point to directory part
    */
    pvPtr = (BYTE *)((BYTE *)pSignature + sizeof( SIGNATURE ));

    /*
    ** Copy data over
    */
    for (i = 0 ; i < usDirSize ; i++)
    {
      *pbScratch++ = *pvPtr++;
    }
  }

  /*
  ** Got what is needed so free resource
  */
  DosFreeResource( pSignature );
  pdrTable = (PDRENTRY) apResources[DIRRES];

  /*
  ** If lGetPtr is 1 then just return with pcnfData->iCntFiles set and
  ** apResources[DIRRES] loaded with directory
  */
  if (pcnfData->lGetPtr == 1)
  {
    return( TRUE );
  }

  /*
  **  Search for supplied printer name in the directory and if located get
  **  the requisite parameters i.e. length and offset for the printer segment
  **  tobe loaded.
  */
  i = 0;

  while (i < iCntFiles)
  {
    if (szIsEqual( pcnfData->szSegName, (CHAR *)pdrTable ))
    {
      break;
    }
    i++;

    pc = (char *)pdrTable;
    pc += DRENTRYSIZE;
    pdrTable = (DRENTRY *)pc;

    /*
    ** pdrTable++;
    */
    /*
    ** (CHAR *)pdrTable += sizeof( DRENTRY );
    */
  }

  if (i >= pcnfData->iCntFiles)        /* Printer segment name not found    */
  {
    RIP( "LoadInfoSegment: printer name not found" );
    return( FALSE );
  }

  /*
  ** Open resource for individual PPB
  */

  if (DosGetResource( hmodule, ALLPPDS, (SHORT) pdrTable->lResNum,
                      (PPVOID) &pvPtr ))
  {
    PrintLog( (PSZ)"LoadInfoSeg() failed: returning error\n" );
    return( FALSE );
  }

  /*
  ** allocate memory for printer descriptor segment
  */
  if (!(pdesPpd = (PDESPPD)GplMemoryAlloc(pProcessHeap, sizeof(DESPPD))))
  {
    RIP( "LoadInfoSegment: memory allocation failed" );
    DosFreeResource( pvPtr );
    return( FALSE );
  }
  apResources[PPDRES] = (void  *)pdesPpd;

  /*
  ** copy printer descriptor segment from resource file
  */

  pPpd = apResources[PPDRES];

  for (i = 0; i < sizeof(DESPPD ); i++)
  {
    *pPpd++ = *pvPtr++;
  }

  /*
  ** allocate memory for printer stored items segment
  */
  j = pdesPpd->desItems.iSizeBuffer;

  if (!(apResources[RESBUF] = (void *) GplMemoryAlloc( pProcessHeap, j )))
  {
    RIP( "LoadInfoSegment: RESBUF memory allocation failed" );
    GplMemoryFree( (PB) pdesPpd );
    DosFreeResource( pvPtr );
    return( FALSE );
  }

  /*
  ** Copy over variable part of printer description
  */
  pPpd = (BYTE *)apResources[RESBUF];

  for (i = 0; i < j; i++)
  {
    *pPpd++ = *pvPtr++;
  }
  DosFreeResource( pvPtr );

  /*
  ** Convert the offsets in Forms index table to pointers
  */

  plOffsets = (PLONG)((PBYTE)apResources[RESBUF] +
                        pdesPpd->desForms.ofsFormIndex);
  ppFormIndex = (PSZ *)((PBYTE)apResources[RESBUF] +
                        pdesPpd->desForms.ofsFormIndex);

  for ( i = 0; i < pdesPpd->desForms.usFormCount; i++ )
  {
    ppFormIndex[ i ] =  (PSZ)((PBYTE)apResources[RESBUF] + *plOffsets);
    plOffsets++;
  }

  return( TRUE );
}

/***************************************************************************
 *
 * FUNCTION NAME = FreeMemory
 *
 * DESCRIPTION   = To free all the memory allocated from heap.
 *
 * Input argument : Array of pointer to printer resources.
 *
 * INPUT         =
 *                 PB    * apResources   array of pointer to printer
 *                                          resources
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

void FreeMemory( PB *apResources )
{
  SHORT i;

  for (i = 0 ; i < IMAXRES ; i++)
  {
    if (apResources[i] != NULL)
    {
      GplMemoryFree( (PB) apResources[i] );
    }
  }
  return ;
}

#if      DEBUG
//extern void   OpenLogFile(PDV );
//BOOL fFirstTime = TRUE;
//BOOL fInt3Called = FALSE;
//
///***************************************************************************
// *
// * FUNCTION NAME = EnableDebug
// *
// *
// * DESCRIPTION   =
// *
// *
// * Input argument :
// *
// * INPUT         = PDV pdv     The DEVOPENSTRUCT comment string
// *
// *
// *
// * OUTPUT        = NONE
// *
// *
// * RETURN-NORMAL = NONE
// *
// * RETURN-ERROR  = NONE
// *
// **************************************************************************/
//
//void EnableDebug( PDV pdv )
//{
//  PSZ pszTmp;
//  CHAR szBuffer[10];
//
//  fInt3Called = FALSE;
//
//  /*
//  **Read from os2.ini whether interrupt to be caused or not
//  */
//  PrfQueryProfileString( HINI_PROFILE, (PSZ) "PSCRIPT", (PSZ) "INT3", (PSZ) "",
//                        (PSZ) szBuffer, (ULONG) sizeof( szBuffer ) );
//
//  if (szBuffer[0] == '1')
//  {
//    Int3( );
//    fInt3Called = TRUE;
//  }
//
//  /*
//  ** OpenLogFile is always called so that it can query
//  ** os2.ini to determine whether or not logging should
//  ** actually be turned on.
//  */
//  OpenLogFile( pdv );
//}
//
#endif

/***************************************************************************
 *
 * FUNCTION NAME = SetCanvasMetrics
 *
 * DESCRIPTION   = Set the canvas metrics (size and orientation) for the
 *                 physical device.
 *
 * Input argument :
 *
 * INPUT         = PCANVAS pcan            pointer to canvas structure
 *                 PB  *apResources     array of pointers to printer
 *                                      resources
 *
 * OUTPUT = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR = NONE
 *
 **************************************************************************/
 void SetCanvasMetrics( PCANVAS pcan, PB * apResources )
{
  SHORT    i;
  LONG     li, lScale;
  SHORT   *piPtr;                    /* pointer to array of integers      */
  PDESPPD  pdesPpd;                  /* pointer to printer descriptor
                                          segment                           */
  PCNFDATA pcnfData;                 /* pointer to printer configuratiuon
                                        data                              */
  CHAR szPaper[MAX_PSIZE];
  FIXED fxRes;                       /* The device resolution in DPI      */
  FIXED fxScratch;                   /* a scratch variable                */

  #define  FX_PAPER_WIDTH 0x88000L   /* Paper width = 8.5 inches          */
  #define  FX_PAPER_HEIGHT 0xb0000L  /* Paper height = 11.0 inches        */
  #define  FX_ONEBY72    0x0038DL    /* Multiplier 1/72                   */

  pcnfData = (PCNFDATA) apResources[CNFRES];
  pdesPpd = (PDESPPD) apResources[PPDRES];

  #if      DEBUG
    LogCall( "SetCanvasMetrics(%lp)\n",
            ((PB)&pcan)+sizeof(pcan) );
  #endif                                 /* DEBUG                             */

  /*
  ** store the paper name in array which corresponds to a starting page
  ** no of <= shPageno
  */

  PageAtPaper( (PCNFDATA)  pcnfData, (PSZ)szPaper, (SHORT *)&i );
  lScale = (LONG) pcnfData->jobProperties.uScale;

  /*
  ** Assign the currently selected resolution to the print structure.
  ** This is the resolution that will be sent to the print file.
  ** If there are multiple resoulutions use Jop Prop else use ppd
  */
  if ( pdesPpd->desItems.ResList.uNumOfRes &&
       pcnfData->uResolution != 0 )
  {
    pcan->iRes = pcnfData->uResolution;
  }
  else
  {
    pcan->iRes = pdesPpd->desItems.iResDpi;
  }

  fxRes = LongToFx((LONG)pcan->iRes );
  i = (int) ((pcan->iRes * 100L) / pdesPpd->desItems.lScrFreq );

  /*
  ** noScrFreq was multiplied by 100 at parse time
  */
  /*
  ** total no of pic elements in a rectangle
  */
  pcan->nGrayShades = i * i + 1;

  /*
  ** get array of paper dimension integers
  */
  piPtr = PaperDimensions( szPaper, (PB *) apResources );

  /*
  ** if no dimensions returned then
  */
  if (piPtr == NULL)
  {
    /*
    ** Compute the paper boundary.
    */
    pcan->rclPaper.xLeft = 0;
    pcan->rclPaper.xRight = FxToLong( frmul( fxRes,
                                             FX_PAPER_WIDTH) );
    pcan->rclPaper.yTop = FxToLong( frmul( fxRes,
                                           FX_PAPER_HEIGHT) );
    pcan->rclPaper.yBottom = 0;
  }
  else
  {
    pcan->rclPaper.xLeft = 0;
    pcan->rclPaper.xRight = (((long) * piPtr++)*(long)pcan->iRes) / 72L;
    pcan->rclPaper.yTop = (((long) * piPtr) * (long) pcan->iRes) / 72L;
    pcan->rclPaper.yBottom = 0;
  }

  if (pcnfData->jobProperties.iOrient != PORTRAIT)
  {
    li = pcan->rclPaper.yTop;
    pcan->rclPaper.yTop = pcan->rclPaper.xRight;
    pcan->rclPaper.xRight = li;
  }

  /*
  ** at the request of excel we are returning the page size as
  ** the actual size * 100/%scale_factor .
  */
  pcan->rclPaper.xRight = 100L * pcan->rclPaper.xRight / lScale;
  pcan->rclPaper.yTop = 100L * pcan->rclPaper.yTop / lScale;
  pcan->nBitPlanes = 1;                /* no of color bit planes            */
  pcan->nBitsPerPel = 1;               /* no of bits per picture element    */

  /*
  ** Compute the canvas boundary.
  */
  PrintLog( (PSZ) "fxRes = %f\n", fxRes );

  /*
  ** Set the paper's imageable area.  These values should come
  ** from the PPD files.
  **
  **              Due to rounding errors we need to store the imageable
  ** area in points as well as pels. Do not scale the points
  */

  piPtr = ImageCoords((CHAR *) szPaper, (PB *)apResources );

  /*
  **  if no dimensions returned by the routine then
  */
  if (piPtr == NULL)
  {
    /*
    ** These numbers come from generic ppd
    */
    pcan->rclpt.xLeft = 21;
    pcan->rclpt.xRight = 591;
    pcan->rclpt.yTop = 771;
    pcan->rclpt.yBottom = 21;
  }
  /*
  ** Store in LONG form in units of
  ** points
  */
  else
  {
    pcan->rclpt.xLeft = (long)*piPtr++;
    pcan->rclpt.yBottom = (long)*piPtr++;
    pcan->rclpt.xRight = (long)*piPtr++;
    pcan->rclpt.yTop = (long)*piPtr;

    if (pcnfData->jobProperties.iOrient != PORTRAIT)
    {
      li = pcan->rclpt.xLeft;
      pcan->rclpt.xLeft = pcan->rclpt.yBottom;
      pcan->rclpt.yBottom = li;
      li = pcan->rclpt.xRight;
      pcan->rclpt.xRight = pcan->rclpt.yTop;
      pcan->rclpt.yTop = li;
    }
  }

  /*
  ** make sure that imageable area tallies with page dimensions
  */
  if (pcan->rclpt.xRight > pcan->rclPaper.xRight)
  {
    pcan->rclpt.xRight = pcan->rclPaper.xRight;
  }

  if (pcan->rclpt.yTop > pcan->rclPaper.yTop)
  {
    pcan->rclpt.yTop = pcan->rclPaper.yTop;
  }

  /*
  ** Convert to pels
  */
  pcan->rcl.xLeft = pcan->rclpt.xLeft * (long) pcan->iRes / 72L;
  pcan->rcl.yBottom = pcan->rclpt.yBottom * (long) pcan->iRes / 72L;
  pcan->rcl.xRight = pcan->rclpt.xRight * (long) pcan->iRes / 72L;
  pcan->rcl.yTop = pcan->rclpt.yTop * (long) pcan->iRes / 72L;

  /*
  ** At the request of excel we are returning the imageable coords as the
  ** actual size * 100/%scale_factor .
  ** Do not scale left and bottom since it moves printable area over on
  ** scale < 100 and into the unprintable area on >100
  */
  /*
  **  pcan->rcl.xLeft   = 100L * pcan->rcl.xLeft   / lScale;
  **  pcan->rcl.yBottom = 100L * pcan->rcl.yBottom / lScale;
  */
  pcan->rcl.xRight = 100L * pcan->rcl.xRight / lScale;
  pcan->rcl.yTop = 100L * pcan->rcl.yTop / lScale;
  PrintLog( (PSZ) "rclPaper = {%ld, %ld, %ld, %ld}\n",
            pcan->rclPaper.xLeft, pcan->rclPaper.yTop,
            pcan->rclPaper.xRight, pcan->rclPaper.yBottom );
  PrintLog((PSZ)"pcan->rcl = {%ld, %ld, %ld, %ld}\n",
            pcan->rcl.xLeft, pcan->rcl.yTop,
            pcan->rcl.xRight, pcan->rcl.yBottom );
}

/***************************************************************************
 *
 * FUNCTION NAME = PpdDefaults
 *
 * DESCRIPTION   = This routine reads in default values from PPD info
 *                 segment into default types if they still do not
 *                 contain any values .
 *
 * Input argument :Array of pointer to printer resources.
 *
 * INPUT         =
 *                 PB    * apResources  array of pointers to printer
 *                                         resources
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

void PpdDefaults ( PB *apResources )
  /*
  **  PB  *apResources - array of pointer to printer resources
  */
{
  PCNFDATA pcnfData;                   /* pointer to printer configuration
                                          data                              */
  PDESPPD  pdesPpd;                    /* pointer to printer descriptor
                                          segment                           */
  SHORT    i, j, k, l, m;              /* scratch index variables           */
  CHAR     szBuffer[MAX_PSIZE];        /* scratch byte array buffer         */
  CHAR     szPaper[MAX_PSIZE];         /* scratch byte array buffer         */

  pcnfData = (PCNFDATA) apResources[CNFRES];
  pdesPpd = (PDESPPD) apResources[PPDRES];
  *szBuffer = '\0';

  /*
  ** the color device option, even if set true in os2.ini,
  ** should be overruled if the device is a black and white device
  */
  if (pdesPpd->desItems.fIsColorDevice != TRUE)
  {
    pcnfData->jobProperties.fIsColorDevice = FALSE;
  }

  /*
  **Read the default paper setting from  ppb Resources
  */
  CopyStr( szPaper, GetDefaultPageSize(  pdesPpd, apResources[RESBUF] ) );
#if 0
//if ((k = pdesPpd->desPage.ofsDfpgsz) != -1)
//{
//  i = *(apResources[RESBUF] + k );
//
//  for (j = 0; j < i; j++)
//  {
//    szPaper[j] = *(apResources[RESBUF]+k+j+1 );
//  }
//  szPaper[i] = '\0';
//}
#endif

  /*
  ** whichever bins are provided put the default paper names for those bins
  */
  /*
  ** a finite no of input tray exists.
  */
  if ((m = pdesPpd->desInpbins.iInpbinpairs) > 0)
  {
    j = pdesPpd->desInpbins.ofsCmInpbins;
  }
  else
  {
    m = 1;
    j = pdesPpd->desInpbins.ofsDefinputslot;
  }

  /*
  ** copy the tray name into a scratch buffer
  */
  for (i = 0; i < m; i++)
  {
    /*
    **
    */
    j += CopyStr( szBuffer, apResources[ RESBUF ] + j ) + 1;

#if 0
//  k = *(apResources[RESBUF] + j );
//  j++;
//  szCopy((CHAR *) szBuffer, (CHAR *)apResources[RESBUF] + j, k );
//  *(szBuffer + k) = '\0';              /* make it a proper string        */
//  j += k;
#endif

    /*
    **
    */
    j += strlen( apResources[ RESBUF ] + j ) + 1;
#if 0
//  k = *(apResources[RESBUF] + j );
//  j += (1 + k );                     /* skip the next string which is a
//                                        command string                    */
#endif

    /*
    ** if no name exists put a default name in the paper tray
    */
    if (pcnfData->u.iv.pSourcePaper->szPaperName[i][0] == '\0')
    {
      szCopy((PSZ)&pcnfData->u.iv.pSourcePaper->szPaperName[i][0],
             (CHAR *) szPaper, MAX_PSIZE );
    }
  }

  /*
  ** Read the Default Font name setting from ppb  file
  */
  if (*(pcnfData->szFontName) == '\0')
  {
    if ((k = pdesPpd->desFonts.ofsDeffont) != -1)
    {
      /*
      **
      */
      CopyStr( pcnfData->szFontName, apResources[ RESBUF ] + k );

#if 0
//    i = *(apResources[RESBUF] + k );
//
//    for (j = 0; j < i; j++)
//    {
//      pcnfData->szFontName[j] = *(apResources[RESBUF] + k + j + 1 );
//    }
//    pcnfData->szFontName[i] = '\0';
#endif
    }
  }
}

VOID GetFirstPrinterMatch(PSZ pszDestPrinterInfo,SHORT usDestBufSize );

/***************************************************************************
 *
 * FUNCTION NAME = prde_FillPdb
 *
 * Enable : Fill pDeviceBlock subfunction
 *
 * DESCRIPTION   = The information supplied on this call is only required for
 *                 Direct and Indirect DCs, but at this stage the DC Type
 *                 is not known; it is supplied on the Enable DC subfunction.
 *
 *                 So acquire pDeviceBlock Instance Data (from the global
 *                 Device Driver Heap) for all DCs, and (optionally) release
 *                 it when the DC Type is known.
 *
 *
 * Input argument :
 *
 * INPUT         = PDEVOPENSTRUC pdop      pointer to devopen structure
 *                 Dword         param2    DC Type
 *
 *
 * OUTPUT        = NONE
 *
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

ULONG   prde_FillPdb(PDEVOPENSTRUC pdop,Dword param2)
{
  PDV             pdv;              /* pointer to DV structure to be
                                          allocated                         */
  SHORT           k;
  SHORT           i, j, l, m, n;    /* scratch index variables           */
  SHORT           cFnt;             /* font count, hw + soft             */
  CHAR           *pszString;        /* a scratch pointer to a null
                                       terminated string                 */
  PCNFDATA        pcnfData;         /* pointer to printer configuration
                                       data                              */
  PDESPPD         pdesPpd;          /* pointer to printer descriptor
                                       segment                           */
  PB             *apResources;      /* array of pointers to printer
                                       resources                         */
  xPDEVOPENSTRUC  xpdop;
  /*
  ** array of pointer to following resources:-
  ** ppb file header , directory buffer ,
  ** itemsbuffer , configuration buffer , ppd
  ** parameter buffer .
  */
  PLDRIVDATA      plDriv;         /* to point to drivdata
                                       configuration block               */
  LDRIVDATA       lDrivData;
  USHORT          uHWFonts;
  SHORT           usFunN;         /* index into the engines function table */
  PFNL           *apfnDispatch;
  REGREC          Regrec;
  ULONG           ulException;
  /*
  ** create the heap for the current DDC.
  */
  /*
  ** CreateDDCHeap( pddc );
  */

  if (!(apResources = (PB  *)GplMemoryAlloc( pProcessHeap, IMAXRES *4 )))
  {
    RIP( "prde_FillPdb: can't allocate resources instance" );
    return( (ULONG)(ERROR_NEG) );
  }

  for (i = 0; i < IMAXRES; i++)
  {
    apResources[i] = NULL;
  }

  #if      DEBUG
    LogCall( "prde_FillPdb(%lp, %lp)", ((PSZ)&pdop)+sizeof(pdop) );
  #endif                                 /* DEBUG                             */

  PrintLog( (PSZ) "sizeof(DV) = %d\n", sizeof(DV) );
  PrintLog( (PSZ) "sizeof(DDC) = %d\n", sizeof(DDC) );

  /*
  ** Allocate space to store the information specific to a given
  ** printer configuration parameters.
  */
  if (!(apResources[CNFRES] = (void *) GplMemoryAlloc( pProcessHeap,
                                                       sizeof( CNFDATA ))))
  {
    /*
    ** MFR : was logging incorrect message
    */

    RIP( "prde_FillPdb: can't allocate CNFDATA instance" );
    return( (ULONG) (ERROR_NEG) );
  }
  pcnfData = (PCNFDATA) apResources[CNFRES];
  pcnfData->u.iv.pSourcePaper = (PSOURCE)GplMemoryAlloc( pProcessHeap,
                                                         sizeof( SOURCE ) );

  pcnfData->lGetPtr = 1;               /* Open dir, get the count           */

  if (!LoadInfoSegment( (PB *)apResources ))
  {
    RIP( "prde_FillPdb: got zero printer files from resource" );
    return( (ULONG) (ERROR_NEG) );
  }

  /*
  ** Allocate space to store the information specific to a given
  ** output device (port).
  */
  /*
  ** Create a heap and carve out a DV
  */
  if (!(pdv = CreateDCHeap( ) ) )
  {
    RIP( "prde_FillPdb: can't allocate DV instance" );
    return( (ULONG) (ERROR_NEG) );
  }

  #if      DEBUG
    EnableDebug( pdv );
  #endif

  /*
  ** Initialize the channel structure.
  */
  pdv->cn.fChannelIsValid = FALSE;
  pdv->cn.fChannelError = FALSE;

  /*
  ** Allocate the output buffer
  */
  /*
  **
  ** Insert an error return code in the event GplMemoryAlloc fails.
  */
  if ((pdv->cn.abBuf = (PCHAR) GplMemoryAlloc( pdv->pDCHeap, ABBUF_SIZE ))
        == NULL)
  {
/*  DBPRINTF( ( "GplMemoryAlloc for pdv->cn.abBuf failed" ) ); */
    return( (ULONG) ERROR_NEG );
  }

  /*
  **DCR1399.8 build key from printer name - don't use keyapp could be network,
  **name change, ect... have changed name of printer.
  */
  if ( BuildKeyName( pdop, pcnfData ) )
    SETFLAG( pdv->ulGenFlags, IS_DIRECT_PORT );  /*            */


  /*
  **            Do the LoadInfoSegment before the loadprofile/readprofile
  ** so the ini data can be available for readprofile
  */

  /*
  ** load the printer info segment of the printer specified in
  ** pcnfData->szSegName from the printer resources into memory.
  */
  pcnfData->lGetPtr = 0;               /* Get the printer                   */

  if (!LoadInfoSegment((PB *) apResources ))
  {
    RIP( "prde_FillPdb: LoadInfoSegment failed" );
    return( (ULONG) ((long) ERROR_NEG) );
  }

  /*
  ** read in the printer configuration data for the printer defined
  ** by pcnfData->szKeyApp.  this configuration data is read from
  ** os2sys.ini.
  */
  LoadProfile( apResources );


  /*
  ** check the pcnfData->szSegName.  if it is NULL, set it to a
  ** default value.  i pulled this into a subroutine, because it
  ** confused the compiler when it was inline.
  */
  /*
  ** CheckSegName( pcnfData, apResources );
  */
  /*
  ** Overrule Os2.ini if pdop->pdrivdata contains information
  */

  /*
  **
  ** DRIVDATA ptr is null OR count is too small set up defaults
  ** Too small is before 1.3 size
  ** 696 =  40 (DRIVDATA-1) + 656 (size of cnfdata up to pvResource)
  */
  if ( pdop->pdriv == NULL ||
       pdop->pdriv->cb < 696L )
  {
    lDrivData.cb = sizeof( LDRIVDATA );
    lDrivData.lVersion = DRIVERSION;
    szCopy( lDrivData.szDeviceName, pcnfData->szSegName,
            sizeof( lDrivData.szDeviceName ) );
    lDrivData.cnfData = *pcnfData;
    plDriv = (PLDRIVDATA) &lDrivData;
  }
  else
  { /*
    ** There is enough to work with
    */
    if ( pdop->pdriv->cb < (LONG) sizeof( LDRIVDATA ))
    { /*
      ** This is before 2.1.  Copy over
      */
      memset( (PBYTE)&lDrivData, 0, sizeof( LDRIVDATA ) );
      memcpy( (PBYTE)&lDrivData, pdop->pdriv, pdop->pdriv->cb );
      /*
      ** ValidateDriveData will check version before checkingduplex mode.
      ** Will also set resolution to reasonable value
      */

      plDriv = (PLDRIVDATA) &lDrivData;
    }
    else  /* >= case */
    {
      plDriv = (PLDRIVDATA) pdop->pdriv;
    }

    /*
    ** let's verify the drive data is what we think it is.
    */
/***if (!(ValidateDriveData( (PLDRIVDATA) pdop->pdriv, pcnfData ))) **/
    if (!(ValidateDriveData( plDriv, pcnfData )))
    {
      return( (ULONG) ERROR_NEG );
    }

    /*
    ** copy the printer configuration parameters from the
    ** drivdata into the configuration data block.
    **
    ** copy various printer configuration parameters from the
    ** drivdata into the configuration data block.  we have just
    ** initialized cnfData by calling ReadProfile, and we just
    ** want to overwrite some of the entries, not all of them.
    */
    /*
    **Use the forms/trays from LoadProfile since they are current!
    */
    /*
    ** pcnfData->sourcePaper = plDriv->cnfData.sourcePaper;
    */

    szCopy( (PSZ) pcnfData->szFontName, (PSZ) plDriv->cnfData.szFontName,
            sizeof( pdv->szDefFont ) );
    pcnfData->effOutput = plDriv->cnfData.effOutput;
    pcnfData->jobProperties = plDriv->cnfData.jobProperties;
    pcnfData->iCntCopies = plDriv->cnfData.iCntCopies;
    pcnfData->iDestnType = plDriv->cnfData.iDestnType;
    szCopy( (PSZ) pcnfData->szDestnFile, (PSZ) plDriv->cnfData.szDestnFile,
            sizeof( pdv->szDestnFile ) );
    pcnfData->sDuplexMode = plDriv->cnfData.sDuplexMode; /* DCR 1462         */
    pcnfData->uResolution = plDriv->cnfData.uResolution; /*            */
    pcnfData->sUseDLFonts = plDriv->cnfData.sUseDLFonts; /*            */
    pcnfData->usPSLevel1 = plDriv->cnfData.usPSLevel1;

    /* Must do copy now befor font section */
    pdv->sUseDLFonts = pcnfData->sUseDLFonts;            /*            */
    /* D74609 */
    pdv->usPSLevel1 = pcnfData->usPSLevel1;

    /*
    ** @V3.0GAMMA1
    */
    pcnfData->lGammaValue = plDriv->cnfData.lGammaValue;
  }

  /*
  ** if any default values are not set, read them from the resources now.
  */
  PpdDefaults((PB *) apResources );

#if 0
//else            saved for reference
//{
//  /*
//  ** if we get to this point, we have either been passed a
//  ** NULL pointer to drivdata, or a pointer to drivdata which
//  ** contains no printer configuration data.  in either case,
//  ** we need to get the default configuration for the current
//  ** printer from os2sys.ini.
//  **
//  ** make sure that if the drivdata exists, that it is the size
//  ** we think it should be.
//  */
//  if (pdop->pdriv != NULL)
//  {
//    /*
//    ** This check is being removed because it turns out that the Queue Processor
//    ** calls us in such a way that testing th cb would cause an infinite loop
//    ** of RIP's, or if no debug kernal was installed, the job would get lost.
//    ** NOTE: If you uncomment this block make sure that bitmaps still print when
//    **       the user selects to print to a non-default printer, i.e. PostScript
//    **       is not the default printer.
//    ** DMM      if (pdop->pdriv->cb < sizeof(DRIVDATA) - 1)
//    ** DMM      if (pdop->pdriv->cb < sizeof(DRIVDATA) )
//    ** DMM      {
//    ** DMM          RIP( "prde_FillPdb: Invalid drivedata size." );
//    ** DMM          return((ULONG)ERROR_NEG );
//    ** DMM      }
//    */
//  }
//
//  /*
//  ** we will now make up our own drivdata.  the pcnfData is already
//  ** set up from the ReadProfile call done above.
//  */
//  lDrivData.cb = sizeof( LDRIVDATA );
//  lDrivData.lVersion = DRIVERSION;
//  szCopy( lDrivData.szDeviceName, pcnfData->szSegName,
//          sizeof( lDrivData.szDeviceName ) );
//  lDrivData.cnfData = *pcnfData;
//  plDriv = (PLDRIVDATA) &lDrivData;
//}
#endif


  /*
  ** Initialize the size and orientation of the canvas.
  */
  SetCanvasMetrics( &pdv->canvas, (PB *) apResources );
  pdesPpd = (PDESPPD) apResources[PPDRES];

  /* D74609
  ** Read in the postscript language level
  */
  pdv->usLanguageLevel = pdesPpd->desItems.usLanguageLevel;

  /*
  ** Read in the Initialization string used for mode switching to
  ** Postscript mode from current mode  :                           @1462
  */
  if (((k = pdesPpd->desItems.ofsInitString) != -1) &&   /* if a string
                                                            exists         */
     (pcnfData->uPrinterPropFlags & MODESWITCH))        /* And Auto Mode Switching    */
  {
    /*
    ** Get the length of the init string
    */
    /*
    **
    ** Strings are now NULL-terminated.
    */
    pdv->usInitLength = strlen( apResources[RESBUF] + k );

    CopyStr( pdv->szInitString, apResources[RESBUF] + k );
#if 0
//  pdv->usInitLength = *(apResources[RESBUF] + k );
//
//  for (j = 0; j < pdv->usInitLength; j++)
//  {
//    pdv->szInitString[j] = *(apResources[RESBUF] + k + j + 1 );
//  }
//
//  /*
//  ** add a terminating null to be safe
//  */
//  pdv->szInitString[j] = '\0';
#endif
  }
  else
  {
    pdv->usInitLength = 0;

    /*
    ** add a terminating null to be safe
    */
    pdv->szInitString[0] = '\0';
  }

  /*
  ** Read in the Termination string used for mode switching out of
  ** Postscript mode to default mode  :                             @1462
  */
  if ((k = pdesPpd->desItems.ofsTermString) != -1)   /* if a string exists  */
  {
    /*
    ** Get the length of the term string
    */
    /*
    **
    ** Strings are now NULL-terminated.
    */
    pdv->usTermLength = strlen( apResources[RESBUF] + k );

    CopyStr( pdv->szTermString, apResources[RESBUF] + k );
#if 0
//  pdv->usTermLength = *(apResources[RESBUF] + k );
//
//  for (j = 0; j < pdv->usTermLength; j++)
//  {
//    pdv->szTermString[j] = *(apResources[RESBUF] + k + j + 1 );
//  }
//
//  /*
//  ** add a terminating null to be safe
//  */
//  pdv->szTermString[j] = '\0';
#endif
  }
  else
  {
    pdv->usTermLength = 0;

    /*
    ** add a terminating null to be safe
    */
    pdv->szTermString[0] = '\0';
  }

  /*
  ***************************for od_mem kran******************
  */
  /*
  ** Dynamically unhook the functions for MEMORY DC
  */
  if (param2)
  {
    apfnDispatch = (PFNL *)param2;

    /*
    ** copy back the functions for MEMORY DC.
    */
    if (((PDENPARAMS) ((ULONG) pdop + sizeof( DEVOPENSTRUC )))->ulType ==
                      OD_MEMORY)
    {
      for (usFunN = 0 ; usFunN <= NGREMAX ; ++usFunN)
      {
        apfnDispatch[usFunN] = od_memory_vect[usFunN].pfnl;
      }

      /*            */
      if ( ULGreVersion >= RASTER_ENGINE_22 )
      {
        Hook22Calls( apfnDispatch );
      }
    }
  }

  /*
  ********************check above code again*****************
  */

  /*
  ** Allocate memory for fonts list and create one
  */

  /*
  ** Get the font count
  */
  if ( pcnfData->lFontCount == -1L )  /*            */
  {
    pcnfData->lFontCount = DefaultFontCount( pdesPpd );
  }
  pdv->lFontCount = pcnfData->lFontCount;


  uHWFonts = pdesPpd->desFonts.iFonts;  /*            */
  cFnt = uHWFonts;

  Regrec.pfn = (PFNEH)ExceptHandler;
  DosSetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD) &Regrec );
  ulException = setjmp( Regrec.jmp );
  if( ulException )
  {
    goto ReleaseSem;
  }

  /* Get the sem */
  DosRequestMutexSem( globals.hSem, SEM_INDEFINITE_WAIT );

  if ( pdv->lFontCount )  /* if fontcount isn't zero */
  {
    cFnt   += SFQueryFonts( pdv );     /* total fonts             */
  }

  if (cFnt > MAX_FONTS)                /* max amount we have room for       */
  {
    cFnt = MAX_FONTS;
  }
  pdv->cFonts = cFnt;                  /* save total fonts                  */

  if (!(pdv->paFonts = (PAFNT)GplMemoryAlloc( pdv->pDCHeap, cFnt*sizeof(FNT) )))
  {
    PrintLog( (PSZ) "prde_FillPdb: can't allocate Fonts Array\n" );
    return( (ULONG) ((long)ERROR_NEG) );
  }

  j = pdesPpd->desFonts.ofsFontnames;

  /*
  **
  ** Point to font table instead of allocating memory
  */
  /*
  ** Transfer the fonts name to DV structure .
  */
  for (i = 0; i < uHWFonts && i < cFnt; i++)
  {
    k = *((BYTE *)apResources[RESBUF] + j );

    (*pdv->paFonts)[i].pszFontName = FontResourceName[k];
    (*pdv->paFonts)[i].pszFullName = (PSZ) FontFullName[k];
    (*pdv->paFonts)[i].usResource  = k;

    j++;
  }

  /*
  ** Pass the number of HW fonts
  */
  if ( pdv->lFontCount )
  {
    FillSFonts( pdv, (PVOID) &(*pdv->paFonts)[i], cFnt-i, uHWFonts ); /*            */
  }

ReleaseSem:
  DosReleaseMutexSem( globals.hSem );
  DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&Regrec );

  /*
  ** copy various device property things.
  */
  pdv->sourcePaper = *(pcnfData->u.iv.pSourcePaper);  /* paper names for various trays */
  szCopy( (PSZ) pdv->szDefFont, (PSZ) pcnfData->szFontName,
          sizeof( pdv->szDefFont ) );
  pdv->effOutput = pcnfData->effOutput;  /* print effects structure           */
  pdv->jobProperties = pcnfData->jobProperties;  /* placement order           */
  pdv->iCntCopies = pcnfData->iCntCopies;  /* count of copies tobe printed    */
  pdv->iDestnType = pcnfData->iDestnType;  /* The Destination type            */
  pdv->shPageno = 1;                     /* starting page no                  */

  /*
  @904 SAVE THE INI KEY FOR LATER USE
  */
  szCopy( (PSZ) pdv->szKeyApp, (PSZ) pcnfData->szKeyApp,
          sizeof( pdv->szKeyApp ) );
  pdv->sDuplexMode = pcnfData->sDuplexMode;  /* DCR 1462                    */

  /*
  ** !!! what's this?
  */
  szCopy( (PSZ) pdv->szSegName, pcnfData->szSegName,
         sizeof( pdv->szSegName ) );

  /*
  ** we are passed in a DEVOPENSTRUC.  we must now save a local copy
  ** of it.  since the DEVOPENSTRUC is made up of pointers to strings,
  ** we must save the strings themselves, as we are not
  ** guaranteed the application will keep them around.  the pointers in the
  ** DEVOPENSTRUC must point to the local string.
  **
  ** copy the logical address passed in the DEVOPENSTRUC into
  ** dev_dop.logaddress, and make the dop pointer point to it.
  */
  if (pdop->pszLogAddress != NULL)
  {
    szCopy( (PSZ) pdv->dev_dop.LogAddress, (PSZ) pdop->pszLogAddress,
            sizeof( pdv->dev_dop.LogAddress ) );
    pdv->dop.pszLogAddress = (PSZ) pdv->dev_dop.LogAddress;
  }
  else
  {
    pdv->dop.pszLogAddress = NULL;
  }

  /*
  ** !!! clean this up.
  **
  ** if user specifies a filename thru dialog box it overrules anything else
  */
  if (pcnfData->szDestnFile[0] != '\0')
  {
    szCopy( (PSZ) pdv->szDestnFile, (PSZ) pcnfData->szDestnFile,
            sizeof( pdv->szDestnFile ) );
  }
  else if (pdop->pszLogAddress != NULL)
  {
    szCopy( (PSZ) pdv->szDestnFile, (PSZ) pdv->dop.pszLogAddress,
             sizeof( pdv->szDestnFile ) );
  }

  if (pdv->iDestnType == RAW || pdv->iDestnType == ENCAPS)
  {
    szCopy( (PSZ) pdv->dev_dop.LogAddress, (PSZ) pdv->szDestnFile,
            sizeof( pdv->dev_dop.LogAddress ) );
  }

  /*
  ** get the driver name and save it.
  */
  szCopy( (PSZ) pdv->dev_dop.DriverName, (PSZ) "PSCRIPT",
          sizeof( pdv->dev_dop.DriverName ) );
  pdv->dop.pszDriverName = (PSZ) pdv->dev_dop.DriverName;

  /*
  ** copy the drive data.  if the user passed in a NULL pointer to the
  ** drivdata, or incomplete drivdata, we have built our own, so just
  ** copy it through.
  */
  utl_memcopy( (PB) &pdv->dev_dop.DriveData, (PB) plDriv,
               sizeof( LDRIVDATA ) );
  pdv->dop.pdriv = (PDRIVDATA) &pdv->dev_dop.DriveData;

  /*
  ** copy the data type into the dev_dop.datatype and make the dop
  ** pointer point to it.
  */
  /*
  **            Since we only know 2 data types if it's not STD then it's RAW
  ** Note can only be STD if queued
  */
  xpdop = (xPDEVOPENSTRUC) pdop;
  if ( (pdop->pszDataType != NULL)                     &&
       szIsEqual( pdop->pszDataType, (PSZ)"PM_Q_STD" ) &&
       ( xpdop->ulType == OD_QUEUED )                  &&
       ( pdv->iDestnType == SYSTEM) )
  {
    szCopy( (PSZ)pdv->dev_dop.DataType, pdop->pszDataType,
            sizeof( pdv->dev_dop.DataType ) );
    pdv->usDataType = PM_Q_STD;
  }
  else
  {
    szCopy( (PSZ)pdv->dev_dop.DataType, (PSZ) "PM_Q_RAW",
            sizeof( pdv->dev_dop.DataType )  );
    pdv->usDataType = PM_Q_RAW;
  }

  /* @V3.0GAMMA1
  ** If user has gamma set up table
  ** and producing output ( going raw )
  ** The size is 256 colors by 3 (RGB)
  */
  if ( pdv->usDataType == PM_Q_RAW      &&
       pcnfData->lGammaValue != NO_GAMMA )
  {
    pdv->pbGammaTable = GplMemoryAlloc( pdv->pDCHeap, 3 * 256 );
    if ( GplGammaCreateTable( pdv->pbGammaTable, pcnfData->lGammaValue,
                           pcnfData->lGammaValue, pcnfData->lGammaValue, 0L ) )
    { /* Failure: free table, set ptr to zero */
      GplMemoryFree( pdv->pbGammaTable );
      pdv->pbGammaTable = 0;
    }
  }

  /*
  **
  ** Create the async output thread.
  */
  if (xpdop->ulType == OD_DIRECT || (xpdop->ulType == OD_QUEUED &&
      pdv->usDataType == PM_Q_RAW))
  {
    if ((i = (SHORT) GplThreadCreateInstance( pdv->pDCHeap, pcnfData->hmod,
                                              &pdv->hThread )) == FALSE)
    {
      pdv->hThread = 0;
      GplErrSetError( PMERR_CREATE_THREAD );
      return( ERROR_NEG );
    }
  }

  pdv->dop.pszDataType = (PSZ) pdv->dev_dop.DataType;

  /*
  ** copy the comments.  if the user has given us a pointer to comments,
  ** copy the comments into dev_dop.comment, and make the dop pointer
  ** point to it.  otherwise, set the dop pointer to NULL.
  */
  if (pdop->pszComment != NULL)
  {
    szCopy( (PSZ) pdv->dev_dop.Comment,
            (PSZ) pdop->pszComment,
            sizeof( pdv->dev_dop.Comment ) );
    pdv->dop.pszComment = (PSZ) pdv->dev_dop.Comment;
  }
  else
  {
    pdv->dop.pszComment = NULL;
  }

  /*
  ** copy the queueprocname.  if the user has given us a pointer to
  ** the queue proc name, copy the name into dev_dop.queueprocname,
  ** and make the dop pointer point to it.  otherwise, set the dop
  ** pointer to NULL.
  */
  if (pdop->pszQueueProcName != NULL)
  {
    szCopy( (PSZ) pdv->dev_dop.QueueProcName,
            (PSZ) pdop->pszQueueProcName,
            sizeof( pdv->dev_dop.QueueProcName ) );
    pdv->dop.pszQueueProcName = (PSZ) pdv->dev_dop.QueueProcName;
  }
  else
  {
    pdv->dop.pszQueueProcName = NULL;
  }

  /*
  ** copy the queueproc parameters.  if the user has given us a pointer to
  ** the queue proc params, copy the params into dev_dop.queueprocparams,
  ** and make the dop pointer point to it.  otherwise, set the dop pointer
  ** to NULL.
  */
  if (pdop->pszQueueProcParams != NULL)
  {
    szCopy( (PSZ) pdv->dev_dop.QueueProcParams, (PSZ) "COL=C ",
            sizeof( pdv->dev_dop.QueueProcParams ) );
    szCopy( (PSZ) (pdv->dev_dop.QueueProcParams + 6),
            (PSZ) pdop->pszQueueProcParams,
            (sizeof( pdv->dev_dop.QueueProcParams ) - 6) );
    pdv->dop.pszQueueProcParams = (PSZ) pdv->dev_dop.QueueProcParams;
  }
  else
  {
    pdv->dop.pszQueueProcParams = NULL;
  }

  /*
  ** copy the spooler parameters.  if the user has given us a pointer to
  ** the spooler params, copy the params into dev_dop.spoolerparams, and
  ** make the dop pointer point to it.  otherwise, set the dop pointer
  ** to NULL.
  */
  if (pcnfData->jobProperties.szFormName[0] != '\0')
  {
    szCopy( (PSZ) pdv->dev_dop.SpoolerParams, (PSZ) "FORM=",
            sizeof( pdv->dev_dop.SpoolerParams ) );
    szCopy( (PSZ) (pdv->dev_dop.SpoolerParams + 5),
            (PSZ) pcnfData->jobProperties.szFormName,
            (sizeof( pdv->dev_dop.SpoolerParams ) - 5) );
    pdv->dop.pszSpoolerParams = (PSZ) pdv->dev_dop.SpoolerParams;
  }
  else
  {
    pdv->dop.pszSpoolerParams = NULL;
  }

  /*
  ** copy the network parameters.  if the user has given us a pointer to
  ** the network params, copy the params into dev_dop.networkparams, and
  ** make the dop pointer point to it.  otherwise, set the dop pointer
  ** to NULL.
  */
  if (pdop->pszNetworkParams != NULL)
  {
    szCopy( (PSZ) pdv->dev_dop.NetworkParams,
            (PSZ) pdop->pszNetworkParams,
            sizeof( pdv->dev_dop.NetworkParams ) );
    pdv->dop.pszNetworkParams = (PSZ) pdv->dev_dop.NetworkParams;
  }
  else
  {
    pdv->dop.pszNetworkParams = NULL;
  }
  PrintLog( (PSZ) "pdv = %lp\n", pdv );
  GplMemoryFree( pcnfData->u.iv.pSourcePaper );
  FreeMemory( (PB *) apResources );
  GplMemoryFree( (PB) apResources );
  return( (ULONG) pdv );
}

#if 0
///*
//**
//** This function is not called so don't compile but keep for record
//**
//*/
///***************************************************************************
// *
// * FUNCTION NAME = GetFirstPrinterMatch
// *
// *
// * DESCRIPTION   = Obtains the printer information from PM_SPOOLER_DD
// *                 for the first PSCRIPT. printer it finds. The name of the
// *                 printer is returned less the PSCRIPT. prefix.
// *
// *
// * Input argument :
// *
// * INPUT         = PSZ     pszDestPrinterInfo
// *                 SHORT  usDestBufSize
// *
// *
// * OUTPUT        = NONE
// *
// *
// * RETURN-NORMAL = NONE
// *
// * RETURN-ERROR  = NONE
// *
// **************************************************************************/
//
//VOID GetFirstPrinterMatch(PSZ pszDestPrinterInfo,SHORT usDestBufSize)
//{
//  CHAR  chEnumBuffer[255];             /* all printer info's                */
//  CHAR  chDefaultPrinterInfo[255];     /* default printer info              */
//  CHAR  chPrinterBuffer[9];            /* PSCRIPT. plus eos byte            */
//  SHORT iScan;                         /* index used to look at enum buffer */
//  PSZ   pszGenericString;              /* Generic Printer name from string
//                                          table                             */
//
//  /*
//  ** build a default name in case the ini file is blank
//  */
//  szCopy( (PSZ) chDefaultPrinterInfo, (PSZ) "PSCRIPT.",
//          (SHORT) 9 );
//  pszGenericString = (PSZ) GetR3String(IDS_Generic );
//  szCopy( (PSZ) &chDefaultPrinterInfo[8], (PSZ) pszGenericString,
//          (SHORT) (sizeof(chDefaultPrinterInfo) - 8) );
//
//  /*
//  ** we will enermerate PM_SPOOLER_DD since all printers are listed here
//  ** even if they are not associated with a queue - this is to support a
//  ** system that could be generating postscript output via file, but not
//  ** printing.
//  */
//  PrfQueryProfileString( (HINI) HINI_SYSTEMPROFILE, (PVOID) "PM_SPOOLER_DD",
//                         (PVOID) NULL, (PVOID) chDefaultPrinterInfo,
//                         (PVOID) chEnumBuffer, (ULONG) sizeof(chEnumBuffer) );
//  iScan = 0;
//
//  while (iScan < sizeof(chEnumBuffer) && chEnumBuffer[iScan] != '\0')
//  {
//    /*
//    ** &chEnumBuffer[iScan] is a pointer to a description in the enum buf
//    */
//    /* now check the printer name to make sure it's PSCRIPT
//    */
//    szCopy( (PSZ) chPrinterBuffer,
//           (PSZ) &chEnumBuffer[iScan],
//           sizeof(chPrinterBuffer) );
//
//    if (szIsEqual((PSZ) "PSCRIPT.", (PSZ) chPrinterBuffer))
//    {
//      /*
//      ** we have the first PSCRIPT match so lets use it
//      */
//      szCopy( (PSZ) pszDestPrinterInfo, (PSZ) &chEnumBuffer[iScan+8],
//              usDestBufSize );
//        return ;
//    }
//
//    /*
//    ** skip the current enumerated name and come to next name
//    */
//    while (chEnumBuffer[iScan] != '\0')
//    {
//      iScan++;
//    }
//    iScan++;                           /* skip the null also.Another null
//                                          means enumeration over            */
//  }
//  RIP( "GetFirstPrinterMatch could not match a name" );
//}
//
///*
//** End of not compiled
//*/
#endif

/*
** !!!CR there is no code to check the graphics engine version number
** !!!So,what 's it supposed to check for?
*/

/***************************************************************************
 *
 * FUNCTION NAME = prde_FillLdb  (Enable Subfunction 1)
 *
 *
 * DESCRIPTION : Sets up the dispatch table for the graphics engine, calls
 *               display.dll to get a dispatch table for its entry points.
 *               Sets the return flags to tell the engine that:
 *
 *                   Bit 0 == 1   Each DC requires its own physical device
 *                                block.
 *
 *                    Bit 1 == 0   Multiple DC's may coexist. **Bit2 == 0
 * Device/File names in the DevOpenStruc *are significant. **Note:When we load
 * the display driver we call it as
 *
 * if  we *were the graphics engine module. This kind of *charade continues
 * while  we're using the display
 *                     driver as our imaging engine.  The display driver
 *                     sees the DC as a memory DC while the engine sees
 *                     it as a printer DC.
 *
 *  Comments:
 *
 *  The initialization process is serialized with semaphores
 *  because it is possible for more than one process to have done
 *  an OpenDC at the same time.
 *
 *  But first we must put it on the ExitList, because when any
 *  process grabs a semaphore we must be able to free the semaphore
 *  if the process dies unexpectedly
 *
 *
 * INPUT         = FLParamsLst    *param1
 *                 FLReturnsLst   *param2
 *
 *
 * OUTPUT        = NONE
 *
 *
 * RETURN-NORMAL = SUCCESS
 *
 * RETURN-ERROR  = FAILURE
 *
 **************************************************************************/

#define EXLST_PRTY_UNLOCK       0xFD00   /* Unlock DLL: free semaphores, etc */

SHORT prde_FillLdb( FLParamsLst  *param1, FLReturnsLst *param2 )
{
  static BOOL   fGotPibTib  = FALSE;
  SHORT  usRetCode;

  RIPIF( fRipOnEnter,"###### Entering Fill Log Dev Blk ######" );

  #if DEBUG
      LogCall("prde_FillLdb(%lp, %lp)", ((PB) &param1) + sizeof(param1) );
  #endif

  DosExitList( EXLST_ADD | EXLST_PRTY_UNLOCK, (PFNEXITLIST) Exitpdd );

#if 0
//  usRetCode = DosExitList(EXLST_ADD | EXLST_PRTY_UNLOCK, (PFNEXITLIST)Exitpdd );
//  if (usRetCode)
//  {
//    #if DEBUG
//        if ( usRetCode == ERROR_INVALID_DATA )
//            RIP( "INVALID_DATA for ExitList" );
//        else
//            RIP( "INSUFFICIENT_MEMORY for ExitList" );
//    #endif
//    GplErrSetError (  PMERR_INSUFFICIENT_MEMORY) ;
//    return((SHORT) FAILURE );
//  }
#endif

  DosRequestMutexSem( tsemDriver, SEM_INDEFINITE_WAIT );

  /*
  ** create the driver's global heap and initialize some stuff.
  ** this heap is for  constant driver wide stuff,ie resource
  ** string tables,etc. there will be a separate heap created
  ** for  each DDC opened. These DDC specific heaps will be
  ** destroyed when each DDC is closed. this global heap will
  ** be created when the first DDC is opened ,and will remain
  ** in place,as the driver will remain in memory until the
  ** cows come home.
  */
/*InitGlobalHeap( );*/

  /*
  ** set a flag which means that the logical device block has been
  ** established.  this flag is used in InitGlobal and KillGlobalHeap.
  ** it is used so that we don't try to create the heap when it exists,
  ** or deleted it once the logical device block exists.
  */
  SetLDBFlag( );

  if (!fGotPibTib)
  {
    if (param1->ulVersion > 0x120L)
    {
      aAER = param2->aAER;
    }

    /*
    ** if (DosGetInfoBlocks(&ptib, &ppib))
    ** {
    **   RIP( "prde_FillLdb: DosGetInfoBlocks failed" );
    **   GplErrSetError(PMERR_BASE_ERROR );
    **   goto Failure;
    ** }
    ** else
    */

    fGotPibTib = TRUE;
  }

  /*
  **  Install ptrs our functions in engine's dispatch tbl.
  */
  usRetCode = InstallDispatchVectors( param2->apfn,param1 );

  if (usRetCode == FAILURE)
  {
    RIP( "FillLdb: InstallDispatchVectors failed" );
    goto Failure;
  }

  /*
  ** Set the flags to show that we want:
  **   bit 0 = 1 => each DC requires its own pDeviceBlock (Pdb)
  **   bit 1 = 0 => an arbitary number of DCs can co-exist
  **   bit 2 = 0 => the device and file name fields of OpenDevDc
  **                are significant, and should not be ignored.
  ** All other bits are reserved and should not be modified.
  */
  *(param2->pFlags) &= 0xFFFFFFF8L;
  *(param2->pFlags) |= 0x00000081L;
  usRetCode = (SHORT) SUCCESS;

CleanUpAndExit:

  DosReleaseMutexSem( tsemDriver );
  return( usRetCode );

Failure:

  usRetCode = (SHORT)
  FAILURE;
  goto CleanUpAndExit;
}

#if 0
///***************************************************************************
// *
// * FUNCTION NAME = CleanUpDDC
// *
// *
// * DESCRIPTION : This function is applied to each of the existing DDC map
// *               entries at exit list time (See the ExitDriver function
// *               below).  For those entries owned by the current process
// *               it cleanses the corresponding DDC entry for subsequent
// *               exit list processing by the Graphic Engine.
// *
// * INPUT         = PDDCENTRY  ptagDDCMap
// *                 LHANDLE    lh
// *
// * OUTPUT        = NONE
// *
// * RETURN-NORMAL = NONE
// *
// * RETURN-ERROR  = NONE
// *
// **************************************************************************/
//
//VOID CleanUpDDC( PDDCENTRY ptagDDCMap, LHANDLE lh )
//{
//  PDDC pDDC;
//
//  /*
//  **!!!    if (ptagDDCMap->pidOwner != (SHORT)  (ULONG)  lh) return;
//  ** !!!
//  **  !!!  pDDC= ptagDDCMap->pddc;
//  */
//  /*
//  ** When a crash occurs during the processing for a document, we treat it
//  ** like a DEVESC_ABORTDOC action.  The only difference is that the output
//  ** thread doesn't exist anymore.
//  **
//  **    Larry!!!  What do I do here?
//  **
//  **    pDDC->PDBI.Output.fConditions    = OUTPUT_ABORTED | DISCARD_OUTPUT;
//  **    pDDC->PDBI.Output.selBufferFirst = 0;
//  **    pDDC->PDBI.Output.selBufferLast  = 0;
//  */
//}
#endif

/***************************************************************************
 *
 * FUNCTION NAME = prde_DisablePdb
 *
 * DESCRIPTION : Disable pDeviceBlock subfunction
 *
 * INPUT         = PDV   pdv  Pointer to the driver's device structure
 *                 PB    Param2
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

SHORT prde_DisablePdb( PDV pdv, PB param2 )
{
  PFNT  pFnt;
  INT   i;
  PCACHEDFONT pCachedFont;
  PVOID pDCHeap;

  #if      DEBUG
    LogCall( "prde_DisablePdb(%lp, %lp)", ((PB)&pdv) + sizeof(pdv) );
    CloseLogFile( pdv );
  #endif

  /*
  ** Free the memory allocated to the device.
  */
  if (pdv)
  {
    if (pdv->hThread)
    {
      GplThreadDeleteInstance( &pdv->hThread );

      /*
      ** @V3.0100101
      ** Zero-out the thread handle.
      */
      pdv->hThread = (HTHREAD) 0;
    }

    /*
    ** these pointers were allocated at the time of opening a dc so
    ** release the allocated memory now.
    */
    for (i = 0; i < pdv->cFonts; i++)
    {                                  /* free softfont string entries      */
      pFnt = &(*pdv->paFonts)[i];

      if ((SHORT)pFnt->usResource <= 0) /* softfont                               */
      {
        /*
        ** I'm counting on the global heap having been created
        ** HM_MOVEABLE, so I don't need to tell it the size of
        ** the memory to free
        */
        if (pFnt->pszFullName)
        {
          GplMemoryFree( pFnt->pszFullName );
        }
      }
    }
    GplMemoryFree( (PB) pdv->paFonts );

    pCachedFont = pdv->FontCache;
    for ( i = 0; i < NUMCACHEDFONTS; i++ )
    {
      if ( pCachedFont->pFontDef )
      {
        GplMemoryFree( pCachedFont->pFontDef );
      }
      pCachedFont++;
    }

    /* @V3.0GAMMA1
    ** Free Gamma table if allocated
    */
    if ( pdv->pbGammaTable )
    {
      GplMemoryFree( pdv->pbGammaTable );
    }

    GplMemoryFree( (PB) pdv->cn.abBuf );
    pDCHeap = pdv->pDCHeap;
    GplMemoryFree( pdv );
    KillHeap( pDCHeap );
  }
  return( SUCCESS );
}

/***************************************************************************
 *
 * FUNCTION NAME = Exitpdd
 *
 * DESCRIPTION :
 *
 * This is where processes go when they die.
 *
 * The usTermCode parameter specifies the reason the process
 * ended. This parameter can be one of the following values:
 *
 * Value           Meaning
 * ----------------------------------------------
 * TC_EXIT         Normal exit
 * TC_HARDERROR    Hard-error abort
 * TC_KILLPROCESS  Unintercepted DosKillProcess
 * TC_TRAP         Trap operation
 *
 * The important reason for this routine is that the process may have died
 * unexpectedly, such as by a GP fault.  If the process is holding a
 * semaphore then we must release it now so that the death of this process
 * does not cause the whole system to freeze while waiting on a semaphore
 * that will never be freed.
 *
 * INPUT         = SHORT usTremCode
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = Does not return.  See the documentation for DosExitList.
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID Exitpdd( SHORT usTermCode )
{
  RIPIF( fRipOnEnter, "###### Entering Exitpdd ######" );

#if 0
//  if (hmodDisplay)
//  {
//    /*
//    ** make sure they do it more than once!
//    */
//    DosFreeModule( hmodDisplay );
//  }
#endif

  /*
  ** Now we release any global semaphores owned by the dead process.
  */
  DosReleaseMutexSem( tsemDriver );
  DosReleaseMutexSem( tsemLPT1 );
  DosReleaseMutexSem( tsemLPT2 );
  DosReleaseMutexSem( tsemLPT3 );
  DosReleaseMutexSem( tsemCOM1 );
  DosReleaseMutexSem( tsemCOM2 );
  DosReleaseMutexSem( tsemCOM3 );

  DosCloseMutexSem( tsemDriver );
  DosCloseMutexSem( tsemLPT1 );
  DosCloseMutexSem( tsemLPT2 );
  DosCloseMutexSem( tsemLPT3 );
  DosCloseMutexSem( tsemCOM1 );
  DosCloseMutexSem( tsemCOM2 );
  DosCloseMutexSem( tsemCOM3 );

  DosExitList( EXLST_EXIT, NULL );
}

/***************************************************************************
 *
 * FUNCTION NAME = ValidateDriveData
 *
 * DESCRIPTION :This routine does some checking to make sure we are passed in
 *              resonable drive data.  We don't want to be blown out of the
 *              water because some app passes us garbage.  This routine returns
 *              TRUE if the drivedata is valid, otherwise it returns FALSE.
 *
 * INPUT         = PLDRIVDATA  pdriv
 *                 PCNFDATA    pcnfData
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = TRUE or FALSE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

BOOL ValidateDriveData( PLDRIVDATA pdriv, PCNFDATA pcnfData )
{
 #define  LDRIVDATA12_SIZE (sizeof(LDRIVDATA) - 4)
  BOOL bUpdateDrivData;

  /*
  ** it is ok if the application has passed us no driver data.
  ** in this case, we will use defaults.
  */
  if (pdriv == NULL)
  {
    return( TRUE );
  }

  /*
  ** we have been passed a pointer to driver data.  let's check
  ** some things and make sure it is reasonable.
  **
  ** Since we have not changed the first elements of the structures
  ** LDRIVDATA/CNFDATA in 1.3, we can access them safely even if they
  ** are 1.2 structures.
  */
  /*
  **  if (pdriv->cb < sizeof(LDRIVDATA)  Don't check size - will cause error in
  **   && pdriv->cb != LDRIVDATA12_SIZE) latter drivers
  **      goto vdd_error;
  */

  /*
  ** This section supplies defaults for older drivdata
  ** Note - the print manager will give the right size of drivedata even for
  ** older printers.
  ** If the space is available update drivdata as well as cnfdata
  **
  */

  bUpdateDrivData = ( pdriv->cb == sizeof(LDRIVDATA) );

  switch ( pdriv->lVersion )
  {
    case DRIVERSION_1_3:
    case DRIVERSION_1_31:
      pcnfData->sUseDLFonts = 1;
      pcnfData->usPSLevel1 = 0;
      pcnfData->lGammaValue = NO_GAMMA;

      if ( bUpdateDrivData )
      {
        pdriv->cnfData.sUseDLFonts = 1;
        pdriv->cnfData.usPSLevel1 = 0;
        pdriv->cnfData.lGammaValue = NO_GAMMA;
      }
    case DRIVERSION_2_11:

      /* fill in here for things added after next version */

    default:
      break;
  }


  if ((pdriv->cnfData.effOutput.fIsFliptb != TRUE) &&
     (pdriv->cnfData.effOutput.fIsFliptb != FALSE))
  {
    goto vdd_error;
  }

  if ((pdriv->cnfData.effOutput.fIsDrawInverted != TRUE) &&
     (pdriv->cnfData.effOutput.fIsDrawInverted != FALSE))
  {
    goto vdd_error;
  }

  if ((pdriv->cnfData.effOutput.fIsFliplr != TRUE) &&
     (pdriv->cnfData.effOutput.fIsFliplr != FALSE))
  {
    goto vdd_error;
  }

  if ((pdriv->cnfData.effOutput.iJobTimeout < 0) ||
     (pdriv->cnfData.effOutput.iWaitTimeout < 0))
  {
    goto vdd_error;
  }

  if ((pdriv->cnfData.jobProperties.iOrient != PORTRAIT) &&
     (pdriv->cnfData.jobProperties.iOrient != LANDSCAPE))
  {
    goto vdd_error;
  }

  if ((pdriv->cnfData.jobProperties.fIsColorDevice != TRUE) &&
     (pdriv->cnfData.jobProperties.fIsColorDevice != FALSE) &&
     (pdriv->cnfData.jobProperties.fIsColorDevice != NONE))
  {
    goto vdd_error;
  }


  if (pdriv->cnfData.jobProperties.uScale <= 0)
  {
    goto vdd_error;
  }

  if ((pdriv->cnfData.jobProperties.iManualfeed < -1) ||
     (pdriv->cnfData.jobProperties.iManualfeed > 1))
  {
    goto vdd_error;
  }

  if ((pdriv->cnfData.iDestnType < SYSTEM) || (pdriv->cnfData.iDestnType >
     ENCAPS))
  {
    goto vdd_error;
  }

  if (pdriv->cnfData.iCntCopies <= 0)
  {
    goto vdd_error;
  }

  /*
  ** Added by DCR OSDD.1462
  */
  /*
  ** Driver knows about this
  */
  if (pdriv->lVersion > DRIVERSION_1_3)
  {
    if ((pdriv->cnfData.sDuplexMode < DUPLEX_NONE) ||
       (pdriv->cnfData.sDuplexMode > DUPLEX_DUPLEXTUMBLE))
    {
      goto vdd_error;
    }
  }
  else                                 /* Fill in defaults                  */
  {
    pcnfData->sDuplexMode = DUPLEX_NONE;
  }

  /*
  ** Make sure res is OK this will ge negs too
  */
  if ( pdriv->cnfData.uResolution > 8000 )
  {
    pcnfData->uResolution = 0;   /* indicates error */
  }

  /*
  ** Values are 0 or 1
  */
  if ( (USHORT)pdriv->cnfData.sUseDLFonts > 1 )
  {
    pcnfData->sUseDLFonts = 1;
  }

  /* D74609
  ** Values are 0 or 1
  */
  if ( pdriv->cnfData.usPSLevel1 > 1 )
  {
    pcnfData->usPSLevel1 = 0;
  }

  /*@V3.0GAMMA1
  ** Gamma must be between min and max
  */
  if ( pdriv->cnfData.lGammaValue > MAX_GAMMA ||
       pdriv->cnfData.lGammaValue < MIN_GAMMA  )
  {
    pdriv->cnfData.lGammaValue = NO_GAMMA;
  }

  /*
  ** the drive data looks good for now.
  */
  return( TRUE );

vdd_error:

  RIP( "ValidateDriveData:  Invalid DriveData." );
  GplErrSetError(  PMERR_INV_DRIVER_DATA );
  return( FALSE );
}

/***************************************************************************
 *
 * FUNCTION NAME = LoadProfile
 *
 *
 * DESCRIPTION : loads the configuration data from os2sys.ini.
 *
 *
 *
 * INPUT         = PB  *apResources
 *
 *
 * OUTPUT        = NONE
 *
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID LoadProfile( PB *apResources )
{
  ReadProfile( apResources );
  return ;
}

#if 0
///***************************************************************************
// *
// * FUNCTION NAME = CheckSegName
// *
// * DESCRIPTION : checks the szSegName in the global configuration data.  if it
// *               is NULL, it sets it to the default value.
// *
// * INPUT         = PCNFDATA    pcnfData
// *                 PB      *apResources
// *
// * OUTPUT        = NONE
// *
// * RETURN-NORMAL = NONE
// *
// * RETURN-ERROR  = NONE
// *
// **************************************************************************/
//
//VOID CheckSegName( PCNFDATA pcnfData, PB *apResources )
//{
//  if (pcnfData->szSegName[0] == '\0')
//  {
//    szCopy( (PSZ) pcnfData->szSegName, (PSZ) apResources[SGNRES],
//            sizeof( pcnfData->szSegName ) );
//  return ;
//}
#endif



/***************************************************************************
 *
 * FUNCTION NAME = ProfileAllocStringQuery
 *
 * DESCRIPTION   = This function queries the size, in bytes, a buffer is
 *  needed for a string from the OS2.INI file.  It then allocates the memory
 *  and copies the profile string to the buffer.  This is used to retrieve
 *  profile strings of variable sizes.
 *  This function frees the profile string buffer if an error occurs, but it
 *  is the programmer's responsibility to free the buffer if this function
 *  returns successful.
 *  This function uses GplMemoryAlloc() to allocate memory to the buffer, so
 *  GplMemoryFree() should be used to free memory.
 *
 * INPUT         = HINI hIni - Initialization file handle.
 *                 PSZ pszAppName - String containing application name to be
 *                   queried.
 *                 PSZ pszKeyName - String containing key name that is to be
 *                   queried.
 *                 PSZ pszDefaultName - String that contains a default profile
 *                   name.  This name is provided to the return buffer if the
 *                   string being searched for cannot be found.
 *                   ** IMPORTANT** If no default string is desired, this
 *                   should be set to NULL.
 *
 * OUTPUT        = PPVOID ppBuffer - Double pointer.  The buffer containing
 *                   the profile string is allocated and its address is stored
 *                   here and returned to the calling function.
 *
 * RETURN-NORMAL = Non-0 - Number of characters in buffer.
 * RETURN-ERROR  = 0 - Error in memory allocation, profile search failed, or
 *                 simply no strings were found.
 *
 ****************************************************************************/
LONG ProfileAllocStringQuery( HINI hIni, PSZ pszAppName, PSZ pszKeyName,
                              PSZ pszDefaultName, PPVOID ppBuffer )
{
  ULONG ulDataLength;       /* Profile string length, in characters. */
  LONG ulNumOfStrChars = 0; /* Return values for this function.      */

  /*
  ** Initially, set the double-pointer to NULL.
  */
  *ppBuffer = NULL;

  /*
  ** Retrieve the memory size needed for the specific key name.
  ** PrfQueryProfileSize returns either TRUE or FALSE.
  */
  if (PrfQueryProfileSize( hIni, pszAppName, pszKeyName,
                           &ulDataLength ) == TRUE)
  {
    /*
    ** Allocate memory where the profile string will be stored.  The reason
    ** why 1 is added to ulDataLength is to take the NULL character into
    ** consideration.
    */
    if (*ppBuffer = (PVOID) GplMemoryAlloc( pProcessHeap, ulDataLength + 1 ))
    {
      /*
      ** Retrieve the needed string from the OS2.INI file.
      ** PrfQueryProfileString returns the number of characters returned in
      ** the string.  Therefore, retrieve the number of characters returned.
      ** This value will be returned by the function.
      ** If 0 is returned, it means that there is no string for the buffer.
      ** Therefore, free the buffer resserved for ppBuffer and set it to NULL.
      */
      if ((ulNumOfStrChars = PrfQueryProfileString( hIni, pszAppName, pszKeyName,
                                                    pszDefaultName, (PSZ) *ppBuffer,
                                                    ulDataLength)) == 0)
      {
        GplMemoryFree( *ppBuffer );
        *ppBuffer = NULL;
      }
    }
  }

  return( ulNumOfStrChars );
}
