/*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 = UTILS.C
 *
 * DESCRIPTIVE NAME = Utility functions
 *
 *
 * VERSION = V2.0
 *
 * DATE      12/12/88
 *
 * DESCRIPTION General utilities module with odds & ends, such as string
 *             compare, copy etc..
 *
 * FUNCTIONS
 *             IsIta_COM_Port_Name
 *             IsIta_Device_Name
 *             clear_memory            * commented out *
 *             copy_memory             * commented out *
 *             compare_memory
 *             get_mod_handle
 *             int_to_str
 *             lstrcmp
 *             lstrncmp
 *             lstrcpy
 *             lstrlen
 *             lstrtoksrch
 *             prompt_for_carousel
 *             prompt_for_page
 *             save_mod_handle
 *             str_to_int
 *             flush_opt_buffer
 *             queue_opt_buffer
 *             fill_opt_init
 *             fill_opt_end
 *             distance
 *             round_divide
 *             to_upper
 *
 *
 * NOTES
 *
 *
 * STRUCTURES  hModule
 *
 * EXTERNAL REFERENCES PlotterDef
 *
 * EXTERNAL FUNCTIONS
 *
*/
#define  INCL_SPL
#define  INCL_DOSMEMMGR                /* Dos memory manager                */
#define  INCL_DOSSEMAPHORES            /* Dos semaphores                    */
//#define  INCL_DOSDEVICES               /* Dos callback stuff                */
#define  INCL_SPLDOSPRINT
#define  INCL_SPLERRORS
#define  INCL_ERRORS

#include "plotters.h"
#include "dialog.h"
#include "error.h"
#include "utils.h"
#include "output.h"
#include "prdmath.h"
#include "dosio.h"
#include "init.h"
#include "profile.h"
#include "xforms.h"
#include "dispatch.h"

#include <stdio.h>
#include <string.h>

/*
** Define new spooler flag if not defined
**  The SPLDATA_NOAUTORETRY flag is set when the printer driver does
** not want the spooler to automatically timeout the PM message box
** with Retry return value, as it currently does after 3 minutes.
*/
#ifndef SPLDATA_NOAUTORETRY
#define SPLDATA_NOAUTORETRY 0x4000
#endif

/*
** Memory mgmt defines
*/
#define DEFAULT_SIZE  10
#define FROM_HEAP     0x80000000

/*
** Static data
*/
HMODULE hModule;

/*
** Public Routines
*/
LONG PLOTOPT round_divide(LONG, LONG);

/*
** Local Routines
*/
LOCAL CHAR PLOTOPT to_upper( CHAR c );
#if      FILLOPTPATH
    ULONG PLOTOPT distance(PPOINTL,POPTLN);
#endif

/***************************************************************************/

/***************************************************************************
 *
 * FUNCTION NAME = compare_memory
 *
 * DESCRIPTION   = compare memory at address pAddr1 with that at pAddr2,
 *                 for Size bytes
 *
 * INPUT         = pAddr1 - memory location 1
 *                 pAddr2 - memory location 2
 *                 Size   - amount of memory to compare
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = TRUE if identical
 * RETURN-ERROR  = FALSE
 *
 **************************************************************************/


BOOL  PLOTOPT compare_memory( PBYTE pAddr1, PBYTE pAddr2, SHORT Size)
{

  while (Size > 0 && *pAddr1++ == *pAddr2++)
    --Size;

  return (Size > 0 ? FALSE : TRUE);
}

/***************************************************************************
 *
 * FUNCTION NAME = clear_memory
 *
 * DESCRIPTION   = clear Size bytes of memory
 *
 * INPUT         = pDest  - memory location to clear
 *                 Size   - amount of memory to clear
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/


/******************************************************************************/
/*  C Set/2 conversion - This func is not used anywhere, will comment out     */
/*                       for now and let Mark decide if we should delete      */
/******************************************************************************/
//VOID  PLOTOPT clear_memory(PBYTE pDest,
//                   SHORT Size)
//{
//  /*
//  ** set Size bytes to zero at pDest
//  */
//  while (Size > 0)
//  {
//    *pDest++ = 0;
//    --Size;
//  }
//}

/***************************************************************************
 *
 * FUNCTION NAME = copy_memory
 *
 * DESCRIPTION   = copy Size bytes of memory
 *
 * INPUT         = pDest  - memory location to copy to
 *                 pSrc   - memory location to copy from
 *                 Size   - amount of memory to copy
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

/******************************************************************************/
/*  C Set/2 conversion - This func is not used anywhere, will comment out     */
/*                       for now and let Mark decide if we should delete      */
/******************************************************************************/
VOID  PLOTOPT copy_memory( PBYTE pDest, PBYTE pSrc, SHORT Size)
{
  /*
  ** Copy memory at address pSrc to pDest,  for Size bytes.
  */
  while (Size > 0)
  {
    *pDest++ = *pSrc++;
    --Size;
  }
}


/***************************************************************************
 *
 * FUNCTION NAME = get_mod_handle
 *
 * DESCRIPTION   = This function returns our module handle (hModule)
 *
 * INPUT         = NONE
 *
 * OUTPUT        = hModule
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

HMODULE  get_mod_handle()
{
  return (hModule);
}

/***************************************************************************
 *
 * FUNCTION NAME = IsIta_COM_Port_Name
 *
 * DESCRIPTION   = Check the physical port name.   Any port name starting
 *                 with "COM" and appexed with any integer > 1 will be
 *                 valid
 *
 * INPUT         = pPortName - name of port
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = TRUE
 * RETURN-ERROR  = FALSE
 *
 **************************************************************************/

BOOL  IsIta_COM_Port_Name (PSZ pPortName)
{
  BOOL   bRetCode = FALSE;
  USHORT usLen;

  if (!pPortName)
    return(FALSE);
  /*
  **  Check the physical port name.
  */
  usLen = lstrlen(pPortName);

  /*
  ** Port name should start with "COM" and
  ** appexed with atleast one integer.
  */
  if (usLen > 3 && (lstrncmp(pPortName, "COM", 3)))
  {
    bRetCode = TRUE;

    /*
    ** The first integer should >= 1. Zero is not allowed.
    */
    pPortName += 3;

    if (*pPortName < '1' || *pPortName++ > '9')
    {
      bRetCode = FALSE;
    }

    else
    {

      /*
      ** The second through last integer.
      */
      for (usLen -= 4; usLen > 0; --usLen)
      {
        if (*pPortName < '0' || *pPortName++ > '9')
        {
          bRetCode = FALSE;
          break;
        }
      }
    }
  }
  else
  {
    bRetCode = FALSE;
  }

  return (bRetCode);
}

/***************************************************************************
 *
 * FUNCTION NAME = IsIta_Device_Name
 *
 * DESCRIPTION   = Check that the physical device name is supported.
 *
 * INPUT         = pDeviceName - name of device
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = TRUE
 * RETURN-ERROR  = FALSE
 *
 **************************************************************************/

BOOL  IsIta_Device_Name( PSZ pDeviceName)
{
  ULONG ulIndex;

  /*
  **   Check that the physical device name is a supported type
  */
  if (pDeviceName)
  {
    for (ulIndex = 0; ulIndex < usPlotterCount; ulIndex++)
    {
      if (lstrcmp(PlotterDef[ulIndex].pDeviceName, pDeviceName))
        return (TRUE);
    }
  }

  return (FALSE);
}

/***************************************************************************
 *
 * FUNCTION NAME = int_to_str
 *
 * DESCRIPTION   = This function converts the given integer to its
 *                 character representation and returns the length of
 *                 the string representing the integer.  The integer
 *                 may be positive or negative.  The result string is
 *                 assumed to describe a string of sufficient length
 *                 to hold the character representation of the integer
 *                 plus a trailing NULL'.  The resulting string will
 *                 be at least MinDigits in length.
 *
 * INPUT         = Value     - value to convert
 *                 MinDigits - minimum number of returned digits
 *
 * OUTPUT        = pResult   - the destination string
 *
 * RETURN-NORMAL = TRUE
 * RETURN-ERROR  = FALSE
 *
 **************************************************************************/

SHORT PLOTOPT int_to_str( LONG  Value, PSZ   pResult, SHORT MinDigits)
{
  BOOL bNegative;

  short Index,Length = 0;

  if (bNegative = Value < 0L)
  {
    /*
    ** This will fail for the largest negative number - 0x80000000
    */
    Value = -Value;
    *pResult++ = '-';
  }

  do
  {
    pResult[Length++] = (UCHAR)(Value%10+'0');
    --MinDigits;
  } while ((Value /= 10) > 0 || MinDigits > 0);

  pResult[Length] = '\0';

  for (Index = Length-1; Index > 0; Index -= 2, pResult++)
  {                                    /* reverse string                    */
    char C = *pResult;

    *pResult       = pResult[Index];
    pResult[Index] = C;
  }

  return (bNegative ? Length + 1 : Length);
}

/***************************************************************************
 *
 * FUNCTION NAME = lstrcmp
 *
 * DESCRIPTION   = Compare the two strings for equality. Ignores case
 *
 * INPUT         = pStr1 - string 1
 *                 pStr2 - string 2
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = TRUE if identical.
 * RETURN-ERROR  = FALSE
 *
 **************************************************************************/

BOOL PLOTOPT lstrcmp( PSZ pStr1,
                      PSZ pStr2)
{

  /*
  ** Compare the two strings for equality,  regardless of case.
  */
  while (*pStr1 && *pStr2 && to_upper(*pStr1) == to_upper(*pStr2))
  {
    ++pStr1;
    ++pStr2;
  }

  return  to_upper(*pStr1) == to_upper(*pStr2);
}

/***************************************************************************
 *
 * FUNCTION NAME = lstrncmp
 *
 * DESCRIPTION   = Compare the two strings for equality for a given
 *                 length. Ignores case
 *
 * INPUT         = pStr1  - string 1
 *                 pStr2  - string 2
 *                 Length - length to compare
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = TRUE if identical.
 * RETURN-ERROR  = FALSE
 *
 **************************************************************************/

BOOL PLOTOPT lstrncmp( PSZ    pStr1,
                       PSZ    pStr2,
                       ULONG Length)
{
  ULONG Index = 0;

  while (Index < Length && (to_upper(*pStr1) == to_upper(*pStr2)))
  {
    Index++;
    pStr1++;
    pStr2++;
  }

  return  Index == Length ? TRUE : FALSE;
}

/***************************************************************************
 *
 * FUNCTION NAME = lstrcpy
 *
 * DESCRIPTION   = Copy from string pFrom to pTo.
 *
 * INPUT         = pFrom  - soruce string
 *                 pTo    - destination string
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID PLOTOPT lstrcpy( PSZ pTo,
                      PSZ pFrom)
{

  while (*pFrom)
    *pTo++ = *pFrom++;

  *pTo = '\0';
}

/***************************************************************************
 *
 * FUNCTION NAME = lstrlen
 *
 * DESCRIPTION   = Returns the length of the NULL-terminated string passed
 *
 * INPUT         = pString - string
 *
 * OUTPUT        = length of string
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

USHORT PLOTOPT lstrlen( PSZ pString)
{
  USHORT usSize = 0;

  while (*pString++)
    ++usSize;

  return (usSize);
}

/***************************************************************************
 *
 * FUNCTION NAME = lstrtoksrch
 *
 * DESCRIPTION   = This function searches for the Char token in the
 *                 string pointed at by the input pointer, replaces
 *                 this with a NULL and updates the pointer past the
 *                 NULL.
 *
 * INPUT         = pString - string to search
 *                 Char    - character to search for
 *
 *               It doesn't make sence updating the pointer
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID PLOTOPT lstrtoksrch( PSZ  pString,
                          CHAR Char)
{
  while (*pString && (*pString != Char))
    pString++;

  if (*pString)
  {
    *pString = '\0';
    pString++;
  }
}

/***************************************************************************
 *
 * FUNCTION NAME = prompt_for_carousel
 *
 * DESCRIPTION   = This function prompts the user to insert the
 *                 specified carousel.
 *
 * INPUT         = pDDC     - pointer to device driver context
 *                 Carousel - carousel requested
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

BOOL  prompt_for_carousel( PDDC  pDDC,
                           SHORT Carousel)
{
  PPDEVICE pPDevice = pDDC->pPDevice;

  char Caption[65],Message[90];

  if (pPDevice->bRealPlotter)
  {
    WinLoadString(pPDevice->hAB, hModule, pPDevice->StringID, 64, Caption);
    WinLoadString(pPDevice->hAB, hModule, NEWCAROUSEL1, 80, Message);
    int_to_str((long)Carousel, Message+lstrlen(Message), 1);

    SplMessageBox(NULL,                /* pPDevice->pszLogAddress,            */
                  SPLINFO_DDERROR|SPLINFO_INFORMATION,
                  SPLDATA_PENCHGREQD | SPLDATA_NOAUTORETRY,
                  Message,
                  Caption,
                  (USHORT)HWND_DESKTOP,
                  MB_OK);
  }

  return (TRUE);
}

/***************************************************************************
 *
 * FUNCTION NAME = prompt_for_page
 *
 * DESCRIPTION   = This function prompts the user to insert a sheet
 *                 of paper.
 *
 * INPUT         = pDDC     - pointer to device driver context
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

BOOL  prompt_for_page(PDDC pDDC)
{
  PPDEVICE pPDevice = pDDC->pPDevice;

  char Caption[65],Message[90];

  if (pPDevice->bRealPlotter)
  {
    WinLoadString(pPDevice->hAB, hModule, pPDevice->StringID,
                  64, Caption);
    WinLoadString(pPDevice->hAB, hModule, NEWSHEET1,
                  80, Message);
    int_to_str((long)pPDevice->CurrentPage+1, Message+lstrlen(Message), 1);

    SplMessageBox(NULL,                /* pPDevice->pszLogAddress,            */
                  SPLINFO_DDERROR|SPLINFO_INFORMATION,
                  SPLDATA_FORMCHGREQD | SPLDATA_NOAUTORETRY,
                  Message,
                  Caption,
                  (USHORT)HWND_DESKTOP,
                  MB_OK);
  }

  return (TRUE);
}


/***************************************************************************
 *
 * FUNCTION NAME = save_mod_handle
 *
 * DESCRIPTION   = This function is called from the initialize routine
 *                 when the driver is loaded.  It's purpose is to
 *                 stuff away the handle to the module in a ring2 data
 *                 segment.
 *
 * INPUT         = hMod - module handle
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID  save_mod_handle( HMODULE hMod)
{
  hModule = hMod;
  return ;
}

/***************************************************************************
 *
 * FUNCTION NAME = str_to_int
 *
 * DESCRIPTION   = This function converts an integer string to an
 *                 integer value.  The integer value is passed back
 *                 via pInt.  A pointer to the 1st position past the
 *                 integer value in the string is returned as a
 *                 result.  Leading spaces are ignored, and overflow
 *                 is accounted for by this function.
 *
 * INPUT         = pStr - pointer to string
 *
 * OUTPUT        = pInt - pointer to destination integer
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

PSZ PLOTOPT str_to_int( PSZ    pStr, PSHORT pInt)
{

  if (pStr != NULL)
  {
    BOOL bNegative;

    SHORT Diff;
    SHORT Result    = 0;
    SHORT Threshold = MAX_SHORT / 10;
    SHORT MaxLast   = MAX_SHORT % 10;
    SHORT Value;

    while (*pStr == ' ')
      ++pStr;

    if (bNegative = *pStr == '-')
      pStr++;

    for ( ; (Value = *pStr-'0') >= 0          &&
             Value <= 9                       &&
             ((Diff = Threshold-Result) > 0   ||
             (Diff == 0 && Value <= MaxLast));
             pStr++)
    {
      Result = Result *10+Value;
    }

    if (bNegative)
      Result = -Result;

    *pInt = Result;
  }

  return (pStr);
}

#if      FILLOPTPATH

/***************************************************************************
 *
 * FUNCTION NAME = fill_opt_init
 *
 * DESCRIPTION   = Initialize the fill optimization  code
 *
 * INPUT         = pDDC - pointer to device driver context
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID  fill_opt_init( PDDC pDDC)
{

  /*
  ** Allocate some storage and initialize the pointers etc.
  ** NOTE:  this function relies on GplMemoryAlloc clearing memory!
  */
  pDDC->pOptBuffer = (POPTBUF)GplMemoryAlloc(pDDC->pPDevice->hmcbHeap,
                                                       sizeof(OPTBUF));

  if (pDDC->pOptBuffer)
  {
    pDDC->usOptMode = FOM_RECORD;

    /*
    **          
    ** MV changed to true. Plotting works better if we start with
    ** the first line of the path. If we start with the closest line
    ** to the current position, we usually draw half the path and
    ** then have to come back to finish the other half.
    */
    pDDC->pOptBuffer->bMovePenFirst = TRUE;

    /*
    **          
    ** Remember where the pen is because recording points changes
    ** the physical position var without moving the pen. Therefore,
    ** we must restore the var before playing the lines.
    */
    pDDC->pOptBuffer->ptlWhere = pDDC->pPDevice->PhysPosition;
  }
  else
    pDDC->usOptMode = FOM_NONE;

  return ;
}

/***************************************************************************
 *
 * FUNCTION NAME = fill_opt_end
 *
 * DESCRIPTION   = Flush any remaining buffered lines and release memory
 *
 * INPUT         = pDDC - pointer to device driver context
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID  fill_opt_end( PDDC pDDC)
{

  /*
  **   Task is simple - if there is a buffer,  plot any remaining lines
  **  and then release the storage.   Finally,  update the pen position.
  */
  if (pDDC->pOptBuffer)
  {
    flush_opt_buffer(pDDC);

    if (GplMemoryFree ((PVOID)pDDC->pOptBuffer))
      pDDC->pOptBuffer = (POPTBUF)NULL;
  }

  pDDC->usOptMode = FOM_NONE;          /* No more                           */

  /*
  **  basically, lift the pen
  */
  lift_pen(pDDC);

  return ;
}

/***************************************************************************
 *
 * FUNCTION NAME = distance
 *
 * DESCRIPTION   = Returns a value proportional to the "plot distance"
 *                 between 2 points.  Plot distance is defined as the
 *                 larger of (delta x, delta y).  This works with
 *                 plotters because the pen can move in both
 *                 directions at the same time, and so the time to
 *                 move to a new position is determined solely by the
 *                 larger of the X or Y distance.
 *
 * INPUT         = pptl  - origin point
 *                 pplLn - line being considered
 *
 * OUTPUT        = a ulong representing the distance
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

ULONG  PLOTOPT distance( PPOINTL pptl,
                         POPTLN  pplLn)
{
  LONG lX0,lY0;
  LONG lX1,lY1;

  lX0 = pptl->x - pplLn->aptl[0].x;
  lY0 = pptl->y - pplLn->aptl[0].y;
  lX1 = pptl->x - pplLn->aptl[1].x;
  lY1 = pptl->y - pplLn->aptl[1].y;

  if (lX0 < 0)
    lX0 = -lX0;

  if (lY0 < 0)
    lY0 = -lY0;

  if (lX1 < 0)
    lX1 = -lX1;

  if (lY1 < 0)
    lY1 = -lY1;

  if (lY0 > lX0)
    lX0 = lY0;                         /* The larger of the pair            */

  if (lY1 > lX1)
    lX1 = lY1;

  return  lX0 > lX1 ? lX1 : lX0;
}

/***************************************************************************
 *
 * FUNCTION NAME = flush_opt_buffer
 *
 * DESCRIPTION   = Processes the contents of the area fill
 *                 optimization buffer, drawing a minimal (not
 *                 minimum) path through the accumulated line
 *                 segments.
 *
 * INPUT         = pDDC - pointer to device driver context
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID  flush_opt_buffer( PDDC pDDC)
{
  POPTBUF  pOP;                        /* pointer to vertex array buffer    */
  POPTLN   pLN;                        /* Pointer to particular line        */
                                       /* segment                           */
  INT      i,iMin;                     /* indices: general and minimum      */
                                       /* selected                          */
  INT      nLS;                        /* number of line segments in the    */
                                       /* buffer                            */
  ULONG    ulDist;
  ULONG    ulTemp;
  ULONG    ulTemp2;
  PPDEVICE pPDevice;                   /* Physical device details           */


  if (!pDDC->pOptBuffer || pDDC->pOptBuffer->usIndex == 0)
    return ;                           /* Nothing to output                 */

  pOP             = pDDC->pOptBuffer;
  pDDC->usOptMode = FOM_PLAY;          /* Play back, so output the data     */
  pPDevice        = pDDC->pPDevice;

  /*
  **restore the physical position to the real poistion. ie. The
  ** position before we started recording the points
  */
  pPDevice->PhysPosition = pOP->ptlWhere;

  if (pOP->bMovePenFirst == TRUE)
  {

    /*
    **          
    ** If this is the first buffer start at the first point.
    */
    move_pen(pDDC, &pOP->olDat[0].aptl[0], FALSE);
    pOP->bMovePenFirst = FALSE;
  }

  /*
  ** For all line segments in the buffer... choose the one nearest
  ** the current pen location and draw it.  Repeating the process
  ** until we have got them all drawn.
  */
  nLS = pOP->usIndex;

  while (nLS--)
  {
    ulDist = ~0L;
    iMin   = 0;

    for (i = 0; i < pOP->usIndex && ulDist; i++)
    {
      if (pOP->olDat[i].bActive)
      {
        /*
        **This one has some life in it still!
        */
        ulTemp = distance(&pPDevice->PhysPosition, &pOP->olDat[i]);

        if (ulTemp < ulDist)
        {

          /*
          ** Found a closer one,  so remember it!
          */
          ulDist = ulTemp;
          iMin   = i;

        }
      }
    }

    /*
    ** Now have the line,  so draw it - but find nearest end!
    */
    pLN = &pOP->olDat[iMin];

    /*
    ** Get X distance.
    ** Force positive result.
    */
    if (pPDevice->PhysPosition.x > pLN->aptl[0].x)
      ulTemp = pPDevice->PhysPosition.x-pLN->aptl[0].x;
    else
      ulTemp = pLN->aptl[0].x-pPDevice->PhysPosition.x;

    /*
    ** Get Y distance.
    ** Force positive result.
    */
    if (pPDevice->PhysPosition.y > pLN->aptl[0].y)
      ulTemp2 = pPDevice->PhysPosition.y-pLN->aptl[0].y;
    else
      ulTemp2 = pLN->aptl[0].y-pPDevice->PhysPosition.y;

    /*
    ** take the larger
    */
    if (ulTemp2 > ulTemp)
      ulTemp = ulTemp2;

    if (ulTemp > ulDist)
    {
      /*
      ** We guessed the       end - so plot the other way!
      */
      move_pen( pDDC, &pLN->aptl[ 1 ], FALSE );
      plot_line(pDDC, &pLN->aptl[0]);
    }
    else
    {

      move_pen( pDDC, &pLN->aptl[ 0 ], FALSE );
      plot_line(pDDC, &pLN->aptl[1]);
    }
    pLN->bActive = FALSE;              /* No more to go!                    */
  }
  pOP->usIndex    = 0;
  pOP->ptlWhere   = pPDevice->PhysPosition;
  pDDC->usOptMode = FOM_RECORD;        /* Back to record mode               */

  return ;
}

/***************************************************************************
 *
 * FUNCTION NAME = queue_opt_buffer
 *
 * DESCRIPTION   = records a pair of points (line segment) in the area
 *                 fill optimization buffer for processing when the
 *                 buffer fills and is flushed.
 *
 * INPUT         = pDDC     - pointer to device driver context
 *                 ptlStart - requested origin for line
 *                 ptlEnd   - termination point
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 * RETURN-ERROR  = NONE
 *
 **************************************************************************/

VOID  queue_opt_buffer( PDDC   pDDC, POINTL ptlStart, POINTL ptlEnd)
{
  POPTBUF  pOB;
  PPDEVICE pPDevice = pDDC->pPDevice;


  if (((pDDC->usDCType == OD_QUEUED)      &&
      (pPDevice->DataType == PM_Q_STD)) ||
      !pDDC->pOptBuffer)
  {
    /*
    **  Since we are not actually sending this to the plotter, why bother...
    */
    return;
  }                                    /* endif                             */

  /*
  **    Store the line ends in the next entry in the array of such things.
  **  If the array is now full,  then empty it out.
  */
  pOB = pDDC->pOptBuffer;

  if (pOB)
  {
    /*
    ** Actually have a buffer!  What a surprise
    */
    if (pOB->usIndex >= OPTBUFELEMENTS)
    {
      flush_opt_buffer(pDDC);
    }                                  /* endif                             */

    pOB->olDat[pOB->usIndex].aptl[0] = ptlStart;
    pOB->olDat[pOB->usIndex].aptl[1] = ptlEnd;
    pOB->olDat[pOB->usIndex].bActive = TRUE;
    pOB->usIndex++;
  }                                    /* endif                             */

  return;
}
#endif

/***************************************************************************
 *
 * FUNCTION NAME = round_divide
 *
 * DESCRIPTION   = Divide two numbers and round up or down
 *
 * INPUT         =
 *
 * OUTPUT        = Result (LONG)
 *
 *
 **************************************************************************/

LONG PLOTOPT round_divide(LONG lDividend, LONG lDivider)
{
  LONG lTemp;
  LONG lResult;

  lResult = lDividend / lDivider;
  /*
  ** check for round condition
  ** 1. We did not divide by 1
  ** 2. Remainder is >= 1/2 the divider
  */
  if ( (lDivider != 1) && (lDividend % lDivider >= lDivider >> 1) )
  {
    if (lResult > 0)
    {
      lResult++;
    }
    else
    {
      if (lResult < 0)
      {
        lResult--;
      }
      else
      {
        /*
        ** result is 0 so look at the original numbers.
        ** if they were both positive or
        ** both negative Result is positive so
        ** round up.
        */
        if (lDividend < 0 && lDivider < 0 ||
            lDividend >=0 && lDivider >=0)
        {
          lResult++;
        }
        else
        {
          lResult--;
        }
      }
    }
  }

  return (lResult);
}

/***************************************************************************
 *
 * FUNCTION NAME = to_upper
 *
 * DESCRIPTION   = Converts a character from upper to lower case
 *
 * INPUT         = CHAR
 *
 * OUTPUT        = CHAR
 *
 *
 **************************************************************************/

LOCAL CHAR PLOTOPT to_upper( CHAR c )
{
  if ( c >= 'a' && c <= 'z' )
  {
    c = c + 'A' - 'a';
  }
    return(c);
}

/***************************************************************************
 *
 * FUNCTION NAME = return_driver_data
 *
 * DESCRIPTION   = This takes a copy of the driver data that we are not sure
 *                 about (from the application) and returns a copy that we
 *                 are sure about.
 *
 * This function will return valid driver data.  It does this by allocating
 * a copy of driver data and initializing it to its default state.  It is the
 * caller's responsibility to free this data.  If driver data was given to
 * us, then we will copy over that data onto our initialized data up to either
 * the size that they gave us or our current size.
 *
 * NOTE:
 *    We should always have a DRIVDATA passed to us.  If the application did
 *    not give us any, then the spooler will give us the queue's job
 *    properties (DevOpenDC calls SplQmSetup which verifys the parameters).
 *
 * INPUT         = hmcbHeap              - a heap
 *                 pInput              - input job properties
 *                 pszLogicalAddress   - the logical address that we are
 *                                       printing to
 *                 fDevPostDeviceModes - Did DevPostDeviceModes call us?
 *
 * OUTPUT        = PDRIVDATA - A copy of job properties that we are sure about
 *
 **************************************************************************/
LOCAL PDRIVDATA PLOTOPT
return_driver_data (PVOID     hmcbHeap,
                    PDRIVDATA pInput,
                    PSZ       pszLogicalAddress,
                    BOOL      fDevPostDeviceModes)
{
   USHORT       rc;
   PDRIVDATA    pOutput = NULL;
   PPROFILEINFO pISetup, pOSetup;
   ULONG        ulMemorySize = 0L;             /* receive size of segment  */
   ULONG        ulFlags;
   ULONG        ulSizeForName;
   USHORT       Plotter;
   USHORT       StringID;
   BOOL         fMustInitializeToUnknown = TRUE;
   BOOL         fCanCopy                 = FALSE;
   CHAR         achDeviceName[33];
   PSZ          pszGoodDeviceName        = NULL;

   pOutput = (PDRIVDATA)GplMemoryAlloc (hmcbHeap, DRIVDATASIZ);
   if (!pOutput)
      return pOutput;

   // Set up the pointer to the profile
   pOSetup = (PPROFILEINFO)pOutput->abGeneralData;

   /* Set up the driver data that we have just allocated.
   ** ->cb            we know about.
   ** ->lVersion      we know about.
   ** ->szDeviceName  will be filled in when we know what the name is.
   ** ->abGeneralData will be filled in after we know the device name.
   */
   pOutput->cb       = DRIVDATASIZ;
   pOutput->lVersion = JOBPROP_VERSION;                              // @MJH3

   /* See how much of the old data we can copy, unless the given pointer is
   ** null
   */
   if (pInput)
   {
      /* Find out how big the memory is.  Keep in mind that for tiled
      ** memory this value will be 65536.
      */
      ulMemorySize = DRIVDATASIZ;
      /*
      ** CSet/2 Conversion: Jim R
      ** We have to be carefull using DosQueryMem.  From my testing it only
      ** seems to work correctly if the memory was allocated using
      ** DosAllocMem.
      */
      rc = DosQueryMem (pInput, &ulMemorySize, &ulFlags);
      if (!rc &&
          (!(ulFlags & (PAG_FREE   | PAG_GUARD))) &&   // Things we don't want
          ( (ulFlags & (PAG_COMMIT | PAG_READ))))      // Things we do want
      {
         if (pInput->cb)
         {
            fCanCopy = TRUE;

            /* At least we know that we have data to copy.
            ** Now, see if we were given enough to look at the device name.
            */
            ulSizeForName = (PCHAR)pInput->abGeneralData-(PCHAR)pInput;

            /* See if we can look at the device name in the job properties
            */
            if ((ulMemorySize >= ulSizeForName) &&
                (pInput->cb   >= ulSizeForName)  )
            {
               /* We can see the name */
               if (IsIta_Device_Name (pInput->szDeviceName))
               {
                  /* We were given a valid device name.
                  */
                  fMustInitializeToUnknown = FALSE;
                  pszGoodDeviceName = pInput->szDeviceName;
               }
               else
                  fCanCopy = FALSE;
            }

            /* Set up the pointer to the profile
            */
            pISetup = (PPROFILEINFO)pInput->abGeneralData;

            /* Now, see if we were given enough to look at the version.
            */
            ulSizeForName = (PCHAR)&pISetup->Version-(PCHAR)pInput
                            +sizeof (pISetup->Version);

            /* See if we can look at the version in the job properties
            */
            if ((ulMemorySize >= ulSizeForName) &&
                (pInput->cb   >= ulSizeForName)  )
            {
               if (pISetup->Version != JOBPROP_VERSION)              // @MJH3
                  fCanCopy = FALSE;
            }
         }
      }

      /* Here, we were given job properties.  If the above tests failed,
      ** then post a warning.
      */
      if (FALSE == fCanCopy)
         GplErrSetWarning (PMERR_INV_DRIVER_DATA);
   }

   /* See if we were not given a device name.
   */
   if (fMustInitializeToUnknown)
   {
      /* Try to find that device name and printer name
      ** from the logical address...
      */
      find_device_name (hmcbHeap, achDeviceName, pszLogicalAddress);

      /* Did we return a valid name?
      */
      if (*achDeviceName && IsIta_Device_Name (achDeviceName))
         pszGoodDeviceName = achDeviceName;
      else
         /* We could not find a device name!
         */
         GplErrSetError (PMERR_DEVICE_DRIVER_ERROR_1);
   }

   /* By now we *should* have a device name
   */
   if (pszGoodDeviceName)
   {
      PSZ   pszPrinterName    = NULL;
      BOOL  bCouldFindProfile = FALSE;

      strcpy (pOutput->szDeviceName, pszGoodDeviceName);

      if (!('\\' == pszLogicalAddress[0] && '\\' == pszLogicalAddress[1]))
      {
         /* If the logical address is in the form of \\server\queue then
         ** there should be no spooler printer name present on the
         ** requestor.
         */
         /* Find the spooler's printer name */
         pszPrinterName = find_printer_name (hmcbHeap,
                                             pszGoodDeviceName,
                                             pszLogicalAddress,
                                             fDevPostDeviceModes);
         // fill up the pOSetup with our defaults first and then get info
         // from the ini file.
         get_device_id (pszGoodDeviceName, &Plotter, &StringID);
         get_defaults (Plotter, pOSetup);

         bCouldFindProfile = get_profile (hmcbHeap,
                                          APP_NAME,
                                          pszGoodDeviceName,
                                          pszPrinterName,
                                          pOSetup);
      }
      else
         bCouldFindProfile = FALSE;

      if (((PSZ)NULL == pszPrinterName) ||  // Could not find printer name
          !bCouldFindProfile)               // Could not find the profile
      {
         /* Failure ... initialize from the structure instead of printer
         ** properties.
         */
         get_device_id (pszGoodDeviceName, &Plotter, &StringID);
         get_defaults (Plotter, pOSetup);
      }

      /* Clean up */
      if (pszPrinterName)
         GplMemoryFree ((PVOID)pszPrinterName);
   }
   else
   {
      /* Big time failure!  We do not know what device to initialize our
      ** job properties to.  What can we do now???
      ** Maybe we should default to our first device...
      */
      GplErrSetError (PMERR_DEVICE_DRIVER_ERROR_1);
      get_defaults (0, pOSetup);
      strcpy (pOutput->szDeviceName, PlotterDef[0].pDeviceName);
   }

   /* Shall we overwrite what we have initialized with what was given to us?
   */
   if (fCanCopy)
   {
      USHORT min;

      /* Don't copy more than the size of out driver data
      ** OR is specified in the structure
      ** OR that we can access!
      */
      min = ((USHORT)pInput->cb <= (USHORT)ulMemorySize) ? (USHORT)pInput->cb
                                                         : (USHORT)ulMemorySize;
      min = (DRIVDATASIZ <= min) ? DRIVDATASIZ : min;
      CopyMem ((PBYTE)pOutput, (PBYTE)pInput, min);

      /* Now, change some of the information back to our current settings.
      */
      pOutput->cb       = DRIVDATASIZ;
      pOutput->lVersion = JOBPROP_VERSION;                           // @MJH3
   }

   return pOutput;
}

/***************************************************************************
 *
 * FUNCTION NAME = find_device_name
 *
 * DESCRIPTION   = Given a logical address (spooler printer name, spooler
 *                 device name. port name, or a UNC name) find the associated
 *                 PLOTTER device name.
 *
 * This function takes a logical address (queue name, device name, port name,
 * or file name) and tries to find a PLOTTER.device name.  It will place the
 * device name string in pszDeviceName.  If there are multiple plotter devices
 * attached to the printer (spooler term) then we will return the first one
 * because we have know way of determining the proper one to select.
 *
 * INPUT         = hmcbHeap            - Heap handle
 *                 pszDeviceName     - OUTPUT parameter
 *                 pszLogicalAddress - Logical address
 *
 * OUTPUT        = VOID
 *                 pszDeviceName     - Device name that we found.  If no device
 *                                     name, then an error occured.
 *****************************************************************************/
VOID PLOTOPT
find_device_name (PVOID hmcbHeap, PSZ pszDeviceName, PSZ pszLogicalAddress)
{
   SPLERR       rc;
   PBYTE        pbQueue         = NULL;
   ULONG        cbBytes;
   ULONG        cbNeeded;
   ULONG        cReturned;
   ULONG        cTotal;
   PPRQINFO3    prq;
   PPRDINFO3    prd;
   PSZ          pszDrivers      = NULL;
   PSZ          pszComputerName = NULL;
   PSZ          pszTemp         = NULL;
   PSZ          pszComma;
   register INT i;

   *pszDeviceName = 0;

   if ('\\' == pszLogicalAddress[0] && '\\' == pszLogicalAddress[1])
   {
      /* This is a network print queue in the form of \\server\queue
      */
      pszTemp = (PSZ)GplMemoryAlloc (hmcbHeap, strlen (pszLogicalAddress)+1);
      if (pszTemp)
         strcpy (pszTemp, pszLogicalAddress);
      else
         // Error!
         return;

     /* Set up pointers ... */
     pszComputerName = pszTemp;
     /* Get rid of the queue name in the \\server\queue string */
     for (i = 2; pszComputerName[i]; i++)
        if ('\\' == pszComputerName[i])
        {
           pszComputerName[i] = 0;
           pszLogicalAddress = pszComputerName+i+1;
           break;
        }

     /* pszComputerName   should now be \\server
     ** pszLogicalAddress should now be queue
     */
   }

   /* See if it is a queue or a port
   */
   cbNeeded = 0;
   cbBytes  = 0;
   rc = SplQueryQueue (pszComputerName,          // Computer name
                       pszLogicalAddress,        // Queue Name
                       3,                        // Level
                       pbQueue,                  // Buffer
                       cbBytes,                  // is size
                       &cbNeeded);               // # bytes needed
   if (NERR_QNotFound == rc)
   {
      // Must be a port
      cbNeeded = 0;
      cbBytes  = 0;

      // Find out how much memory to allocate
      rc = SplEnumDevice (pszComputerName,       // Computer name
                          3,                     // Level
                          pbQueue,               // Buffer
                          cbBytes,               // is size
                          &cReturned,            // Number returned
                          &cTotal,               // Total available
                          &cbNeeded,             // # bytes needed
                          (PVOID)NULL);          // Reserved
      if (ERROR_MORE_DATA == rc || NERR_BufTooSmall == rc)
      {
         // Allocate the memory
         cbBytes = cbNeeded;
         pbQueue = (PBYTE)GplMemoryAlloc (hmcbHeap, cbNeeded);
         if (!pbQueue)
            // Error!
            return;

         rc = SplEnumDevice (pszComputerName,    // Computer name
                             3,                  // Level
                             pbQueue,            // Buffer
                             cbBytes,            // is size
                             &cReturned,         // Number returned
                             &cTotal,            // Total available
                             &cbNeeded,          // # bytes needed
                             (PVOID)NULL);       // Reserved
         if (rc)
            // Error!
            return;
      }
      else if (rc)
         // Error!
         return;

      // The data returned by the spooler is valid.  Setup the pointer.
      prd = (PPRDINFO3)pbQueue;

      /* Test for a PORT name first
      */
      for (i = 0; i < cReturned; i++)
      {
         if (0 == strcmp (prd->pszLogAddr, pszLogicalAddress))
         {
            pszDrivers = prd->pszDrivers;
            break;
         }
         prd++;
      }

      // The data returned by the spooler is valid.  Setup the pointer.
      prd = (PPRDINFO3)pbQueue;

      /* Test for a PRINTER name first
      */
      for (i = 0; i < cReturned; i++)
      {
         if (0 == strcmp (prd->pszPrinterName, pszLogicalAddress))
         {
            pszDrivers = prd->pszDrivers;
            break;
         }
         prd++;
      }

      /* If it was not a port name, then perhaps it is a filename.
      ** File names that are passed in are fully qualified -- look for a ':'
      */
      if (':' == pszLogicalAddress[1] &&
          !pszDrivers                  )
      {
         prd = (PPRDINFO3)pbQueue;            // Reset pointer

         for (i = 0; i < cReturned; i++)
         {
            if (0 == strcmp (prd->pszLogAddr, "FILE"))
            {
               pszDrivers = prd->pszDrivers;
               break;
            }
            prd++;
         }
      }
   }
   else if (ERROR_MORE_DATA == rc || NERR_BufTooSmall == rc)
   {
      PSZ    pszDevice = NULL;
      ULONG  ulSize;

      // Must be a queue
      cbBytes = cbNeeded;

      // Allocate some memory
      pbQueue = (PBYTE)GplMemoryAlloc (hmcbHeap, cbNeeded);
      if (!pbQueue)
      {
         GplErrSetError (PMERR_INSUFFICIENT_MEMORY);
         return;
      }

      rc = SplQueryQueue (pszComputerName,       // Computer name
                          pszLogicalAddress,     // Queue Name
                          3,                     // Level
                          pbQueue,               // Buffer
                          cbBytes,               // is size
                          &cbNeeded);            // # bytes needed
      if (rc)
         // Error!
         return;

      /* Now that we have information about the queue.  All we need here
      ** is the device name.
      */
      prq = (PPRQINFO3)pbQueue;
      ulSize = lstrlen (prq->pszPrinters)+1;
      pszDevice = GplMemoryAlloc (hmcbHeap, ulSize);
      if (pszDevice)
         strcpy (pszDevice, prq->pszPrinters);
      else
      {
         // Error!
         GplErrSetError (PMERR_INSUFFICIENT_MEMORY);
         return;
      }

      /* This can be a comma seperated list of printers so remove the first
      ** comma!  Why do we take the first one?  Because each of the printers
      ** should have exactly the same information (courtesy of the spooler).
      */
      if ((pszComma = strchr (pszDevice, ',')) != (PSZ)NULL)
      {
         *pszComma = 0;
      }

      // Free the memory.  We are done with it now.
      GplMemoryFree ((PVOID)pbQueue);
      pbQueue = NULL;
      cbBytes = 0;

      rc = SplQueryDevice (pszComputerName,      // Computer name
                           pszDevice,            // Printer name
                           3,                    // Level
                           pbQueue,              // Buffer
                           cbBytes,              // Size of buffer
                           &cbNeeded);           // # bytes needed

      if (ERROR_MORE_DATA == rc || NERR_BufTooSmall == rc)
      {
         // Allocate the memory
         cbBytes = cbNeeded;
         pbQueue = (PBYTE)GplMemoryAlloc (hmcbHeap, cbNeeded);
         if (!pbQueue)
            // Error!
            return;

         rc = SplQueryDevice (pszComputerName,   // Computer name
                              pszDevice,         // Printer name
                              3,                 // Level
                              pbQueue,           // Buffer
                              cbBytes,           // Size of buffer
                              &cbNeeded);        // # bytes needed
         if (rc)
            // Error!
            return;
      }
      else if (rc)
         // Error!
         return;

      prd = (PPRDINFO3)pbQueue;
      if (0 == strcmp (prd->pszPrinterName, pszDevice))
         pszDrivers = prd->pszDrivers;

      // Clean up
      if (pszDevice)
         GplMemoryFree ((PVOID)pszDevice);
   }
   else if (rc)
      // Error!
      return;

   if (pszDrivers)
   {
      PSZ  pszPrinter, pszDriver, pszDevicename;
      BOOL fDriverFound;

      /* Here, we are given a list of printer drivers '.' device name.
      ** We have to go through each element in the list and see if the
      ** printer driver is us and the device name is ours.
      */
      pszPrinter = pszDrivers;
      while (*pszPrinter)
      {
         fDriverFound = FALSE;
         pszDriver = pszPrinter;
         while (*pszPrinter)
         {
            if ('.' == *pszPrinter)
               break;
            pszPrinter++;
         }
         if ('.' == *pszPrinter)
         {
            fDriverFound  = TRUE;
            *pszPrinter   = 0;
            pszPrinter++;
            pszDevicename = pszPrinter;
         }
         if (fDriverFound && *pszDevicename)
         {
            /* See if there is another driver after this one.  If so then
            ** remove the comma.
            */
            while (*pszPrinter && ',' != *pszPrinter)
               pszPrinter++;
            if (',' == *pszPrinter)
            {
               *pszPrinter = 0;
               pszPrinter++;
            }

            if (0 == strcmp (pszDriver, APP_NAME))
            {
               /* Its a match.  We are going to take the first one that
               ** we find.  Why??? Because we aren't mind readers.
               ** If we are in this code and we are looking at the following
               ** example:
               **    PLOTTERS.HP7586B,EPSON.LQ-2550,PLOTTERS.HP7596A
               ** we can ignore all other printer drivers but our own.
               ** However, if there is more than one PLOTTERS we have no
               ** way of determining which device the app really wants.
               */
               if (IsIta_Device_Name (pszDevicename))
               {
                  strcpy (pszDeviceName, pszDevicename);
                  *pszPrinter = 0;
                  *pszDriver  = 0;
               }
            }
         }
      }
   }

   // Clean up
   // Free the memory.  We are done with it now.
   if (pbQueue)
      GplMemoryFree ((PVOID)pbQueue);
   if (pszTemp)
      GplMemoryFree ((PVOID)pszTemp);
}

/***************************************************************************
 *
 * FUNCTION NAME = find_printer_name
 *
 * DESCRIPTION   = This function takes a logical address (spooler printer name,
 *                 spooler device name. port name, or a UNC name) and returns
 *                 the string containing the spooler's printer name.  It is up
 *                 to the caller to free this string (it is allocated off of
 *                 the heap).
 *
 * INPUT         = hmcbHeap              - A heap handle
 *                 pszDeviceName       - The device name (used if the logical
 *                                       address is a file name)
 *                 pszLogicalAddress   - The logical address
 *                 fDevPostDeviceModes - Is DevPostDeviceNames calling us?
 *                                       We need a different strategy if so.
 *
 * OUTPUT        = PSZ - A string containing the spooler printer name.
 *                 If an error occured, then NULL.
 *
 *****************************************************************************/
PSZ PLOTOPT
find_printer_name (PVOID hmcbHeap,
                   PSZ   pszDeviceName,
                   PSZ   pszLogicalAddress,
                   BOOL  fDevPostDeviceModes)
{
   SPLERR       rc;
   PBYTE        pbQueue         = NULL;
   ULONG        cbBytes;
   ULONG        cbNeeded;
   ULONG        cReturned;
   ULONG        cTotal;
   PPRQINFO3    prq;
   PPRDINFO3    prd;
   PSZ          pszPrinter      = NULL;
   PSZ          pszRet          = NULL;
   PSZ          pszComputerName = NULL;
   PSZ          pszTemp         = NULL;
   PSZ          pszComma;
   register INT i;

   cbNeeded = 0;
   cbBytes  = 0;

   /* We have a problem because the spooler's printer name can be the same
   ** as it's device name.  That is why we need to know who is calling us.
   ** For the DevPostDeviceModes call, we are given a device name.  And,
   ** in the spooled case, we are given a queue name.
   */
   if (fDevPostDeviceModes)
   {
      /* See if it is a valid device name */
      rc = SplQueryDevice ((PSZ)NULL,           // Computer name
                           pszLogicalAddress,   // Device Name
                           3,                   // Level
                           pbQueue,             // Buffer
                           cbBytes,             // is size
                           &cbNeeded);          // # bytes needed
      if (ERROR_MORE_DATA == rc || NERR_BufTooSmall == rc)
      {
         /* It is a valid device name */
         pszRet = (PSZ)GplMemoryAlloc (hmcbHeap, strlen (pszLogicalAddress)+1);
         if (pszRet)
         {
            strcpy (pszRet, pszLogicalAddress);
            return pszRet;
         }
         else
            // Error!
            return NULL;
      }
      /* It wasn't a valid device name... Fall through to see if it might
      ** be a queue name instead.
      */
   }

   if ('\\' == pszLogicalAddress[0] && '\\' == pszLogicalAddress[1])
   {
      register int i;

      /* This is a network print queue in the form of \\server\queue
      */
      pszTemp = (PSZ)GplMemoryAlloc (hmcbHeap, strlen (pszLogicalAddress)+1);
      if (pszTemp)
         strcpy (pszTemp, pszLogicalAddress);
      else
         // Error!
         return NULL;

     /* Set up pointers ... */
     pszComputerName = pszTemp;
     /* Get rid of the queue name in the \\server\queue string */
     for (i = 2; pszComputerName[i]; i++)
        if ('\\' == pszComputerName[i])
        {
           pszComputerName[i] = 0;
           pszLogicalAddress = pszComputerName+i+1;
           break;
        }

     /* pszComputerName   should now be \\server
     ** pszLogicalAddress should now be queue
     */
   }

   /* See if it is a queue name.  If not then it is a port name.
   */
   rc = SplQueryQueue (pszComputerName,     // Computer name
                       pszLogicalAddress,   // Queue Name
                       3,                   // Level
                       pbQueue,             // Buffer
                       cbBytes,             // is size
                       &cbNeeded);          // # bytes needed
   if (ERROR_MORE_DATA == rc || NERR_BufTooSmall == rc)
   {
      // Must be a queue
      // Allocate some memory
      cbBytes = cbNeeded;
      pbQueue = (PBYTE)GplMemoryAlloc (hmcbHeap, cbNeeded);
      if (!pbQueue)
         // Error!
         return;

      rc = SplQueryQueue (pszComputerName,     // Computer name
                          pszLogicalAddress,   // Queue Name
                          3,                   // Level
                          pbQueue,             // Buffer
                          cbBytes,             // is size
                          &cbNeeded);          // # bytes needed
      if (rc)
         // Error!
         return;

      prq = (PPRQINFO3)pbQueue;
      pszPrinter = prq->pszPrinters;

      /* This can be a comma seperated list of printers so remove the first
      ** comma!  Why do we take the first one?  Because each of the printers
      ** should have exactly the same information (courtesy of the spooler).
      */
      if ((pszComma = strchr (pszPrinter, ',')) != (PSZ)NULL)
      {
         *pszComma = 0;
      }
   }
   else if (NERR_QNotFound == rc)
   {
      // Must be a port
      cbNeeded = 0;
      cbBytes  = 0;

      // Find out how much memory to allocate
      rc = SplEnumDevice ((PSZ)NULL,    // Computer name
                          3,            // Level
                          pbQueue,      // Buffer
                          cbBytes,      // is size
                          &cReturned,   // Number returned
                          &cTotal,      // Total available
                          &cbNeeded,    // # bytes needed
                          (PVOID)NULL); // Reserved
      if (ERROR_MORE_DATA == rc || NERR_BufTooSmall == rc)
      {
         // Allocate the memory
         cbBytes = cbNeeded;
         pbQueue = (PBYTE)GplMemoryAlloc (hmcbHeap, cbNeeded);
         if (!pbQueue)
            // Error!
            return;

         rc = SplEnumDevice (pszComputerName,// Computer name
                             3,              // Level
                             pbQueue,        // Buffer
                             cbBytes,        // is size
                             &cReturned,     // Number returned
                             &cTotal,        // Total available
                             &cbNeeded,      // # bytes needed
                             (PVOID)NULL);   // Reserved
         if (rc)
            // Error!
            return;
      }
      else if (rc)
         // Error!
         return;

      prd = (PPRDINFO3)pbQueue;

      /*
      ** First we should check if the logical address is null
      */
      if (!pszLogicalAddress ||
          (!pszLogicalAddress[0] &&
           !pszPrinter))
      {
         BOOL  fExit = FALSE;
         CHAR  achTest[100];

         // Build a test string
         sprintf (achTest, "%s.%s", APP_NAME, pszDeviceName);

         for (i = 0; !fExit && i < cReturned; i++)
         {
            BOOL  fContinue = TRUE;

            pszComma = strchr (prd->pszDrivers, ',');
            if (pszComma)
               *pszComma = 0;
            else
               fContinue = FALSE;

            do
            {
               if (0 == strcmp (achTest, prd->pszDrivers))
               {
                  pszPrinter = prd->pszPrinterName;
                  fExit = TRUE;
                  break;
               }

               if (pszComma)
               {
                  prd->pszDrivers = pszComma+1;
                  pszComma = strchr (prd->pszDrivers, ',');
                  if (pszComma)
                     *pszComma = 0;
               }
               else
                  fContinue = FALSE;
            } while (fContinue);
            prd++;
         }
      }
      /*
      ** Next Test for a PORT name first
      */
      else
      {
        for (i = 0; i < cReturned; i++)
        {
           if (0 == strcmp (prd->pszLogAddr, pszLogicalAddress))
           {
              pszPrinter = prd->pszPrinterName;
              break;
           }
           prd++;
        }
      }
      /* If it was not a port name, then perhaps it is a filename.
      ** File names that are passed in are fully qualified -- look for a ':'
      */
      if (':' == pszLogicalAddress[1] &&
          !pszPrinter                  )
      {
         BOOL  fExit = FALSE;

         prd = (PPRDINFO3)pbQueue;            // Reset pointer

         // FILE name
         for (i = 0; !fExit && i < cReturned; i++)
         {
            if (0 == strcmp (prd->pszLogAddr, "FILE"))
            {
               BOOL  fContinue = TRUE;
               CHAR  achTest[100];

               // Build a test string
               sprintf (achTest, "%s.%s", APP_NAME, pszDeviceName);

               pszComma = strchr (prd->pszDrivers, ',');
               if (pszComma)
                  *pszComma = 0;
               else
                  fContinue = FALSE;

               do
               {
                  if (0 == strcmp (achTest, prd->pszDrivers))
                  {
                     pszPrinter = prd->pszPrinterName;
                     fExit = TRUE;
                     break;
                  }

                  if (pszComma)
                  {
                     prd->pszDrivers = pszComma+1;
                     pszComma = strchr (prd->pszDrivers, ',');
                     if (pszComma)
                        *pszComma = 0;
                  }
                  else
                     fContinue = FALSE;
               } while (fContinue);
            }
            prd++;
         }
      }
   }
   else if (rc)
      // Error!
      return;

   if (pszPrinter)
   {
      pszRet = (PSZ)GplMemoryAlloc (hmcbHeap, strlen (pszPrinter)+1);
      if (pszRet)
         strcpy (pszRet, pszPrinter);
   }

   // Clean up
   // Free the memory.  We are done with it now.
   if (pbQueue)
      GplMemoryFree ((PVOID)pbQueue);
   if (pszTemp)
      GplMemoryFree ((PVOID)pszTemp);

   return pszRet;
}

/***************************************************************************
 *
 * FUNCTION NAME = function: ConvertWithRatation
 *
 * DESCRIPTION   = Converts an X length and a Y length accounting
 *                 for rotation. MV
 * NOTE!!!!!!!!!
 *
 *
 * INPUT         = HDC   hDC    - DC handle
 *                 PDDC  pDDC   - Divice info block
 *                 BOOL  bWorldToDevice - Convert world to device
 *                 PLONG pLXlen - Pointer to a long X length
 *                 PLONG pLYlen - Pointer to a long Y length
 *
 * OUTPUT        = UPDATES the the x and y lengths with the
 *                 world to device converted values
 *
 * RETURN-NORMAL = TRUE = SUCCESS
 * RETURN-ERROR  = FALSE = FAIL
 *
 **************************************************************************/
BOOL PLOTOPT
ConvertWithRotation (HDC hDC, PDDC pDDC, BOOL bWorldToDevice,
                     PLONG plXLen, PLONG plYLen)
{
    POINTL  ptlConv[ 3 ];

    ptlConv[0].x = 0;
    ptlConv[0].y = 0;
    ptlConv[1].x = 0;
    ptlConv[1].y = *plYLen;
    ptlConv[2].x = *plXLen;
    ptlConv[2].y = 0;

    if (bWorldToDevice)
    {
      if (!convert_world_to_device(hDC, &ptlConv[0], 3L, pDDC))
      { /* if error */
        return(FALSE);
      }
    }
    else /* device to world */
    {
      if (!convert_device_to_world(hDC, &ptlConv[0], 3L, pDDC))
      { /* if error */
        return(FALSE);
      }
    }


    ptlConv[1].x -= ptlConv[0].x;
    ptlConv[1].y -= ptlConv[0].y;

    ptlConv[2].x -= ptlConv[0].x;
    ptlConv[2].y -= ptlConv[0].y;

    // check for rotation
    if (ptlConv[1].x)
    {
       *plYLen = (LONG) (( f_lsqrt(ptlConv[1].x * ptlConv[1].x +
                                 ptlConv[1].y * ptlConv[1].y)  +
                                  32768) >> 16 );
    }
    else
    {
       *plYLen = (LONG) ABS(ptlConv[1].y);
    }

    // check for rotation
    if (ptlConv[2].y)
    {
       *plXLen = (LONG) (( f_lsqrt(ptlConv[2].x * ptlConv[2].x +
                                 ptlConv[2].y * ptlConv[2].y)  +
                                   32768) >> 16 );
    }
    else
    {
       *plXLen = (LONG) ABS(ptlConv[2].x);
    }

    return ( TRUE );

}
