/*DDK*************************************************************************/
/*                                                                           */
/* 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.                                */
/*                                                                           */
/*****************************************************************************/
#pragma  pagesize(55)

/**************************************************************************
 *
 * SOURCE FILE NAME = ESCQUERY.C
 *
 * DESCRIPTIVE NAME = PLOTTER DRIVER SOURCE
 *
 *
 * VERSION = V2.0
 *
 * DATE        09/18/88
 *
 * DESCRIPTION PLOTTER DRIVER SOURCE FILE-This file contains the functions
 *             for exported entry points DevEscape, QueryDeviceCaps,
 *             QueryDeviceNames, and QueryHardcopyCaps.
 *
 *
 * FUNCTIONS   GetPageData          Get info on physical page size
 *             Escape()             Provide device specific interface
 *                                  to the driver
 *
 *             QueryDeviceBitmaps() Return bitmap formats supported
 *             QueryDeviceCaps      Returns information about the device
 *                                  capabilities
 *
 *             QueryDeviceNames()   Returns information about the device names
 *             QueryDeviceResource  load all pointers
 *             QueryHardcopyCaps()  hardcopy information return
 *
 *
 *
 *
 *
 *
 * NOTES
 *
 *
 * STRUCTURES
 *
 * EXTERNAL REFERENCES
 *
 * EXTERNAL FUNCTIONS
 *
*/
#define  INCL_DEV                      /* Include Device stuff              */
#define  INCL_SPL                      /* Spooler stuff                     */
#define  INCL_DOSMODULEMGR             /* DosLoadModule prototype           */
#define  INCL_SPLFSE                   /* File System Interface             */
#define  INCL_WINWINDOWMGR
#define  INCL_DOSEXCEPTIONS            /*  Exception Management Support     */

#define  INCL_GENPLIB_THREAD           /* genplib thread calls              */
#define  INCL_GENPLIB_HANDLER          /* exception handler                 */

#include "plotters.h"
#include "color.h"
#include "dialog.h"
#include "dosio.h"
#include "error.h"
#include "escquery.h"
#include "output.h"
#include "utils.h"
#include "attribs.h"
#include "profile.h"
#include "devmode.h"
#include "forms.h"

/*
** additional C includes
*/
#include <setjmp.h>


LOCAL VOID   GetPageData(PPDEVICE,SHORT,PHCINFO);
LOCAL USHORT number_of_hardcoded_forms (USHORT Plotter);

/***************************************************************************
 *
 * FUNCTION NAME = GetPageData
 *
 *
 * DESCRIPTION   = Get info on physical page size
 *
 *                 Extract information about the physical page size.
 *                 pPDevice points to the relevant entry in the
 *                 PaperResTable and PrintOffTable for this
 *                 particular plotter.  Form is the number for the
 *                 particular paper * size of interest.  These range
 *                 from 0 to 4 for sizes A to E (imperial), 5 to 9
 *                 for A4 to A0 (metric) and 10 for 24in roll, 11 for
 *                 36in roll.
 *
 *
 *
 * INPUT         = (pPDevice,Form,pInfo)
 *
 *
 * OUTPUT        = NONE
 *
 *
 *
 *
 * RETURN-NORMAL = NONE
 *
 *
 *
 * RETURN-ERROR  = NONE
 *
 *
 *
 **************************************************************************/

LOCAL VOID  GetPageData(PPDEVICE pPDevice,SHORT Form,PHCINFO pInfo)
{

  /*
  ** Extract information about the physical page size. pPDevice
  ** points  to the relevant entry in the PaperResTable and
  ** PrintOffTable for this  particular plotter. Form is the
  ** number for the particular paper * size of interest. These
  ** range from 0 to 4 for sizes A to E (imperial),  5 to 9 for A4
  ** to A0 (metric) and 10 for 24in roll, 11 for 36in roll.
  */
  USHORT usOffIndex = PlotterClass[pPDevice->Plotter].PrintOffIndex;
  USHORT usRollLength;                 /* User supplied roll length in inchs*/

  /*
  ** First get the paper size in millimeters
  */
  pInfo->cx = PhysicalPageSize[Form].x;
  pInfo->cy = PhysicalPageSize[Form].y;

  /*
  ** Offset table stuff - discover the unusable sizes around the
  ** edge
  */
  if (pInfo->cy == 0)
  {
    /*
    ** Roll - so set it to the length specified in setup data
    */
    if ((1 << Form) == MEDSZ_R24)
      usRollLength = pPDevice->pSetup->Roll24;/* 24 inch roll               */
    else
      usRollLength = pPDevice->pSetup->Roll36;/* 36 inch roll               */

    pInfo->cy = (usRollLength * MMPER10INCH) / 10; /* Into mm              */

    /*
    ** The following deserves some explanation. When roll paper
    ** is in use, the user has the option of specifying how long
    ** the roll is. SO, static data is meaningless.   The
    ** formula takes the current length of the roll (calculated
    ** above) and removes the X and Y Offset data (all this is in
    ** mm) then converts to pels. The reason for the X AND Y
    ** offset removal is explained by the HP specifications for
    ** these plotters.  This specifies a particular clear zone at
    ** each end of the page, and these numbers just happen to be
    ** in the Offset table.
    **
    ** EricB - using X and Y offset is suspect
    */
    // caps width already includes the clips.
    //pInfo->yPels = (pInfo->cy - PrintOffTable[usOffIndex][Form].x -
    //                PrintOffTable [usOffIndex][Form].y) * STEPSPERMM;
  }

  /*
  ** Swap X and Y coordinates for landscape mode here.  Also
  ** calculate clip limits.
  */
  if (pPDevice->fsTransform & DXF_PORTRAIT)
  {
    /*
    ** Portrait mode: only do the clip limits here
    ** xRightClip, xLeftClip, yTopClip and yBottomClip in millimeters
    ** Note: X and Y hardware clip are swaped
    */
    pInfo->xRightClip = pInfo->cx-PrintOffTable[usOffIndex][Form].x;
    pInfo->xLeftClip = PrintOffTable[usOffIndex][Form].x;
    pInfo->yBottomClip = PrintOffTable[usOffIndex][Form].y;
    pInfo->yTopClip = pInfo->cy-PrintOffTable[usOffIndex][Form].y;
  }
  else
  {
    /*
    ** Swap the X, Y data - plotter is in Landscape mode
    ** Note: X and Y hardware clip are swaped
    */
    SWAPLONG(pInfo->cx, pInfo->cy);

    /*
    ** Also do the clip limit data
    */
    pInfo->xLeftClip = PrintOffTable[usOffIndex][Form].y;
    pInfo->yBottomClip = PrintOffTable[usOffIndex][Form].x;
    pInfo->xRightClip = pInfo->cx-PrintOffTable[usOffIndex][Form].y;
    pInfo->yTopClip = pInfo->cy-PrintOffTable[usOffIndex][Form].x;
  }

  /*
  ** get the number of pels between left and right clip limits
  */
  pInfo->xPels = round_divide((pInfo->xRightClip - pInfo->xLeftClip) *
                               pPDevice->lPltrRes_XPelsPerMeter, 1000);
  pInfo->yPels = round_divide((pInfo->yTopClip - pInfo->yBottomClip) *
                               pPDevice->lPltrRes_YPelsPerMeter, 1000);

  return ;
}

/***************************************************************************
 *
 * FUNCTION NAME = Escape()
 *
 *
 * DESCRIPTION   = Provide device specific interface to the driver
 *
 * INPUT         = hDC,EscCode,lInCount,pbInData,plOutCount,
 *                 pbOutData,pDDC,FunN)
 *
 * OUTPUT        = NONE
 *
 *
 *
 *
 * RETURN-NORMAL = DEV_OK                - All is well
 *
 *
 *
 * RETURN-ERROR  = DEVESC_ERROR          - An incorrect value was passed in
 *                 DEVESC_NOTIMPLEMENTED - Request is not implemented
 *
 *
 **************************************************************************/

LONG  Escape(HDC hDC,LONG EscCode,LONG lInCount,PBYTE pbInData,PLONG
                        plOutCount,PBYTE pbOutData,PDDC pDDC,ULONG FunN)

/*
**  HDC hDC;                              Handle to DC
**  LONG EscCode;                         Escape function
**  LONG lInCount;                        Number of bytes pointed to by
**                                        pbInData
**  PBYTE pbInData;                       Pointer to input parameters
**  PLONG plOutCount;                     Pointer to number bytes pointed
**                                        to by pbOutData
**  PBYTE pbOutData;                      Pointer to output returns
**  PDDC  pDDC;                           Pointer to cookie
**  ULONG FunN;                           Function number
*/
/*
**  This function allows applications to access facilities of a
**  particular device that are not directly available through the
**  GPI.  Escape calls are in general sent to the device driver
**  and must be understood by it. The following are predefined
**  escape function:
**
**      DEVESC_QUERYESCSUPPORT  0L
**      DEVESC_GETSCALINGFACTOR 1L
**      DEVESC_STARTDOC         8150L
**      DEVESC_ENDDOC           8151L
**      DEVESC_NEXTBAND         8152L
**      DEVESC_ABORTDOC         8153L
**      DEVESC_NEWFRAME         16300L
**      DEVESC_FLUSHOUTPUT      16302L
**      DEVESC_RAWDATA          16303L
**      DEVESC_STD_JOURNAL      32600L
**
**  In addition, devices may define additional escape functions,
**  using  codes > 32767.
*/

{
  PPDEVICE pPDevice = pDDC->pPDevice;
  LONG    lResult;
  USHORT  SplJob;                          /* Job ID from spooler      */
  LONG    lTemp;                           /* Miscellaneous temporary  */
  PSZ     pFrom;                           /* use                      */
  PSZ     pTo;
  LONG    cnt;
  REGREC  regrec;
  ULONG   ulException;

  /*
  ** Register an exception handler for the thread
  */
  REGISTERHANDLER( regrec, hModule );
  ulException = setjmp( regrec.jmp );
  if( ulException )
  {
    // clean up here
    switch( ulException )
    {
      case XCPT_PROCESS_TERMINATE:
      case XCPT_ASYNC_PROCESS_TERMINATE:
      LeaveDriver( pDDC );
      UNREGISTERHANDLER( regrec );
      DosExit( EXIT_THREAD, 0 );
    }
    // error result
    lResult = DEV_ERROR;
    goto EscapeExit;
  }

  /*
  ** we can not lock the driver for abortdoc because the spooler
  ** will call us to abort the job on another thread.
  */
  if (EscCode != DEVESC_ABORTDOC)
  {
    if (!EnterDriver(pDDC))
        return(GPI_ERROR);
  }
  /*
  **  In case of MEMORYDC Dispatch to Graphics Engine
  */
  if (pDDC->usDCType == OD_MEMORY)
  {
    lResult = InnerGreEscape (pDDC->hdcMemory, EscCode, lInCount,
                            pbInData, plOutCount, pbOutData, FunN );
    if (EscCode != DEVESC_ABORTDOC)
    {
      LeaveDriver( pDDC );
    }
    return( lResult );
  }

  if (EscCode < 0L || EscCode > 65535L)
  {
    /*
    ** Value is out of range
    */
    if (EscCode != DEVESC_ABORTDOC)
    {
      LeaveDriver( pDDC );
    }
    return  DEVESC_NOTIMPLEMENTED;
  }


  switch (LOUSHORT(EscCode))
  {
    case  DEVESC_QUERYESCSUPPORT :     /* Find what we accept               */
      if (lInCount == 2 || lInCount == 4)
      {
        /*
        ** A valid size, so determine the value they want
        */
        if (lInCount == 2)
          lTemp = *((PUSHORT)pbInData);
        else
          lTemp = *((PULONG)pbInData);

        if (lTemp == DEVESC_QUERYESCSUPPORT ||
        lTemp ==
           DEVESC_GETSCALINGFACTOR ||
           lTemp == DEVESC_STARTDOC ||
           lTemp == DEVESC_ENDDOC ||
           lTemp == DEVESC_NEXTBAND ||
           lTemp == DEVESC_ABORTDOC ||
           lTemp == DEVESC_NEWFRAME ||
           lTemp == DEVESC_FLUSHOUTPUT ||
           lTemp == DEVESC_RAWDATA)
        {
          lResult = DEV_OK;
        }
        else
          lResult = DEVESC_NOTIMPLEMENTED;
      }
      else
      {
        GplErrSetError(PMERR_INV_LENGTH_OR_COUNT);
        lResult = DEVESC_ERROR;
      }
      break;
    case  DEVESC_GETSCALINGFACTOR :    /* Graphics/text scaling factor      */
      if (*plOutCount >= 8)
      {
        /*
        ** Value is a power of 2, so 0 means scaling factor 1
        */
        *((PULONG)pbOutData) = 0L;
        *((PULONG)pbOutData+1) = 0L;
        *plOutCount = 8;
        lResult = DEV_OK;
      }
      else
      {
        GplErrSetError(PMERR_BUFFER_TOO_SMALL);
        lResult = DEVESC_ERROR;
      }
      break;
    case  DEVESC_STARTDOC :            /* Start of job                      */
      DBPRINTF(("DevEscape : DEVESC_STARTDOC\r\n"));
      lResult = DEV_OK;

      /*
      ** handle cleanup for multiple STARTDOCs.  MarkV
      */
      if (pDDC->usDCType == OD_QUEUED &&
          pPDevice->DataType == PM_Q_STD &&
          pPDevice->bSpoolStdStarted)
      {
        perform_std_spooling(pDDC, hDC);
      }

      if (pPDevice->bPageStarted)
        end_page(pDDC, hDC, TRUE, 0L);

      if (pPDevice->bDocStarted)
        end_doc(pDDC);

      if (pPDevice->bJobStarted)
        end_job(pDDC);

      pDDC->pPDevice->bEnableOutput = TRUE; /* Back on  */

      if (pDDC->usDCType == OD_QUEUED)
      {
        /*
        **  Handle multiple startdoc case.
        **  Free previous startdoc name before allocating a new one. MarkV
        */
        if (pPDevice->pszDocumentName)
        {
          if (GplMemoryFree (pPDevice->pszDocumentName))
            pPDevice->pszDocumentName = (PSZ)NULL;
        }
        /*
        **  Handle document name if one was passed in.
        */
        if (lInCount > 0 && pbInData && (pPDevice->pszDocumentName = (PSZ)
           GplMemoryAlloc(pPDevice->hmcbHeap, (lInCount+1))))
        {
          /*
          ** PTT showed that we allocated heap based on
          ** lInCount, but copied string until null encountered.
          ** In PTT's case, these did not match, and we were
          ** erasing important stuff on the heap.
          ** 11/06/91, SU, MGXFER
          */
          pTo = pPDevice->pszDocumentName;
          pFrom = (PSZ)pbInData;
          cnt = 0;

          while (*pFrom && (cnt < lInCount))
          {
            *pTo++ = *pFrom++;
            cnt++;
          }                            /* endwhile                          */
          *pTo = '\0';
        }

        if (pPDevice->DataType == PM_Q_STD)
        {
          /*
          ** Open the spooler
          */
          if (!pPDevice->bSpoolStdOpened)
          {
            if (SplStdOpen(hDC))
              pPDevice->bSpoolStdOpened = TRUE;
            else
              lResult = DEV_ERROR;
          }
          /*
          ** Start the metafile
          */
          if (!pPDevice->bSpoolStdStarted)
          {
            if (SplStdStart(hDC))
              pPDevice->bSpoolStdStarted = TRUE;
            else
              lResult = DEV_ERROR;
          }
        }
      }
      else
      {
        /*
        ** Must be the OD_DIRECT trip through the driver
        */
        if (pPDevice->DataType == PM_Q_STD && pPDevice->pSetup->bColorSort)
        {
          /*
          ** Start Journaling at startdoc time.
          ** Starting Journaling/Color sorting saves the DC state at
          ** this point. B735545
          */
          pPDevice->bColorSorting = TRUE;
          start_color_sorting(pDDC, hDC);
        }
      }

      /*
      ** if output is enabled and we are a raster device
      */
      if (pPDevice->bEnableOutput  &&
          pPDevice->pSetup->usFlags & FL_RASTER)
      {
        /*
        ** Initialize banding by calling start_page().
        */
        if (!pPDevice->bPageStarted)
        {
          start_page(pDDC);          // Initialize everything!
        }
      }
      break;
  case  DEVESC_ENDDOC :              /* End of document                   */
      DBPRINTF (("DevEscape:DEVESC_ENDDOC\r\n"));
      SplJob = 0;

      if (pDDC->usDCType == OD_QUEUED)
      {
        if (pPDevice->DataType == PM_Q_STD && pPDevice->bSpoolStdStarted)
          SplJob = perform_std_spooling(pDDC, hDC);
        else
        {
          if (pPDevice->DataType == PM_Q_RAW)
          {
            end_page(pDDC, hDC, FALSE, FunN);
            SplJob = end_doc(pDDC);
          }
        }
      }
      else
      {
        end_page(pDDC, hDC, FALSE, FunN);
        GplThreadFlushBuffer(pPDevice->hThread, TRUE);  // Flush and wait
      }

      /*
      ** if color sorting clean up journal stuff B735545
      */
      if (pDDC->usDCType == OD_DIRECT &&
          pPDevice->DataType == PM_Q_STD &&
          pPDevice->bColorSorting)
      {
        pPDevice->bColorSorting = FALSE;
      }

      /*
      ** Return ID information to user
      */
      if (plOutCount)
      {
        /*
        ** Somewhere to put the data?
        */
        if (pbOutData && *plOutCount >= 2)
        {
          *((PUSHORT)pbOutData) = SplJob;
          *plOutCount = 2L;
        }
        else
          *plOutCount = 0L;
      }
      if (pDDC->pPDevice->bEnableOutput == FALSE)
      {
        GplThreadResetAbortDoc(pPDevice->hThread);
        if (pPDevice->hJournalGpl)
           // Notify journalling that the abort doc condition is over
           GplJournalResetAbortDoc (pPDevice->hJournalGpl);
      }
      pDDC->pPDevice->bEnableOutput = TRUE; /* Back on           */
      lResult = DEV_OK;
      break;
    case  DEVESC_NEWFRAME :            /* Start a new frame (page)          */
      DBPRINTF(("DevEscape-DEVESC_NEWFRAME\\r\n"));

      if (pDDC->usDCType == OD_DIRECT ||
          (pDDC->usDCType == OD_QUEUED &&
           pPDevice->DataType == PM_Q_RAW))
      {
        end_page(pDDC, hDC, FALSE, FunN);
      }
      lResult = DEV_OK;
      break;
    case  DEVESC_NEXTBAND :            /* Another band of output starts     */
      DBPRINTF(("DevEscape-DEVESC_NEXTBAND\r\n"));
      lResult = DEVESC_ERROR;

      if (pDDC->usDCType == OD_DIRECT ||
          (pDDC->usDCType == OD_QUEUED &&
           pPDevice->DataType == PM_Q_RAW))
      {
        /*
        ** Valid data available?
        */
        if (*plOutCount >= sizeof(RECTL))
        {
          next_band(pDDC, hDC, (PRECTL)pbOutData, FunN);
          *plOutCount = sizeof(RECTL);
          lResult = DEV_OK;
        }
        else
        {
          GplErrSetError(PMERR_BUFFER_TOO_SMALL);
        }
      }
      break;
    case  DEVESC_ABORTDOC :            /* Trash whatever there is           */
      DBPRINTF(("DevEscape-DEVESC_ABORTDOC\r\n"));
      lResult = DEV_OK;

      /*
      ** turn off process drawing
      */
      // if we take out com_draw on abort doc
      // we must be prepared for another start doc
      //GreSetProcessControl(hDC, COM_DRAW, 0L);

      pPDevice->bEnableOutput = FALSE;

      if (pDDC->usDCType == OD_QUEUED)
      {
        if (pPDevice->DataType == PM_Q_STD && pPDevice->bSpoolStdStarted)
        {
          HSTD hJob;

          pPDevice->bSpoolStdStarted = FALSE;
          hJob = SplStdStop(hDC);
          SplStdDelete(hJob);          /* Delete our record of it           */
          pPDevice->bSpoolStdOpened = FALSE;
          SplStdClose(hDC);
        }
        DBPRINTF(("ABORTing from OD_QUEUED output threadr\r\n"));
        /*
        ** No need to send data to plotter, as queued mode
        ** does not actually generate any data!
        */
        if (pPDevice->hSpooler)
        {
          /*
          ** if we  have written anything...  MarkV fix for ed.exe and
          ** picview. removed code to open spooler if not open.  This
          ** code caused a 2104 error STARTDOC_NOT_ISSUED.
          */
          if (!SplQmAbortDoc(pPDevice->hSpooler))
          {
            lResult = DEVESC_ERROR;
          }
        }                              /* endif                             */
      }
      else
      {
        DBPRINTF(("ABORTing from OD_DIRECT output thread\r\n"));
        /*
        ** May be direct - different problem
        */
        if (pDDC->usDCType == OD_DIRECT)
        {
          /*
          ** PTR B727171 fix follows
          */
          if (pPDevice->bPageStarted)
          {
            DBPRINTF(("a page had started\r\n"));
            /*
            ** Thread Stuff
            ** Call thread code to set flag in outputinfo block
            ** to abort the job
            */
            GplThreadAbortDoc(pPDevice->hThread);

            DBPRINTF(("ENDDOC complete\r\n"));
          }                            /* endif page started                */
        }                              /* endif direct DC                   */
      }                                /* endif                             */
      if (pPDevice->hJournalGpl)
         // Notify journalling that an abort doc occured!
         GplJournalAbortDoc (pPDevice->hJournalGpl);

      /*
      ** made it to here, so Abort thread stuff is complete...
      */
      lResult = DEV_OK;
      break;

    case  DEVESC_FLUSHOUTPUT :
      lResult = (LONG)GplThreadFlushBuffer(pPDevice->hThread, FALSE);
      break;
    case  DEVESC_RAWDATA :
      DBPRINTF (("DevEscape-DEVESC_RAWDATA) : Count=%ld\n",lInCount));
      lResult = DEV_OK;
      pPDevice->bSomeRawData = TRUE;

      if (pDDC->usDCType == OD_DIRECT || (pDDC->usDCType == OD_QUEUED &&
         pPDevice->DataType == PM_Q_RAW))
      {
        /*
        ** Empty the local buffer of anything we accumulated,
        ** then do_write the data directly to the outside world.
        */
        if (pPDevice->bEnableOutput)
        {
          ULONG fulDataType;
          /*
          ** Output the data, but there may need to call
          ** start_page() to send any initialization data.
          */
          if (!pPDevice->bPageStarted)
          {
            start_page(pDDC);          // Initialize everything!
          }

          /*
          ** Move the data to our own output data buffer.
          ** The current output data buffer will be added to
          ** the output data queue when the buffer is full or
          ** an enddoc is processed.
          ** If the printer is binary capable assume raw data
          ** jobs may contain binary.
          */
          fulDataType =
            (PlotterClass[pDDC->pPDevice->Plotter].fsOdd & (OD_PCL | OD_HPRTL) ?
               THREAD_DT_BINARY :         /* Data is Binary Data(RASTER)      */
               THREAD_DT_PRINTERLANGUAGE);/* date is HPGL*/

          if (!GplThreadOutput(pDDC->pPDevice->hThread, pbInData,
               lInCount, fulDataType) )
          {
            /*
            ** Didn't do as expected - report an error
            */
            lResult = DEV_ERROR;
          }
        }
      }
      else
      {
        lResult = DEVESC_NOTIMPLEMENTED;
      }
      break;
    default  :
      lResult = DEVESC_NOTIMPLEMENTED;
      break;
  }
EscapeExit:
  if (EscCode != DEVESC_ABORTDOC)
  {
    LeaveDriver( pDDC );
  }
  UNREGISTERHANDLER( regrec );
  return  lResult;
}

/***************************************************************************
 *
 * FUNCTION NAME = QueryDeviceBitmaps()
 *
 *
 * DESCRIPTION   = Return bitmap formats supported
 *
 *                 This function returns a list of bitmap
 *                 formats supported by the device.  The
 *                 plotters driver supports no bitmaps, so this
 *                 function zeros out the return data area, as
 *                 required.
 *
 *
 * INPUT         = (hDC,pReturns,lCount,pDDC,FunN)
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = GPI_OK;
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

LONG  QueryDeviceBitmaps(HDC hDC,PULONG pulReturns,LONG lCount,PDDC
                                    pDDC,ULONG FunN)
{
  LONG i;
  ULONG ulRet;

  if (!EnterDriver(pDDC))
      return(GPI_ERROR);

  /**************************************************************************
  **          In case of MEMORYDC Dispatch to Graphics Engine              **
  **************************************************************************/
  if (pDDC->usDCType == OD_MEMORY)
  {
    ulRet = InnerGreQueryDeviceBitmaps (pDDC->hdcMemory, pulReturns,
                                        lCount, FunN);
    LeaveDriver( pDDC );
    assert(ulRet != FALSE);
    return(ulRet);
  }
  /*
  ** Validate the count
  */
  if (lCount < 2)
  {
      GplErrSetError( PMERR_INV_LENGTH_OR_COUNT);
      DBPRINTF(("QueryDeviceBitmaps: invalid number of entries\n"));
      LeaveDriver(pDDC);
      return(GPI_ERROR);
  }
  else
  {
    for (i = 0; i < lCount / 2; i++)
    {
      switch ((SHORT)i)
      {
        case 0 :
          *(pulReturns++) = 1L;            /* 1 Plane */
          *(pulReturns++) = 8L;            /* 8 bits per pel */
          break;
        case 1 :
          *(pulReturns++) = 1L;            /* 1 Plane */
          *(pulReturns++) = 4L;            /* 4 bits per pel */
          break;
        case 2 :
          *(pulReturns++) = 1L;            /* 1 Plane */
          *(pulReturns++) = 24L;           /* 24 bits per pel */
          break;
        case 3 :
          *(pulReturns++) = 1L;            /* 1 Plane */
          *(pulReturns++) = 1L;            /* 1 bits per pel */
          break;
        default:
          *(pulReturns++) = 0L;            /* NULL Plane */
          *(pulReturns++) = 0L;            /* NULL bits per pel */
      }
    }
  }

  LeaveDriver(pDDC);
  return  GPI_OK;
}

/***************************************************************************
 *
 * FUNCTION NAME = QueryDeviceCaps
 *
 *
 * DESCRIPTION   = Returns information about the device capabilities
 *
 *                 Information is returned to the caller in pData.
 *                 lCount items are returned, starting with value
 *                 Index.  Values which are transform sensitive are
 *                 returned in DEVICE UNITS.  This includes values
 *                 like the character size.
 *
 *
 *
 *
 *
 *
 * INPUT         = (hDC,Index,pData,lCount,pDDC,FunN)
 *
 *
 * OUTPUT        = NONE
 *
 *
 *
 *
 * RETURN-NORMAL = GPI_OK    - no problems
 *
 *
 *
 * RETURN-ERROR  = GPI_ERROR - Illegal Index or lCount field
 *
 *
 *
 **************************************************************************/
/*
** CAP_ADDITIONAL_GRAPHICS change
** add flag indicating that we support new font metrics structures by
** zeroing new fields- MarkV
**
*/

#ifndef  CAPS_ENHANCED_FONTMETRICS
  #define  CAPS_ENHANCED_FONTMETRICS 0x2000 /* 1.3 driver handles 2.0 FM    */
#endif

/*
** CAP_ADDITIONAL_GRAPHICS change
** add Caps flag to get pre clipped lines from the 202(or greater) ENG
** during area fills.  The preclipped lines will come to PolyScanLine
** with the COM_PRECLIP COM flag set. Also see code in PolyScanLine
** MarkV 09/23/92
*/

#ifndef  CAPS_CLIP_FILLS               /* defined in 2.0 headers eng ver
                                          202 or greater                    */
#define  CAPS_CLIP_FILLS 4096L
#endif
/*
** CAP_ADDITIONAL_GRAPHICS change
** add Caps flag to identify 1.3 drivers that support setcurrentposition
** and getcurrentposition with the transform flag varied on or off.
** MV 11/21/92
*/

#ifndef  CAPS_TRANSFROM_SUPPORT        /* defined in 2.0 headers eng ver
                                          202 or greater                    */
#define  CAPS_TRANSFROM_SUPPORT  0x00004000L
#endif

LONG  QueryDeviceCaps(HDC hDC,LONG Index,PULONG pData,LONG lCount,
                                 PDDC pDDC,ULONG FunN)
{
  PPDEVICE     pPDevice = pDDC->pPDevice;
  LONG         lResult  = GPI_OK;
  LONG         *plddcaps;                  /* display driver dev caps           */
  ULONG        ulCapsMax = CAPS_DEVICE_POLYSET_POINTS+1;

  if (!EnterDriver(pDDC))
      return(GPI_ERROR);


  if (Index < 0L || lCount < 1L)
  {
    /*
    ** Silly values
    */
    GplErrSetError(PMERR_INV_LENGTH_OR_COUNT);
    lResult = GPI_ERROR;
  }
  else
  {

    if (pDDC->usDCType == OD_MEMORY)
    {
      if (!(plddcaps = (LONG *)GplMemoryAlloc(NULL,ulCapsMax * sizeof(LONG))))
      {
         LeaveDriver( pDDC );
         return( 0L );
      }

      if (!((ULONG) InnerGreQueryDeviceCaps (pDDC->hdcMemory, 0L, plddcaps,
                                           ulCapsMax, FunN)))
      {
         LeaveDriver( pDDC );
         return( 0L );
      }
    }

    /*
    ** Loop through the various attributes.
    */
    while (lCount--)
    {
      switch ((SHORT)Index++)
      {
        case  CAPS_FAMILY :
          *pData = OD_DIRECT;
          break;
        case  CAPS_IO_CAPS :
          *pData = CAPS_IO_SUPPORTS_OP;
          break;
        case CAPS_TECHNOLOGY :
          /*
          ** we return that we are a raster device if the
          ** device is not a pen plotter
          ** This will cause FreeLance to do Gradient fills
          */
          if (PlotterClass[pDDC->pPDevice->Plotter].fsOdd & (OD_PCL | OD_HPRTL))
            *pData = CAPS_TECH_RASTER_PRINTER;
          else
            *pData = CAPS_TECH_VECTOR_PLOTTER;
          break;
        case  CAPS_DRIVER_VERSION :
          *pData = VERSION;
          break;
        case  CAPS_WIDTH :
          *pData = pPDevice->lCapsWidth;
          break;
        case  CAPS_HEIGHT :
          *pData = pPDevice->lCapsHeight;
          break;
      case  CAPS_WIDTH_IN_CHARS :

          /* Check this out Kran */
          if (pDDC->usDCType == OD_MEMORY)
          {
             *pData = pPDevice->lCapsWidth / plddcaps[CAPS_CHAR_WIDTH];
             break;
          }
          *pData = pPDevice->lCapsWidth / (pPDevice->lCapsDefCharWidth ?
                                           pPDevice->lCapsDefCharWidth:1L);
          break;

      case  CAPS_HEIGHT_IN_CHARS :

          /* Check this out Kran */
          if (pDDC->usDCType == OD_MEMORY)
          {
             *pData = pPDevice->lCapsWidth / plddcaps[CAPS_CHAR_HEIGHT];
             break;
          }
          *pData = pPDevice->lCapsHeight / (pPDevice->lCapsDefCharHeight ?
                                            pPDevice->lCapsDefCharHeight:1L);
          break;

        case  CAPS_HORIZONTAL_RESOLUTION :
          *pData = pPDevice->lPltrRes_XPelsPerMeter;
          break;

        case  CAPS_VERTICAL_RESOLUTION :
          *pData = pPDevice->lPltrRes_YPelsPerMeter;
          break;

      case  CAPS_CHAR_WIDTH :

          if (pDDC->usDCType == OD_MEMORY)
          {
             *pData = plddcaps[CAPS_CHAR_WIDTH];
             break;
          }
          *pData = pPDevice->lCapsDefCharWidth;
          break;
      case  CAPS_CHAR_HEIGHT :

          if (pDDC->usDCType == OD_MEMORY)
          {
             *pData = plddcaps[CAPS_CHAR_HEIGHT];
             break;
          }
          *pData = pPDevice->lCapsDefCharHeight;
          break;
        case  CAPS_SMALL_CHAR_WIDTH :
          if (pDDC->usDCType == OD_MEMORY)
          {
             *pData = plddcaps[CAPS_CHAR_WIDTH];
             break;
          }
          *pData = pPDevice->lCapsDefCharWidth;
          break;
        case  CAPS_SMALL_CHAR_HEIGHT :
          if (pDDC->usDCType == OD_MEMORY)
          {
             *pData = plddcaps[CAPS_SMALL_CHAR_HEIGHT];
             break;
          }
          *pData = pPDevice->lCapsDefCharHeight;
          break;
      case  CAPS_COLORS :
          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_COLORS];
          else
             if (pDDC->pPDevice->usHPGLType & HPGL2)
               *pData = CAPS_COLORS_8BIT;
             else
               *pData = (LONG)pPDevice->NumColorsAvailable;
          break;

      case  CAPS_COLOR_PLANES :
          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_COLOR_PLANES];
          else
             *pData = 1L;
          break;

      case  CAPS_COLOR_BITCOUNT :
          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_COLOR_BITCOUNT];
          else
             if (pDDC->pPDevice->usHPGLType & HPGL2)
               *pData = CAPS_COLOR_BITCOUNT_24;
             else
               *pData = 4L;
          break;

      case  CAPS_COLOR_TABLE_SUPPORT :
          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_COLOR_TABLE_SUPPORT];
          else
             *pData = 0L;
          break;

      case  CAPS_MOUSE_BUTTONS :
          *pData = 0L;
          break;
      case  CAPS_FOREGROUND_MIX_SUPPORT :

          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_FOREGROUND_MIX_SUPPORT];
          else
             *pData = CAPS_FM_OVERPAINT|CAPS_FM_LEAVEALONE;
          break;

      case  CAPS_BACKGROUND_MIX_SUPPORT :

          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_BACKGROUND_MIX_SUPPORT];
          else
             *pData = CAPS_BM_OVERPAINT|CAPS_BM_LEAVEALONE;
          break;

      case  CAPS_VIO_LOADABLE_FONTS :

          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_VIO_LOADABLE_FONTS];
          else
             *pData = 0L;
          break;

        case  CAPS_WINDOW_BYTE_ALIGNMENT :
          *pData = 0L;
          break;

      case  CAPS_BITMAP_FORMATS :
          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_BITMAP_FORMATS];
          else
             // added vector bitblt
             // we do not set the bit for Pen plotters.
             // Because setting CAPS_RASTER_BITBLT will
             // cause the GRE to simulate BoxInterior with
             // sdBitBlt as apposed to fillpath. This
             // was causing old plotters that to not support
             // rectangle fill commands not to work.
             // A better fix would be to add the code to sdBitBlt
             // to support vector filling of rectangle areas without
             // calling draw_box.
             if (pDDC->pPDevice->usHPGLType & HPGL2)
               *pData = 4L;
             else
               *pData = 0L;
          break;

      case  CAPS_RASTER_CAPS :
          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_RASTER_CAPS];
          else
             // added vector bitblt
             // we do not set the bit for Pen plotters.
             // Because setting CAPS_RASTER_BITBLT will
             // cause the GRE to simulate BoxInterior with
             // sdBitBlt as apposed to fillpath. This
             // was causing old plotters that to not support
             // rectangle fill commands not to work.
             // A better fix would be to add the code to sdBitBlt
             // to support vector filling of rectangle areas without
             // calling draw_box.
             if (pDDC->pPDevice->usHPGLType & HPGL2)
               *pData = CAPS_RASTER_BITBLT;
             else
               *pData = 0L;                 /* CAPS_RASTER_BANDING;          */


             // To get sdBitBlts for chars we will set CAPS_RASTER_FONTS
             //
             //if (PlotterClass[pDDC->pPDevice->Plotter].fsOdd & (OD_PCL))
             //{
             //  *pData |= CAPS_RASTER_FONTS;
             //}
          break;

      case  CAPS_MARKER_WIDTH :
          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_MARKER_WIDTH];
          else
             *pData = pPDevice->lCapsDefMarkerWidth;
          break;

      case  CAPS_MARKER_HEIGHT :
          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_MARKER_HEIGHT];
          else
             *pData = pPDevice->lCapsDefMarkerHeight;
          break;

      case  CAPS_DEVICE_FONTS :
          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_DEVICE_FONTS];
          else
             *pData = 1L;
          break;

        case  CAPS_GRAPHICS_SUBSET :
          *pData = 0L;
          break;

        case  CAPS_GRAPHICS_VERSION :
          *pData = 0L;
          break;

        case  CAPS_GRAPHICS_VECTOR_SUBSET :
          *pData = 0L;
          break;

        case  CAPS_DEVICE_WINDOWING :
          *pData = 0L;
          break;

      case  CAPS_ADDITIONAL_GRAPHICS :
         /*
         ** Additional graphics support.
         **   Bit 0  device supports geometric line types.
         **   Bit 1  device supports kerning.
         **   Bit 2  device can supply default vector font.
         **   Bit 3  device can supply default raster font.
         **   Bit 4  device will manage default vector font.
         **   Bit 5  device will manage default raster font.
         **   Bit 13 device will handle new FONTMETRICS
         */

          if (pDDC->usDCType == OD_MEMORY)
          {
             *pData = plddcaps[CAPS_DEVICE_FONTS]
                      &~CAPS_FONT_OUTLINE_DEFAULT
                      &~CAPS_FONT_IMAGE_DEFAULT
                      &~CAPS_FONT_OUTLINE_MANAGE
                      &~CAPS_FONT_IMAGE_MANAGE
                      &~CAPS_ENHANCED_FONTMETRICS
                      | CAPS_CLIP_FILLS ;         /* turn on the pre-clip */
          }
          else
          {
             *pData = (CAPS_FONT_OUTLINE_DEFAULT | CAPS_ENHANCED_FONTMETRICS |
                       CAPS_CLIP_FILLS | CAPS_TRANSFROM_SUPPORT);
             /*
             ** if this is a HPGL2 device we can do wide lines
             */
             if (pDDC->pPDevice->usHPGLType & HPGL2)
             { /* add the cosmetic wideline support flag */
               *pData = *pData | CAPS_COSMETIC_WIDELINE_SUPPORT;
             }
          }
          break;
        case  CAPS_PHYS_COLORS :

          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_PHYS_COLORS];
          else
          {
            if (pDDC->pPDevice->pSetup->usFlags & FL_USEAPPCOLORS)
            {
               //*pData = pPDevice->NumColorsAvailable; */
               // we can do any RGB. However, we will report 8 bit
               // until we check out the bitblt code at 24bit
               *pData = CAPS_COLORS_8BIT;
            }
            else
            {
              /*
              **  ptr B724996
              **
              **  previously returning a hard coded value, specifically
              **  MAX_COLORS, now checks with the device.
              **
              **    dlr / slu                                     @MGX-SYSDEV
              */
              INT i;
              USHORT usColorCount = 0;
              PULONG pulTempData;

              for (i = 0; i < MAX_COLORS; i++)
              {
                if (pDDC->pPDevice->ColorAvailable[i])
                {
                  usColorCount++;
                }
              }
              /*
              ** add one for white background
              */
              usColorCount++;
              pulTempData = (PULONG)pData;
              *pulTempData = (ULONG)usColorCount;
 /*           CSet/2 Conversion: Jim R
 **           (ULONG)*pData = (ULONG)usColorCount;
 */
            }
          }
            break;
      case  CAPS_COLOR_INDEX :

          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_PHYS_COLORS];
          else
             *pData = MAX_COLOR_INDEX;
          break;

      case  CAPS_GRAPHICS_CHAR_WIDTH :

          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_CHAR_WIDTH];
          else
             *pData = pPDevice->lCapsDefCharWidth;
          break;

      case  CAPS_GRAPHICS_CHAR_HEIGHT :
          if (pDDC->usDCType == OD_MEMORY)
             *pData = plddcaps[CAPS_CHAR_HEIGHT];
          else
             *pData = pPDevice->lCapsDefCharHeight;
          break;

        case  CAPS_HORIZONTAL_FONT_RES :
          *pData = pPDevice->lRes_XPelsPerInch;
          break;

        case  CAPS_VERTICAL_FONT_RES :
          *pData = pPDevice->lRes_YPelsPerInch;
          break;

        case  CAPS_LINEWIDTH_THICK :
          if (pDDC->usDCType == OD_MEMORY)
          {
             *pData = plddcaps[LINEWIDTH_THICK];
             break;
          }
          if (pDDC->pPDevice->usHPGLType & HPGL2)
          { /* we set the pen to .25mm for thick lines */
            *pData = (((pPDevice->lRes_YPelsPerInch *
                        100) / 4) / 254);
            *pData = round_divide(*pData, 10);
          }
          else
            *pData = 0L;               /* Not supported on pen plotter   */

          break;
        default  :
          *pData = 0L;                 /* Not defined, so we don't do it    */
          break;
      }
      ++pData;
    }
  }

  if (pDDC->usDCType == OD_MEMORY)
      GplMemoryFree((PVOID)plddcaps);

  LeaveDriver(pDDC);
  return  lResult;
}

/***************************************************************************
 *
 * FUNCTION NAME = QueryDeviceNames()
 *
 *
 * DESCRIPTION   = Returns information about the device names
 *
 *                 Returns information about the device names available
 *                 from this driver,  and about the datatypes handled.
 *                 This is a direct entry point into the driver,  bypassing
 *                 the dispatch table mechanism, since many applications will
 *                 want to call here without initialising
 *                 the driver in the usual sense.
 *
 *
 *
 * INPUT         = (pDrvName,pDN,pDevName,pDevDesc,pDT,
 *                  pDatType,pDDC,FunN)
 *
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = DEV_OK    - Data is as requested
 *
 *
 *
 * RETURN-ERROR  = DEV_ERROR - Illegal parameters is basic cause of this
 *
 *
 *
 **************************************************************************/

BOOL  QueryDeviceNames(PSZ pDrvName,PLONG pDN,PSTR32 pDevName,PSTR64
                                  pDevDesc,PLONG pDT,PSTR16 pDatType,PDDC pDDC
                                  ,ULONG FunN)

/*
**  PSZ pDrvName;                         Driver name - unused by us
**  PLONG pDN;                            Maximum number of device names
**                                        returned
**  PSTR32 pDevName;                      array of 32 byte device names
**  PSTR64 pDevDesc;                      array of 64 byte device
**                                        descriptions
**  PLONG pDT;                            number of data type supported
**  PSTR16 pDatType;                      array of 16 byte data types
**  PDDC pDDC;
**  ULONG FunN;
*/

{

   /*
   ** Process requests for the number of datatypes supported
   */

  if (*pDT)
  {

    /*
    ** User wants to know about data types.
    */
    strcpy (pDatType[0], "PM_Q_STD");

    if (*pDT > 1)
    {
      strcpy (pDatType[1], "PM_Q_RAW");
      *pDT = 2;
    }
    else
      *pDT = 1;
  }
  else
    *pDT = 2L;                         /* User wants the number we have     */

  /*
  ** Process requests for the number of device names supported
  */
  if (*pDN)
  {
    /*
    ** Supply the names the user wants
    */
    USHORT usIndex;
    USHORT usPlotNum;
    HAB hAB = WinInitialize(0);

    /*
    ** Determine number to return to user
    */
    if (*pDN > (LONG)usPlotterCount)
      *pDN = (LONG)usPlotterCount;
    usPlotNum = (USHORT)*pDN;

    for (usIndex = 0; usIndex < usPlotNum; usIndex++)
    {
      /*
      ** Scan through each name we have
      */
      strcpy (pDevName[usIndex], PlotterDef[usIndex].pDeviceName);
      WinLoadString(hAB, hModule,
                    PlotterDef[usIndex].usStringId, 64,
                    pDevDesc[usIndex]);
    }
    WinTerminate(hAB);
    *pDN = (LONG)usPlotNum;
  }
  else
    *pDN = usPlotterCount;             /* User wants to know how many we
                                          have                              */
  return  DEV_OK;
}

/***************************************************************************
 *
 * FUNCTION NAME = QueryDeviceResource
 *
 *
 * DESCRIPTION   = load all pointers
 *
 *                 This function is called by user on setup to load all
 *                 pointers, bitmaps, and by the engine to load the default
 *                 fonts if any.  This is a required function.  The plotter
 *                 driver returns zero, for resource not available.
 *
 *
 *
 *
 *
 * INPUT         = (hDC,Type,Id,pDDC,FunN)
 *
 * OUTPUT        = NONE
 *
 *
 *
 *
 * RETURN-NORMAL = 0
 *
 *
 *
 * RETURN-ERROR  = NONE
 *
 *
 *
 **************************************************************************/

SHORT  QueryDevResource2(HDC hDC,ULONG Type,ULONG Id,PDDC pDDC,ULONG
                                   FunN)
{
  ULONG ulRet;

  if (!EnterDriver(pDDC))
      return(GPI_ERROR);

  /**************************************************************************
  **          In case of MEMORYDC Dispatch to Graphics Engine              **
  **                            Kran                                       **
  **************************************************************************/
  if (pDDC->usDCType == OD_MEMORY)
  {
    ulRet = (ULONG) InnerGreQueryDevResource2 (pDDC->hdcMemory, Type, Id, FunN);
    LeaveDriver( pDDC );
    return( ulRet );
  }
  GplErrSetError(PMERR_DEV_FUNC_NOT_INSTALLED);
  LeaveDriver(pDDC);
  return 0;
}

/***************************************************************************
 *
 * FUNCTION NAME = QueryHardcopyCaps()
 *
 *
 * DESCRIPTION   = hardcopy information return
 *
 *                 Return information about the hardcopy capabilities of
 *                 the device.
 *                 Function either returns the number of forms available,
 *                 or returns information about (selected) forms available
 *
 *
 * INPUT         = (hDC,Start,lCount,pInfo,pDDC,FunN)
 *
 *
 * OUTPUT        = NONE
 *
 *
 *
 *
 * RETURN-NORMAL = Number of forms returned if lCount > 0 OR
 *                 Number of forms available on this particular device
 *
 *
 * RETURN-ERROR  = NONE
 *
 *
 *
 **************************************************************************/

LONG  QueryHardcopyCaps(HDC hDC,LONG Start,LONG lCount,PHCINFO pInfo,
                                   PDDC pDDC,ULONG FunN)

/*
**  HDC hDC;
**  LONG Start;                           Starting form number, 0 is first
**  LONG lCount;                          Number of forms required; 0 asks
**                                        how many forms
**  PHCINFO pInfo;                        Place where data is returned if
**                                        lcount > 0
**  PDDC pDDC;
**  ULONG FunN;
*/

{
  PPDEVICE pPDevice = pDDC->pPDevice;
  INT      Result;
  CHAR     achAppName[255];

  if (!EnterDriver(pDDC))
      return(GPI_ERROR);

  if (lCount < 0L || Start < 0L || (lCount > 0L && !pInfo))
  {
    /*
    ** An illegal combination
    */
    GplErrSetError(PMERR_INV_LENGTH_OR_COUNT);
    Result = -1;
  }
  else
  {
    SHORT         Index;
    USHORT        fsMediaSize;
    USHORT        usNumHardForms;
    USHORT        usNumUserForms;
    USHORT        usNumTotalForms;
    PBYTE         pbPtr = NULL;
    PUSERFORMINFO pufi;

    Result = 0;
    fsMediaSize = PlotterClass[pPDevice->Plotter].DeviceInfo.fsMediaSizeSupport;

    // begin @MJH
    build_app_name (pDDC->pPDevice->pszDriverName,
                    pDDC->pPDevice->pDriverData->szDeviceName,
                    pDDC->pPDevice->pszLogAddress,
                    achAppName);

    usNumHardForms = number_of_hardcoded_forms (pPDevice->Plotter);
    usNumUserForms = number_user_forms_defined (achAppName, HWND_DESKTOP);
    usNumTotalForms = usNumHardForms+usNumUserForms;

    if (lCount == 0)
    {
      Result = usNumTotalForms;
    }
    else
    {
      pbPtr = return_flat_user_forms_list (pDDC->pPDevice->hmcbHeap, achAppName);
      pufi = (PUSERFORMINFO)pbPtr;

      /*
      ** User wants data about specific forms. Start is the
      ** starting number of the (available) form requested.
      */
      for (Index = Start; lCount > 0 && Index < usNumTotalForms; Index++,lCount--)
      {
        /*
        ** Only return valid sizes
        */

        // Is it a hardcoded form?
        if (Index < usNumHardForms)
        {
          /*
          ** Valid - so return it to the caller
          */
          WinLoadString(pPDevice->hAB, hModule, SIZE_A+Index,
                        sizeof(pInfo->szFormname),
                        pInfo->szFormname);

          GetPageData(pPDevice, Index, (PHCINFO)pInfo);

          pInfo->flAttributes = 0;
          if (!(pPDevice->pSetup->usFlags & FL_USERDEFINEDFORMS) &&
              (pPDevice->pSetup->Size == Index))
             pInfo->flAttributes |= HCAPS_CURRENT;

          ++pInfo;                     /* Next entry                        */
          ++Result;
        }
        else
        {

           // It is a user defined form.  Copy in the info.
           strcpy (pInfo->szFormname, pufi->achFormName);

           // Values are stored in hundredths of mm ... round up to nearest mm
           pInfo->cx    = (pufi->ulWidth+50)/100;
           pInfo->cy    = (pufi->ulHeight+50)/100;

           pInfo->xPels = round_divide(pInfo->cx *
                                       pPDevice->lPltrRes_XPelsPerMeter, 1000);
           pInfo->yPels = round_divide(pInfo->cy *
                                       pPDevice->lPltrRes_XPelsPerMeter, 1000);

           pInfo->flAttributes = 0;
           if ((pPDevice->pSetup->usFlags & FL_USERDEFINEDFORMS) &&
               (pPDevice->pSetup->Size == pufi->usFormID))
              pInfo->flAttributes |= HCAPS_CURRENT;

           if (!(pPDevice->fsTransform & DXF_PORTRAIT))
           {
              /*
              ** Swap the X, Y data - plotter is in Landscape mode
              ** Note: X and Y hardware clip are swaped
              */
              SWAPLONG(pInfo->cx,    pInfo->cy);
              SWAPLONG(pInfo->xPels, pInfo->yPels);
              // but not the clipping since there is none for user defined forms
           }

           ++pInfo;
           ++Result;
           ++pufi;
        }
      }

      // Free the memory
      if (pbPtr)
        GplMemoryFree ((PVOID)pbPtr);
    }
  }
  LeaveDriver(pDDC);
  return (LONG)Result;
}

USHORT
number_of_hardcoded_forms (USHORT Plotter)
{
   register USHORT Index;
   USHORT          Result = 0;
   USHORT          fsMediaSize;

   fsMediaSize = PlotterClass[Plotter].DeviceInfo.fsMediaSizeSupport;
   /*
   ** Count of zero means user is requesting the number of
   ** forms available on this particular device.
   */
   for (Index = 0; Index < MAX_MEDIA_SIZES; Index++)
   {
     if (fsMediaSize & (1 << Index))
       ++Result;
   }

   return Result;
}
