/**************************************************************************
 * OS/2 Sample Print Application PRTSAMP
 *
 * Name: prtobj.c
 *
 * Description:   The object window procedure on thread 2.
 *
 *             Tasks asked of the object window are not bound by 1/10
 *             second rule.  Tasks are given to the object window to
 *             perform via WM_USER_* messages.
 *
 *             When tasks are completed, the object window posts a
 *             WM_USER_ACK message to the window that requested the task.
 *
 *             This source file contains the following functions:
 *
 *             threadmain(pv)
 *             ObjectWinProc(hwnd, msg, mp1, mp2)
 *             CalculateDrawingArea(pmp, flag, pptlBegin, pptlEnd)
 *             DrawBorder(hps, pmp, ptlBegin, ptlEnd)
 *             FixedPointsToTwips(fxPointSize)
 *             FixedInchesToTwips(fxInch)
 *             LoadBitmap( pmp, flag )
 *             MMToTwips(lMM)
 *             PaintBitmap(pmp, flag)
 *             PaintDefaultScreen(pmp, flag)
 *             PaintMetaFile(pmp, flag)
 *             PaintText(pmp, flag)
 *             SetClipPath(hps, pmp, ptlBegin, ptlEnd)
 *             trim(s)
 *
 * Concepts:   second thread, object window
 *
 * API's:       WinInitialize
 *              WinCreateMsgQueu
 *              WinRegisterClass
 *              WinCreateWindow
 *              WinPostMsg
 *              WinGetMsg
 *              WinDispatchMsg
 *              WinDestroyWindow
 *              WinDestroyMsgQueue
 *              WinTerminate
 *              WinQueryAnchorBlock
 *              WinQueryWindowULong
 *              WinSetWindowULong
 *              DosOpen
 *              DosQueryFileInfo
 *              DosRead
 *              DosClose
 *              DevOpenDC
 *              GpiCreatePS
 *              GpiCreateBitmap
 *              GpiSetBitmap
 *              GpiWCBitBlt
 *              GpiQueryDefaultViewMatrix
 *              GpiSetDrawControl
 *              GpiResetBoundaryData
 *              GpiPlayMetaFile
 *              GpiScale
 *              GpiTranslate
 *              GpiSetViewingTransformMatrix
 *              GpiResetPS
 *              GpiSetBitmapBits
 *              WinInvalidateRect
 *              GpiLoadMetaFile
 *              WinSendMsg
 *              GpiSetDrawingMode
 *              WinGetLastError
 *              DosSleep
 *              GpiErase
 *              GpiDeleteSegment
 *              GpiOpenSegment
 *              GpiSetCurrentPosition
 *              GpiBox
 *              GpiSetColor
 *              GpiCharString
 *              GpiCloseSegment
 *              WinQueryWindowRect
 *              GpiQueryBitmapParameters
 *              GpiBitBlt
 *              GpiQueryPS
 *              GpiSetPS
 *              WinDefWindowProc
 *              GpiSetCharSet
 *              GpiDeleteSetId
 *              GpiCreateLogFont
 *              GpiQueryFontMetrics
 *              GpiSetCharBox
 *
 *   Files    :  OS2.H, PRTSAMP.H, PRTSDLG.H, PMASSERT.H
 *
 *  Copyright (C) 1991-1993 IBM Corporation
 *
 *      DISCLAIMER OF WARRANTIES.  The following [enclosed] code is
 *      sample code created by IBM Corporation. This sample code is not
 *      part of any standard or IBM product and is provided to you solely
 *      for  the purpose of assisting you in the development of your
 *      applications.  The code is provided "AS IS", without
 *      warranty of any kind.  IBM shall not be liable for any damages
 *      arising out of your use of the sample code, even if they have been
 *      advised of the possibility of such damages.                                                    *
 *
 ****************************************************************************/

/* os2 includes */
#define INCL_DEV
#define INCL_DOSFILEMGR
#define INCL_DOSPROCESS
#define INCL_GPIBITMAPS
#define INCL_GPICONTROL
#define INCL_GPICORRELATION
#define INCL_GPIERRORS
#define INCL_GPILCIDS
#define INCL_GPIMETAFILES
#define INCL_GPIPATHS
#define INCL_GPIPRIMITIVES
#define INCL_GPISEGMENTS
#define INCL_GPITRANSFORMS
#define INCL_SPL
#define INCL_SPLDOSPRINT
#define INCL_WINDIALOGS
#define INCL_WINERRORS
#define INCL_WINMLE
#define INCL_WINSTDFILE
#define INCL_WINSTDFONT
#define INCL_WINWINDOWMGR
#include <os2.h>

/* c language includes */
#include <ctype.h>
#include <memory.h>
#include <process.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys\stat.h>
#include <sys\types.h>

/* application includes */
#include "pmassert.h"
#include "prtsamp.h"
#include "prtsdlg.h"
#include "prtshlp.h"

/* local function prototypes */

VOID CalculateDrawingArea( PMAIN_PARM pmp, ULONG flag,
                           PPOINTL pptlBegin, PPOINTL pptlEnd );
VOID DrawBorder( HPS hps, PMAIN_PARM pmp, POINTL ptlBegin, POINTL ptlEnd );
ULONG LoadBitmap( PMAIN_PARM pmp, ULONG flag );
LONG MMToTwips( LONG lMM );
VOID PaintDefaultScreen( PMAIN_PARM pmp, ULONG flag );
ULONG PaintText( PMAIN_PARM pmp, ULONG flag );
VOID SetClipPath( HPS hps, PMAIN_PARM pmp, POINTL ptlBegin, POINTL ptlEnd );


/*************************************************************************
 *
 * Name: threadmain
 *
 * Description: Similar in nature to main(), except this occurs on thread 2.
 *              Called by _beginthread() in prtcreat.c
 *
 * API's:       WinInitialize
 *              WinCreateMsgQueu
 *              WinRegisterClass
 *              WinCreateWindow
 *              WinPostMsg
 *              WinGetMsg
 *              WinDispatchMsg
 *              WinDestroyWindow
 *              WinDestroyMsgQueue
 *              WinTerminate
 *
 * Parameters: pv, a pointer to the main block of program parameters
 *
 * Return:  [none]
 *
 **************************************************************************/
void _Optlink threadmain( void *pv )
{

   PMAIN_PARM pmp;
   BOOL       bOK;
   HAB        hab;
   HMQ        hmq;
   QMSG       qmsg;

   /* copy and convert pvoid parmeter to a pointer to the main parameter block */
   pmp = (PMAIN_PARM) pv;

   /* thread initialization */
   hab = WinInitialize( 0 );
   hmq = WinCreateMsgQueue( hab, 0 );

   /*
    * ensure that object window does not receive a WM_QUIT on system shutdown
    */
   WinCancelShutdown(hmq, TRUE);

   bOK = WinRegisterClass( hab,
                                OBJECTCLASSNAME,
                                ObjectWinProc,
                                0L,
                                sizeof( PMAIN_PARM ) );
   pmassert( hab, bOK );

   /*
    * create a worker window where the parent is the PM object window,
    * it operates on thread 2,
    * has no visible windows on the desktop,
    * and is not bound by the 1/10 second message processing rule.
    */
   pmp->hwndObject = WinCreateWindow(
                       HWND_OBJECT,       /* parent */
                       OBJECTCLASSNAME,   /* class name */
                       "",                /* no caption needed */
                       0L,                /* style */
                       0L,                /* x */
                       0L,                /* y */
                       0L,                /* cx */
                       0L,                /* cy */
                       HWND_OBJECT,       /* owner */
                       HWND_BOTTOM,       /* position (nop) */
                       0L,                /* id (nop) */
                       (PVOID)pmp,        /* parms passed to wm_create case */
                       (PVOID)NULL);      /* presparams */

   pmassert( hab, pmp->hwndObject );

   /*
    * at this point, application has completely initialized
    * wm_create processing in the object window procedure has completed.
    * let client window know about it
    */
   bOK = WinPostMsg( pmp->hwndClient, WM_USER_ACK, (MPARAM)0L, (MPARAM)0L );
   pmassert( pmp->hab, bOK );


    /* The create processing put values in for next mode and next filename.
       Post this message to activate those new settings */
    bOK = WinPostMsg( pmp->hwndClient, WM_USER_NEW_MODE, 0, 0 );
    pmassert( pmp->hab, bOK );


   /*
    * dispatch messages; these messages will be mostly user-defined messages
    * processed on thread 2
    */
   while( WinGetMsg ( hab, &qmsg, (HWND)NULLHANDLE, 0L, 0L ))
   {
      WinDispatchMsg ( hab, &qmsg );
   }


   /* Use the new-mode processing to close current file before exiting */
   pmp->ulNextMode = MODE_UNKNOWN;
   WinPostMsg( pmp->hwndClient, WM_USER_NEW_MODE, 0, 0 );


   /* make thread one terminate */
   WinPostMsg( pmp->hwndClient, WM_QUIT, (MPARAM)0L, (MPARAM)0L );

   /* clean up */
   WinDestroyWindow( pmp->hwndObject );
   WinDestroyMsgQueue( hmq );
   WinTerminate( hab );

   /* end this thread */
   _endthread();

}  /*  end of threadmain()  */


/**************************************************************************
 *
 * Name: ObjectWinProc
 *
 * Description: a window procedure like most others, except it is not
 *              responsible for any visible windows or presentation. It
 *              exists to perform lengthy tasks on thread2 of the
 *              application.  WM_* messages appear here in alphabetical order.
 *
 * API's:       GpiLoadMetaFile
 *              WinDefWindowProc
 *              WinInvalidateRect
 *              WinPostMsg
 *              WinQueryWindowULong
 *              WinSendMsg
 *              WinSetWindowULong
 *              WinQueryAnchorBlock
 *
 * Parameters: mp1 = window handle to acknowledge upon completion of the task
 *             (hwndToAck)
 *             mp2 is an extra parameter and depends on the task
 *
 * returns: an acknowlegement of completion of the task using WinPostMsg
 *   if success: WinPostMsg( hwndToAck, WM_USER_ACK,  msg, rc );
 *               return rc;
 *
 *   if not:     WinPostMsg( hwndToAck, WM_NACK_*   , msg, rc );
 *               return rc;
 *
 *   where msg was the WM_USER_* message that was posted to the object window
 *   and rc is a return code. Depending on the hwndToAck, returning
 *   the result code can be as important as posting it; in particular,
 *   the object window may send synchronous messages to itself and may
 *   check the result code via the return code of WinSendMsg()
 *
 ***************************************************************************/
MRESULT EXPENTRY ObjectWinProc( HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2 )
{
   HAB                hab;
   HWND               hwndToAck;
   LONG               lRC;
   PMAIN_PARM         pmp;
   ULONG              nackmsg;


   /* store the handle of the window to ack upon task completion; */
   hwndToAck = (HWND)mp1;
   hab = WinQueryAnchorBlock( hwnd );
   pmp = (PMAIN_PARM) WinQueryWindowULong( hwnd, QWL_USER );


   switch( msg )
   {

   case WM_CREATE:
     /* mp1 is pointer to main paramters; save it in object window words */
     pmp = (PMAIN_PARM)mp1;
     WinSetWindowULong( hwnd, QWL_USER, (ULONG) pmp );

     /* do more startup processing whilst on thread 2 */

     /* Read .ini for filename, mode, printer, and driver data */
     GetProfile( pmp );

     /* if filename supplied, use it instead */
     if (pmp->szArgFilename && *pmp->szArgFilename)
     {
        ValidateFilename( pmp, pmp->szArgFilename, (HWND)NULLHANDLE);
     }
     /*
      * Try to set the print destination to that which was stored in INI.
      * Parm 2 == FALSE means do not display a dialog
      */
     QueryPrintQueue( pmp, FALSE );

     /*
      * Try to set the form to that which was stored in INI.
      * Parm 2 == FALSE means do not display a dialog
      */
     QueryForm( pmp, FALSE );
     return (MRESULT)NULL;


   case WM_USER_CLOSE:
     /* write settings to ini */
     SaveProfile(pmp);

     /*
      * post a quit to object window;
      * when msg loop falls out in threadmain, then post wm_quit to client
      */
     WinPostMsg( hwnd, WM_QUIT, 0, 0 );
     break;


   case WM_USER_LOAD_BITMAP:
     /*
      * THIS MESSAGE HAS A PARAMETER:
      * mp2 is a flag that indicates whether the memory ps for the bitmap
      * is to be compatible with the screen or the current printer setup
      */


     /* load new bitmap */
     nackmsg = LoadBitmap( pmp, (ULONG)mp2 );
     if (! nackmsg )
     {
        WinPostMsg( hwndToAck, nackmsg, (MPARAM)msg, (MPARAM)0L );
     }

     /* invalidate client area to force a paint */
     WinInvalidateRect( pmp->hwndClient, NULL, FALSE );

     /* done loading bitmap */
     WinPostMsg( hwndToAck, WM_USER_ACK, (MPARAM)msg, (MPARAM)0L );
     return (MRESULT)TRUE;


   case WM_USER_LOAD_METAFILE:
     /* load new metafile */
     pmp->hmf = GpiLoadMetaFile( hab, pmp->szFilename );
     if( GPI_ERROR  == pmp->hmf )
     {
       /* load error */
       pmp->hmf  = (HMF)NULLHANDLE;
       WinPostMsg( hwndToAck, WM_NACK_FILE_READING_ERROR,
                       (MPARAM)msg, (MPARAM)0L );
       return (MRESULT)NULL;
     }

     /* invalidate client area to force a paint */
     WinInvalidateRect( pmp->hwndClient, NULL, FALSE );

     /* done loading metafile */
     WinPostMsg( hwndToAck, WM_USER_ACK, (MPARAM)msg, (MPARAM)0L );
     return (MRESULT)NULL;


   case WM_USER_LOAD_TEXT:
     pmp->f = fopen( pmp->szFilename, "r" );
     if( !pmp->f )
     {
       WinPostMsg( hwndToAck, WM_NACK_FILE_READING_ERROR,
                                 (MPARAM)msg, (MPARAM)0L );
       return (MRESULT)NULL;
     }

     /* has user visited the font dialog? */
     if( pmp->fontdlg.fAttrs.usRecordLength == (USHORT)0 )
     {
       /* no font selected yet; chain to font selection dialog now
        * after user selects a font (prtmenu.c), the program invokes
        * the pagination routine                                     */
       WinPostMsg( pmp->hwndClient, WM_COMMAND, (MPARAM)IDM_SETFONT, (MPARAM)0L);
     }
     else
     {
       /* a font is already selected; invoke the pagination routine  */
       WinSendMsg( hwnd, WM_USER_PAGINATE, (MPARAM)hwnd,
                   (MPARAM)FLAGS_SCREEN );
     }

     /* done loading text */
     WinPostMsg( hwndToAck, WM_USER_ACK, (MPARAM)msg, (MPARAM)0L );
     return (MRESULT)NULL;


   case WM_USER_PAGINATE:
     /* draw text into presentation space */
     lRC = PaintText( pmp, (ULONG)mp2 );
     /* ack and return with result code */
     WinPostMsg( hwndToAck, WM_USER_ACK,(MPARAM)msg,(MPARAM)lRC );
     return (MRESULT) lRC;


   case WM_USER_PAINT_DEFAULT_SCREEN:
     /* draw default screen into presentation space */
     PaintDefaultScreen( pmp, FLAGS_SCREEN );
     /* ack and return with result code */
     WinPostMsg( hwndToAck, WM_USER_ACK, (MPARAM)msg, 0 );
     return (MRESULT)NULL;

   case WM_USER_PAINT_BITMAP:
     /* draw bitmap into presentation space */
     PaintBitmap( pmp, FLAGS_SCREEN );
     /* ack and return with result code */
     WinPostMsg( hwndToAck, WM_USER_ACK, (MPARAM)msg, 0 );
     return (MRESULT)NULL;


   case WM_USER_PAINT_METAFILE:
     /* Play metafile into client area */
     PaintMetaFile( pmp, FLAGS_SCREEN );
     /* ack and return with result code */
     WinPostMsg( hwndToAck, WM_USER_ACK, (MPARAM)msg, (MPARAM)0L );
     return (MRESULT)NULL;


   case WM_USER_PRINT:
     ProcessUserPrint(hwnd, pmp);
     WinPostMsg( hwndToAck, WM_USER_ACK, (MPARAM)msg, (MPARAM)0L );
     return (MRESULT)NULL;
   }

   /* default: */
   return WinDefWindowProc( hwnd, msg, mp1, mp2 );
}  /* end of ObjectWinProc() */


/*************************************************************************
 *
 * name: CalculateDrawingArea
 *
 * Description:  Calculate the drawing area of the screen or printer
 *
 * API's:  [none]
 *
 * Parameters: PMAIN_PARM  pointer to the global data structure
 *             ULONG       flag to indicate screen or printer
 *             PPOINTL     pointer to begin point of drawing area
 *             PPOINTL     pointer to end point of drawing area
 *
 * Return: [none]
 *
 **************************************************************************/
VOID CalculateDrawingArea( PMAIN_PARM pmp, ULONG flag,
                           PPOINTL pptlBegin, PPOINTL pptlEnd )
{
     /* define drawing area on screen or printer */
     pptlBegin->x = FixedInchesToTwips(pmp->form.fxLeftMargin);
     pptlBegin->y = FixedInchesToTwips(pmp->form.fxBottomMargin);
     pptlEnd->x = pptlBegin->x + pmp->sizelPage.cx;
     pptlEnd->y = pptlBegin->y + pmp->sizelPage.cy;
     if (flag != FLAGS_SCREEN)
     {
        /* take hardware clip limits into account */
        pptlBegin->x -= MMToTwips( pmp->hcinfo[pmp->iForm].xLeftClip );
        pptlBegin->x = max( pptlBegin->x, 0 );
        pptlBegin->y -= MMToTwips( pmp->hcinfo[pmp->iForm].yBottomClip );
        pptlBegin->y = max( pptlBegin->y, 0 );
        pptlEnd->x -= MMToTwips( pmp->hcinfo[pmp->iForm].xLeftClip );
        pptlEnd->y -= MMToTwips( pmp->hcinfo[pmp->iForm].yBottomClip );
     }
} /* end of CalculateDrawingArea */


/*************************************************************************
 *
 * name: DrawBorder
 *
 * Description:  Clears drawing area and draws form border
 *
 * API's:  [none]
 *
 * Parameters:  HPS         presentation space to use
 *              PMAIN_PARM  pointer to the global data structure
 *              POINTL      pointer to begin point of drawing area
 *              POINTL      pointer to end point of drawing area
 *
 * Return: [none]
 *
 **************************************************************************/
VOID DrawBorder( HPS hps, PMAIN_PARM pmp, POINTL ptlBegin, POINTL ptlEnd )
{
   BOOL     bOK;
   ULONG    lRC;
   HAB      hab;
   POINTL   pointl;

   hab = pmp->hab;

   /* white out area of presentation space */
   bOK = GpiSetColor( hps, CLR_WHITE );
   pmassert( hab, bOK );
   bOK = GpiSetPattern( hps, PATSYM_SOLID );
   pmassert( hab, bOK );
   pointl.x = -65535;
   pointl.y = -65535;
   bOK = GpiSetCurrentPosition( hps, &pointl );
   pmassert( hab, bOK );
   pointl.x = 65535;
   pointl.y = 65535;
   lRC = GpiBox( hps, DRO_FILL, &pointl, 0L, 0L );
   pmassert( hab, lRC != GPI_ERROR );

   /* draw margins of form as a red border */
   bOK = GpiBeginPath( hps, 1);
   pmassert( hab, bOK );
   bOK = GpiSetColor( hps, CLR_RED);
   pmassert( hab, bOK );
   bOK = GpiSetPattern( hps, PATSYM_DENSE4 );
   pmassert( hab, bOK );
   pointl.x = 0;
   pointl.y = 0;
   bOK = GpiSetCurrentPosition( hps, &pointl );
   pmassert( hab, bOK );
   pointl.x = FixedInchesToTwips(pmp->form.fxxWidth);
   pointl.y = FixedInchesToTwips(pmp->form.fxyHeight);
   lRC = GpiBox( hps, DRO_OUTLINE, &pointl, 0L, 0L );
   pmassert( hab, lRC != GPI_ERROR );
   bOK = GpiSetCurrentPosition( hps,  &ptlBegin );
   pmassert( hab, bOK );
   lRC = GpiBox( hps, DRO_OUTLINE, &ptlEnd, 0L, 0L );
   pmassert( hab, lRC != GPI_ERROR );
   bOK = GpiEndPath( hps );
   pmassert( hab, bOK );
   bOK = GpiFillPath( hps, 1L, FPATH_ALTERNATE );
   pmassert( hab, bOK );

   /* draw a black box around the page */
   bOK = GpiSetColor( hps, CLR_BLACK);
   pmassert( hab, bOK );
   pointl.x = 0;
   pointl.y = 0;
   bOK = GpiSetCurrentPosition( hps, &pointl );
   pmassert( hab, bOK );
   pointl.x = FixedInchesToTwips(pmp->form.fxxWidth);
   pointl.y = FixedInchesToTwips(pmp->form.fxyHeight);
   lRC = GpiBox( hps, DRO_OUTLINE, &pointl, 0L, 0L );
   pmassert( hab, lRC != GPI_ERROR );

} /* end of DrawBorder */


/*************************************************************************
 *
 * name: FixedInchesToTwips
 *
 * Description:  converts inches to TWIPS
 *
 * API's:  [none]
 *
 * Parameters:  fxInch = the size in inches as a fixed number
 *
 * Return:  Twips as a ULONG number
 *
 **************************************************************************/
ULONG FixedInchesToTwips(FIXED fxInch)
{
    ULONG    integer;
    ULONG    fraction;

    integer = FIXEDINT(fxInch) * 1440L;
    fraction = FIXEDFRAC(fxInch);
    fraction = (ULONG)(((LONG)fraction) * 1440L / 0x10000);
    return (integer+fraction);

} /*  end of FixedInchesToTwips()  */


/*************************************************************************
 *
 * name: FixedPointsToTwips
 *
 * Description:  converts a point size
 *
 * API's:  [none]
 *
 * Parameters:  fxPointSize = the point size as a fixed number
 *
 * Return:  Twips as a fixed number
 *
 **************************************************************************/
FIXED FixedPointsToTwips(FIXED fxPointSize)
{
    USHORT      integer;
    USHORT      fraction;

    integer = FIXEDINT(fxPointSize);
    integer *= 20;
    fraction = FIXEDFRAC(fxPointSize);
    fraction = (USHORT)(((LONG)fraction) * 20L / 0x10000);
    integer += fraction;

    return MAKEFIXED(integer, 0);
} /*  end of FixedPointsToTwips()  */


/*************************************************************************
 *
 * name: LoadBitmap
 *
 * Description:  loads a bitmap into a screen or printer PS
 *
 * API's:  [none]
 *
 * Parameters: PMAIN_PARM  pointer to the global data structure
 *             ULONG       flag to indicate screen or printer
 *
 * Return:  0 is sucessful otherwise a NACK message
 *
 **************************************************************************/
ULONG LoadBitmap( PMAIN_PARM pmp, ULONG flag )
{
   FILESTATUS3        filestatus3;
   HBITMAP            hbm;
   HDC                hdcCompatible;
   HFILE              hfile;
   PBITMAPFILEHEADER2 pbmpfileheader2;
   PBITMAPFILEHEADER2 pbmpfileheader2Work;
   PBITMAPINFOHEADER2 pbmp2;
   PBYTE              pBits;
   PBYTE              pBitsAligned;
   PHBITMAP           phbm;
   PHDC               phdc;
   PHPS               phps;
   SIZEL              sizel;
   ULONG              rc, ulAction, cBytes, cScans;
   ULONG              cbImageData;
   LONG               lRC;
   HAB                hab;

   hab = pmp->hab;

   /* open bitmap file */
   rc = DosOpen(  pmp->szFilename,
                  &hfile,
                  &ulAction,
                  0L,
                  FILE_NORMAL,
                  FILE_OPEN,
                  OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE,
                  NULL );
   if( rc != 0 )
   {
     return WM_NACK_FILE_READING_ERROR;
   }

   /* query file size; returned in filestatus3.cbFile */
   rc = DosQueryFileInfo( hfile, FIL_STANDARD,
                                 (PVOID)&filestatus3, sizeof( filestatus3 ));
   pmassert( hab, rc == 0 );

   /* alloc memory for bitmap file read */
   pbmpfileheader2 = (PBITMAPFILEHEADER2) malloc( filestatus3.cbFile );
   pmassert( hab, pbmpfileheader2 );

   /* read bitmap file into memory */
   rc = DosRead( hfile, (PVOID)pbmpfileheader2, filestatus3.cbFile, &cBytes );
   if( rc != 0  ||  cBytes == 0 )
   {
     free( pbmpfileheader2 );
     DosClose( hfile );
     return WM_NACK_FILE_READING_ERROR;
   }

   DosClose( hfile );

   switch( pbmpfileheader2->usType )
   {
   case BFT_BITMAPARRAY:
     /* default to the first bitmap in the array */
     pbmpfileheader2Work = &((PBITMAPARRAYFILEHEADER2)pbmpfileheader2)->bfh2;
     pmassert( hab, pbmpfileheader2Work );
     pbmp2 = &pbmpfileheader2Work->bmp2;
     cbImageData = filestatus3.cbFile - pbmpfileheader2Work->offBits;
     pBits = (PBYTE)pbmpfileheader2 + pbmpfileheader2Work->offBits;
     break;

   case BFT_BMAP:
     /* set pbmp2 to point to the BITMAPINFO2 structure within the file */
     pbmp2 = &pbmpfileheader2->bmp2;
     cbImageData = filestatus3.cbFile - pbmpfileheader2->offBits;
     pBits = (PBYTE)pbmpfileheader2 + pbmpfileheader2->offBits;
     break;

   case BFT_ICON:
   case BFT_POINTER:
   case BFT_COLORICON:
   case BFT_COLORPOINTER:
     /* not supported by print sample */
     free( pbmpfileheader2 );
     return WM_NACK_BITMAP_NOT_SUPPORTED;

   default:
     /* invalid file format */
     free( pbmpfileheader2 );
     return WM_NACK_BITMAP_ERROR;
   }

   /* pBits should be sensible */
   pmassert( hab, pbmp2 );
   pmassert( hab, pBits );

   /* open a new memory dc with compatibility as required per mp2 argument */
   switch( flag )
   {
   case FLAGS_SCREEN:
     hdcCompatible = pmp->hdcClient;
     phdc          = &pmp->hdcMemory;
     phps          = &pmp->hpsMemory;
     phbm          = &pmp->hbm;
     break;

   case FLAGS_PRINTER:
     hdcCompatible = pmp->hdcPrinter;
     phdc          = &pmp->hdcMem4Printer;
     phps          = &pmp->hpsMem4Printer;
     phbm          = &pmp->hbm4Printer;
     break;

   default:
     /* this assert will fail and remind the programmer of a problem */
     pmassert( hab, NULL == "expected FLAGS_*" );
   }


   /* previous uses of hbm, hps, and hdc are properly closed and tidy */
   pmassert( hab, *phps == (HPS)NULLHANDLE );
   pmassert( hab, *phdc == (HDC)NULLHANDLE );
   pmassert( hab, *phbm == (HBITMAP)NULLHANDLE );


   *phdc = DevOpenDC(pmp->hab, OD_MEMORY, "*",0, NULL, hdcCompatible);
   pmassert( hab, *phdc );

   /* create the ps in memory to hold the bitmap */
   sizel.cx = 0;
   sizel.cy = 0;
   *phps = GpiCreatePS( pmp->hab, *phdc, &sizel,
                        PU_PELS | GPIA_ASSOC | GPIT_NORMAL );
   pmassert( hab, *phps );

   /* create a bitmap */
   *phbm = GpiCreateBitmap( *phps,
                            pbmp2,
                            0,                  /* options */
                            (PBYTE)NULL,        /* init table */
                            (PBITMAPINFO2)NULL );  /* bitmap info */

   /* check that bitmap has been created properly */
   if (*phbm == GPI_ERROR)
   {
      /* error creating bitmap */
      GpiAssociate( pmp->hpsMem4Printer, (HDC)NULLHANDLE );
      GpiDestroyPS( pmp->hpsMem4Printer );
      DevCloseDC( pmp->hdcMem4Printer );
      return WM_NACK_BITMAP_ERROR;
   }

   /* set bitmap into the memory ps */
   hbm = GpiSetBitmap( *phps, *phbm );
   pmassert( hab, hbm != HBM_ERROR );

   /*
    * determine if this is a 1.x or 2.x bitmap
    * by looking at the length of the BITMAPINFOHEADER structure
    */
   if( pbmp2->cbFix == sizeof(BITMAPINFOHEADER))
   {
      /* this is an OS2 PM 1.x bitmap */
      cScans = (ULONG) ((PBITMAPINFOHEADER)pbmp2)->cy;
      pmp->cyBitmap = ((PBITMAPINFOHEADER)pbmp2)->cy;
      pmp->cxBitmap = ((PBITMAPINFOHEADER)pbmp2)->cx;
      pmp->cxBitmapRes = pmp->cyBitmapRes = 0;
   }
   else
   {
      /* default to Windows and OS2 2.x PM bitmap */
      cScans = pbmp2->cy;
      pmp->cyBitmap = pbmp2->cy;
      pmp->cxBitmap = pbmp2->cx;
      pmp->cxBitmapRes = pbmp2->cxResolution;
      pmp->cyBitmapRes = pbmp2->cyResolution;
   }


   /*
    * Some print drivers expect the bit image data to be dword
    * aligned.  Allocate a new memory object and copy bit data
    * to it.
    */
   pBitsAligned = (PBYTE) malloc( cbImageData );
   pmassert( hab, pBitsAligned );
   pmassert( hab, ((ULONG)pBitsAligned & 3 ) == 0 );
   memcpy( pBitsAligned, pBits, cbImageData );


   /* take bitmap bits from file buffer and place into memory ps for bitmap */
   lRC = GpiSetBitmapBits( *phps,
                           0,
                           (LONG)cScans,
                           pBitsAligned,
                           (PBITMAPINFO2)pbmp2  );
   pmassert( hab, lRC != GPI_ERROR );

   /* clean up */
   free( pBitsAligned );
   free( pbmpfileheader2 );
   return 0;
} /* end of LoadBitmap */



/*************************************************************************
 *  Function   :  MMToTwips
 *
 *  Description:  Convert Millimetres to Twips
 *
 *  Parameters :  LONG       millimetres
 *
 *  API's      :  [none]
 *
 *  Return     :  LONG       twips
 *
 *************************************************************************/
LONG MMToTwips( LONG lMM )
{
   return ( (LONG)( ((float)lMM) / 25.4 * 1440.0) );
} /* end of MMToTwips */


/*************************************************************************
 *
 * name: PaintBitmap
 *
 * Description:  Paints a loaded bitmap onto the screen or printer
 *
 * API's:   GpiSetBitmap
 *          GpiWCBitBlt
 *
 * Parameters: PMAIN_PARM  pointer to the global data structure
 *             ULONG       flag to indicate screen or printer
 *
 * Return: [none]
 *
 **************************************************************************/
VOID PaintBitmap( PMAIN_PARM pmp, ULONG flag )
{
    HPS              hps, hpsBitmap;
    HBITMAP          hbm, hbmOld;
    POINTL           aptl[4];
    LONG             lResult;
    double           xRatio, yRatio;
    POINTL           pointlBegin, pointlEnd;

    pmassert( pmp->hab, pmp->hbm );

    /* define drawing area on screen or printer */
    CalculateDrawingArea( pmp, flag, &pointlBegin, &pointlEnd );

    /* setup handles for screen or printer */
    switch( flag )
    {
    case FLAGS_SCREEN:
        hps = pmp->hpsClient;
        hbm = pmp->hbm;
        hpsBitmap = pmp->hpsMemory;
        /* draw border around bitmap */
        DrawBorder( hps, pmp, pointlBegin, pointlEnd );
        break;

    case FLAGS_PRINTER:
        hps = pmp->hpsPrinter;
        hbm = pmp->hbm4Printer;
        hpsBitmap = pmp->hpsMem4Printer;
        break;

    default:
        pmassert( pmp->hab, NULL == "bad flag in PaintBitmap" );
    }

    /*
     * No clip path in defined for a bitmap as the bitmap is scaled to
     * fit the available drawing area
     */

    /* setup the source bitblt rectangle */
    aptl[2].x = 0;
    aptl[2].y = 0;
    aptl[3].x = pmp->cxBitmap;
    aptl[3].y = pmp->cyBitmap;

    /* Set up target bitblt rectangles */
    aptl[0].x = pointlBegin.x;
    aptl[0].y = pointlBegin.y;

    if (pmp->cxBitmapRes && pmp->cyBitmapRes)
    {
        /*
         * Since the intended resolution for the bitmap is known,
         * the actual size of the bitmap can be calculated, in
         * twips by converting pels/meter to pels/twip
         */
        aptl[1].x = ((double)pmp->cxBitmap)/((double)pmp->cxBitmapRes)
                    * 1000.0 / 25.4 * 1440.0;
        aptl[1].y = ((double)pmp->cyBitmap)/((double)pmp->cyBitmapRes)
                    * 1000.0 / 25.4 * 1440.0;
    }
    else
    {
       /*
        * Assume the bitmap aspect ratio is 1.0
        */
       xRatio = ((double)pmp->sizelPage.cx) / ((double)aptl[3].x);
       yRatio = ((double)pmp->sizelPage.cy) / ((double)aptl[3].y);
       if (yRatio > xRatio)
       {
           aptl[1].x = xRatio * (double) (aptl[3].x);
           aptl[1].y = xRatio * (double) (aptl[3].y);
       }
       else
       {
           aptl[1].x = yRatio * (double) (aptl[3].x);
           aptl[1].y = yRatio * (double) (aptl[3].y);
       }
    }

    /* move start position so bitmap is top of the page */
    aptl[0].y = pointlEnd.y - aptl[1].y;

    /* calculate final target top/right end-point */
    aptl[1].x = aptl[0].x + aptl[1].x;
    aptl[1].y = aptl[0].y + aptl[1].y;

    /* Deselect bitmap from memory */
    hbmOld = GpiSetBitmap( hpsBitmap, 0L );
    pmassert( pmp->hab, hbmOld != HBM_ERROR );

    /* Bitblt the source bitmap onto the printer presentation space */
    lResult = GpiWCBitBlt( hps,
                           hbm,
                           4L,
                           (PPOINTL)aptl,
                           ROP_SRCCOPY,
                           BBO_IGNORE );
    pmassert( pmp->hab, lResult == GPI_OK );

    return;
} /* end of PaintBitmap */


/*************************************************************************
 *
 * name: PaintDefaultScreen
 *
 * Description:  Paints the default screen
 *
 * API's:  [none]
 *
 * Parameters: PMAIN_PARM  pointer to the global data structure
 *             ULONG       flag to indicate screen or printer
 *
 * Return: [none]
 *
 **************************************************************************/
VOID PaintDefaultScreen( PMAIN_PARM pmp, ULONG flag )
{
    HPS              hps;
    POINTL           pointlBegin, pointlEnd;

    /* define drawing area on screen or printer */
    CalculateDrawingArea( pmp, flag, &pointlBegin, &pointlEnd );

    /* setup handles for screen or printer */
    switch( flag )
    {
    case FLAGS_SCREEN:
        hps = pmp->hpsClient;
        /* draw border around bitmap */
        DrawBorder( hps, pmp, pointlBegin, pointlEnd );
        break;

    default:
        pmassert( pmp->hab, NULL == "bad flag in PaintDefaultScreen" );
    }
    return;
} /* end of PaintDefaultScreen */


/*************************************************************************
 *
 * name: PaintMetaFile
 *
 * Description:  Paints a loaded metafile onto the screen or printer
 *
 * API's:  GpiQueryDefaultViewMatrix
 *         GpiSetDrawControl
 *         GpiResetBoundaryData
 *         GpiPlayMetaFile
 *         GpiScale
 *         GpiTranslate
 *         GpiSetViewingTransformMatrix
 *         GpiResetPS
 *
 * Parameters: PMAIN_PARM  pointer to the global data structure
 *             ULONG       flag to indicate screen or printer
 *
 * Return: [none]
 *
 * Reference: Graham C.E. Winn, "OS/2 PM GPI", VNR Computer Library
 *
 **************************************************************************/
VOID PaintMetaFile( PMAIN_PARM pmp, ULONG flag )
{
    HAB              hab;
    HPS              hps;
    LONG             lRC;
    BOOL             bOK;
    POINTL           pointlBegin, pointlEnd, ptl;
    BYTE             abDesc[ LEN_PLAYMETAFILEDESCS ];
    LONG             alOptions[ LEN_PLAYMETAFILEOPTS ];
    LONG             lCount;
    RECTL            rclBoundary;
    FIXED            afxScale[2];
    MATRIXLF         matlfXform, matlfOld, matlfModel;
    static MATRIXLF matlfIdentity = {0x10000,0,0,0,0x10000,0,0L,0L,1L };

    pmassert( pmp->hab, pmp->hmf );

    hab = pmp->hab;

    /* define drawing area on screen or printer */
    CalculateDrawingArea( pmp, flag, &pointlBegin, &pointlEnd );

    /* setup handles for screen or printer */
    switch( flag )
    {
    case FLAGS_SCREEN:
        hps = pmp->hpsClient;
        /* draw border around bitmap */
        DrawBorder( hps, pmp, pointlBegin, pointlEnd );
        break;

    case FLAGS_PRINTER:
        hps = pmp->hpsPrinter;
        break;

    default:
        pmassert( hab, NULL == "bad flag in PaintMetaFile" );
    }

    /*
     * No clip path in defined for a bitmap as the bitmap is scaled to
     * fit the available drawing area
     */

    /* query the default viewing transform */
    bOK = GpiQueryDefaultViewMatrix( hps, 9, &matlfOld );
    pmassert( hab, bOK );

    /* set metafile play options */
    memset( alOptions, 0, sizeof( alOptions ));
    alOptions[ PMF_SEGBASE             ] = 0;
    alOptions[ PMF_LOADTYPE            ] = LT_NOMODIFY;
    alOptions[ PMF_RESOLVE             ] = 0;
    alOptions[ PMF_LCIDS               ] = LC_LOADDISC;
    alOptions[ PMF_RESET               ] = RES_NORESET;
    alOptions[ PMF_SUPPRESS            ] = SUP_NOSUPPRESS;
    alOptions[ PMF_COLORTABLES         ] = CTAB_REPLACE;
    alOptions[ PMF_COLORREALIZABLE     ] = CREA_NOREALIZE;
    alOptions[ PMF_DEFAULTS            ] = DDEF_LOADDISC;

    /* find out the metafile bounding rectangle */
    bOK = GpiSetDrawControl( hps, DCTL_DISPLAY, DCTL_OFF );
    pmassert( hab, bOK );
    bOK = GpiSetDrawControl( hps, DCTL_BOUNDARY, DCTL_ON );
    pmassert( hab, bOK );
    bOK = GpiResetBoundaryData( hps );
    pmassert( hab, bOK );
    lRC = GpiPlayMetaFile( hps,
                           pmp->hmf,
                           LEN_PLAYMETAFILEOPTS,
                           alOptions,
                           &lCount,
                           LEN_PLAYMETAFILEDESCS,
                           abDesc );
    pmassert( hab, lRC != GPI_ERROR );
    bOK = GpiQueryBoundaryData( hps, &rclBoundary );
    pmassert( hab, bOK );
    /* set draw mode */
    bOK = GpiSetDrawControl( hps, DCTL_DISPLAY, DCTL_ON );
    pmassert( hab, bOK );
    bOK = GpiSetDrawControl( hps, DCTL_BOUNDARY, DCTL_OFF);
    pmassert( hab, bOK );

    /*
     * Determine scale parameters to scale from boundary dimensions to target
     * dimensions about bottom left of boundary. Ensure that both scale
     * factors are equal and set to the smaller of the two possible values
     * (to preserve the aspect ratio of the metafile).
     */
    if (rclBoundary.xRight > rclBoundary.xLeft)
    {
       afxScale[0] = ((double)pmp->sizelPage.cx) * 0x10000 /
                     ((double)(rclBoundary.xRight - rclBoundary.xLeft));
    }
    else
    {
       afxScale[0] = 0x10000;
    }
    if (rclBoundary.yTop > rclBoundary.yBottom)
    {
       afxScale[1] = ((double)pmp->sizelPage.cy) * 0x10000 /
                     ((double)(rclBoundary.yTop - rclBoundary.yBottom));
    }
    else
    {
       afxScale[1] = 0x10000;
    }
    if (afxScale[0] < afxScale[1])
    {
       afxScale[1] = afxScale[0];
    }
    else
    {
       afxScale[0] = afxScale[1];
    }

    /* scale metafile around boundary bottom,left corner */
    ptl.x = rclBoundary.xLeft;
    ptl.y = rclBoundary.yBottom;
    bOK = GpiScale( hps, &matlfXform, TRANSFORM_REPLACE, afxScale, &ptl );
    pmassert( hab, bOK );

    /* translate metafile to correct position */
    ptl.x = pointlBegin.x - rclBoundary.xLeft;
    /* move start position so metafile is top of the page */
    ptl.y = pointlEnd.y - rclBoundary.yBottom -
            (LONG)( ((double)(afxScale[1])) / 65536.0 *
                    ((double)(rclBoundary.yTop - rclBoundary.yBottom)) );

    bOK = GpiTranslate( hps, &matlfXform, TRANSFORM_ADD, &ptl );
    pmassert( hab, bOK );

    /* set default view transform to scale metafile to PS */
    bOK = GpiSetViewingTransformMatrix( hps, 9, &matlfXform,
                                        TRANSFORM_REPLACE);
    pmassert( hab, bOK );

    /* draw the metafile */
    alOptions[PMF_LCIDS] = LC_NOLOAD;
    alOptions[PMF_DEFAULTS] = DDEF_IGNORE;
    lRC = GpiPlayMetaFile( hps,
                           pmp->hmf,
                           LEN_PLAYMETAFILEOPTS,
                           alOptions,
                           &lCount,            /* segment count */
                           LEN_PLAYMETAFILEDESCS,
                           abDesc );
    pmassert( hab, lRC != GPI_ERROR );

    if (flag == FLAGS_SCREEN)
    {
       bOK = GpiResetPS( hps, GRES_ALL );
       pmassert( pmp->hab, bOK );
    }

    /* restore the default viewing transform */
    bOK = GpiSetDefaultViewMatrix( hps, 9, &matlfOld, TRANSFORM_REPLACE);
    pmassert( hab, bOK );

    return;
} /* end of PaintMetaFile() */


/*************************************************************************
 *
 * name: PaintText
 *
 * Description:  Paints the text to the screen or printer
 *
 * API's:  [none]
 *
 * Parameters: PMAIN_PARM  pointer to the global data structure
 *             ULONG       flag to indicate screen or printer
 *
 * Return: EOF flag    PAGINATE_EOF             end of file, no text drawn
 *                     PAGINATE_NOT_EOF         not end of file, text drawn
 *                     PAGINATE_EOF_PART_PAGE   end of file, text drawn
 *
 **************************************************************************/
ULONG PaintText( PMAIN_PARM pmp, ULONG flag )
{
    BOOL             bOK;
    CHAR             szWork[ LEN_WORKSTRING ];
    FONTMETRICS      fm;
    HAB              hab;
    HPS              hps;
    LONG             lCharHeight, lLeading, cLinesOnPage;
    LONG             lRC;
    POINTL           pointl;
    POINTL           pointlBegin, pointlEnd;
    PSZ              pszOK;
    SIZEF            sizef;

    hab = pmp->hab;

    pmassert( hab, pmp->f );
    pmassert( hab, pmp->ulMode == MODE_TEXT );

    /* check for end of file */
    if ( !fgets( szWork, LEN_WORKSTRING, pmp->f) )
    {
      if (flag == FLAGS_SCREEN)
      {
        /* for screen reset back the beginning and read first line */
        fseek( pmp->f, 0, SEEK_SET );
        fgets( szWork, LEN_WORKSTRING, pmp->f);
      }
      else
      {
        /* for printer, return PAGINATE_EOF */
        return PAGINATE_EOF;
      }
    }

    /* define drawing area on screen or printer */
    CalculateDrawingArea( pmp, flag, &pointlBegin, &pointlEnd );

    /* prepare to draw on screen or printer */
    switch( flag )
    {
    case FLAGS_SCREEN:
       hps = pmp->hpsClient;
       /*
        * Paginate the text into a retained segment. The text drawing
        * orders can then be played back at WM_PAINT time with a call
        * to GpiDrawChain.
        */
       bOK = GpiSetDrawingMode( hps, DM_RETAIN );
       pmassert( hab, bOK );

       GpiErase( hps );

       /* delete any previous segment; segment 1 is all we use */
       GpiDeleteSegment( hps, 1 );

       /* open a new segment for the text using segment ID 1 */
       bOK = GpiOpenSegment( hps, 1 );
       pmassert( hab, bOK );

       /* clear PS and draw form border */
       DrawBorder( hps, pmp, pointlBegin, pointlEnd );

       break;

    case FLAGS_PRINTER:
       hps = pmp->hpsPrinter;
       break;

    case FLAGS_INFO:
       hps = pmp->hpsPrinterInfo;
       break;

    default:
       pmassert( hab, NULL == "bad flag in WM_USER_PAGINATE" );
    }

    pmassert( hab, hps );

    /*
     * Set clipping path to page size defined by user.
     * If user picks a font too big, he'll know this visually.
     */
    SetClipPath( hps, pmp, pointlBegin, pointlEnd );

    /* use black color for text */
    bOK = GpiSetColor( hps, CLR_BLACK );
    pmassert( hab, bOK );


    /* Discard LCID (logical character identifier) 1 if it exists. */
    if( GpiSetCharSet( hps, 1 ))
    {
        /* select font 0 and delete font 1   */
        GpiSetCharSet(hps, 0 );
        GpiDeleteSetId(hps, 1 );
    }

    /* select default font 0 */
    GpiSetCharSet(hps, 0 );



    /*
     *  Create the logical font with fAttrs from the font dialog.
     *
     *  This version of the print sample only requests vector (outline)
     *  fonts of the font dialog. (Reference: PRTCREAT.C, the
     *  initialization of the pmp->fontdlg structure.)
     *
     *  Create and select the requested font into the PS and query its
     *  font metrics.
     */

    lRC = GpiCreateLogFont( hps, NULL, 1, &pmp->fontdlg.fAttrs );
    pmassert(hab, lRC != GPI_ERROR );

    if( lRC == FONT_DEFAULT )
    {
      /*
       * This can happen on Postscript device fonts which are outline fonts,
       * and also LaserJet PCL 5 scalable printer device fonts. These
       * have to be emulated on the screen. Give a warning to user that
       * some default font is in use, and carry on.
       *
       * Only warn the user once after each visit to the font dialog.
       * Don't re-enable the client yet. That message is still to come.
       */
      if( pmp->fVisitedFontDialog )
      {
         /* only one warning per font dialog visit */
         pmp->fVisitedFontDialog  = FALSE;

         bOK = WinLoadString( pmp->hab, (HMODULE)0,
                      ERRMSG_DEFAULT_FONT, LEN_WORKSTRING, szWork );
         pmassert( pmp->hab, bOK );
         WinMessageBox( HWND_DESKTOP,
                        pmp->hwndFrame,
                        szWork,
                        pmp->pszTitle,
                        (USHORT)0,
                        MB_OK | MB_MOVEABLE | MB_CUACRITICAL | MB_APPLMODAL);
      }
    }

    bOK = GpiSetCharSet( hps, 1 );
    pmassert( hab, bOK );

    bOK = GpiQueryFontMetrics(hps, sizeof(FONTMETRICS), &fm );
    pmassert(hab, bOK);



    /*
     *   GpiSetCharBox
     *
     *   "Char box" the font to the point size desired by the user.
     *   When sizef.cx == sizef.cy, the result is a normally-proportioned font.
     *   Setting sizef.cx to one-half of sizef.cy yields a compressed font,
     *   and setting sizef.cx to twice sizef.cy yields an expanded font.
     *   Experiment with these settings; this is one of the most flexible
     *   aspects of the Adobe font rasterizing technology in the OS2 graphics
     *   engine. It is true WYSIWYG and very device independent.
     *
     *   Although not demonstrated in this version of the print sample,
     *   GpiSetCharBox will play an important role in emulating printer device
     *   fonts on the screen device for WYSIWYG output.
     */

    sizef.cy = sizef.cx = FixedPointsToTwips( pmp->fontdlg.fxPointSize );
    bOK = GpiSetCharBox(hps, &sizef);
    pmassert(hab, bOK);


    /*
     *  Set Line Space
     *
     *  Here are three methods for determining the vertical
     *  line spacing (also known as line increment or leading)
     *  between lines of text.
     *
     *  1) Add two values from the fontmetrics structure:
     *  lMaxBaselineExt + lExternalLeading.
     *
     *  2) Use fontmetrics values lEmHeight * 1.20 + lExternalLeading
     *  lExternalLeading is zero for PM fonts.
     *
     *  3) The typesetters' rule of thumb: lead is pointsize plus two points.
     *
     * Method 1 and 2 require A) a re-read of the font metrics after the call
     * to GpiSetCharBox because those metrics will change as a result of
     * GpiSetCharBox, or B) some computation given the current font metrics.
     * Method 3 is simplest because the only variable is point size, and that
     * was specified by the user.
     *
     * 1 point = 20 twips.
     */

    lCharHeight =  20 * FIXEDINT( pmp->fontdlg.fxPointSize ) +
                   20 * FIXEDFRAC( pmp->fontdlg.fxPointSize ) / 0x10000;
    lLeading =  lCharHeight + 40;

    /* compute lines on page (leave one line at the bottom for descenders) */
    cLinesOnPage = (pmp->sizelPage.cy / lLeading) - 1;

    /* start at the upper left corner of page */
    pointl.x = pointlBegin.x;
    pointl.y = pointlEnd.y - lLeading;

    /*
     * Read from text file at current file position and draw as much of it
     * as will fit on the page.
     */
    do
    {
        /* remove trailing white space */
        trim( szWork );

        /* move to the spot on the page */
        bOK = GpiSetCurrentPosition( hps,  &pointl   );
        pmassert( hab, bOK );

        lRC = GpiCharString( hps, strlen( szWork ), szWork );
        pmassert( hab, lRC != GPI_ERROR );

        /* move down the page */
        pointl.y -= lLeading;

        /* have reached bottom of page? */
        if( --cLinesOnPage == 0  )
        {
            /* yes, bottom of page; break out of this while loop */
            break;
        }
        pszOK = fgets( szWork, LEN_WORKSTRING, pmp->f );
    } while( pszOK );

    /* reset the clip path */
    bOK = GpiSetClipPath( hps, 0L, SCP_RESET );
    pmassert( pmp->hab, bOK );

    if( flag == FLAGS_SCREEN )
    {

      /* close the drawing segment; only the screen uses retained segments */
      bOK = GpiCloseSegment( hps );
      pmassert( hab, bOK );
      bOK = GpiSetDrawingMode( hps, DM_DRAW );
      pmassert( hab, bOK );
    }

    /* something WAS drawn on the page, yet could be at end of file now */
    lRC = (pszOK) ? PAGINATE_NOT_EOF : PAGINATE_EOF_PART_PAGE;

    return lRC;
}


/*************************************************************************
 *
 * name: SetClipPath
 *
 * Description:  Sets the clipping path for the PS
 *
 * API's:  [none]
 *
 * Parameters:  HPS         presentation space to use
 *              PMAIN_PARM  pointer to the global data structure
 *              PPOINTL     pointer to begin point of drawing area
 *              PPOINTL     pointer to end point of drawing area
 *
 * Return: [none]
 *
 **************************************************************************/
VOID SetClipPath( HPS hps, PMAIN_PARM pmp, POINTL ptlBegin, POINTL ptlEnd )
{
   BOOL     bOK;
   LONG     lRC;
   HAB      hab;
   POINTL   ptl;

   hab = pmp->hab;
   bOK = GpiBeginPath( hps, 1);
   pmassert( hab, bOK );
   /* clip path is inclusive at bottom, left */
   ptl.x = ptlBegin.x;
   ptl.y = ptlBegin.y;
   bOK = GpiSetCurrentPosition( hps,  &ptl );
   pmassert( hab, bOK );
   /* clip path is inclusive at top, right */
   ptl.x = ptlEnd.x;
   ptl.y = ptlEnd.y;
   lRC = GpiBox( hps, DRO_OUTLINE, &ptl, 0L, 0L );
   pmassert( hab, lRC != GPI_ERROR );
   bOK = GpiEndPath( hps );
   pmassert( hab, bOK );
   bOK = GpiSetClipPath( hps, 1L, SCP_AND );
   pmassert( hab, bOK );

} /* end of SetClipPath */


/*************************************************************************
*
* Name: trim
*
* Description: removes trailing whitespace characters from a string
*
* Parameters: psz = a string
*
* Returns: a pointer to the same string passed in
*
**************************************************************************/
PSZ trim( PSZ s )
{
  PCH p;

  /* point p to null-terminator byte of string s */
  p =  s + strlen( s );

  /* work backward and remove white space characters at end of string */
  p--;
  while( s <= p && ( *p == '\r' || *p == ' ' || *p == '\n' || *p == '\t' ))
  {
    *p = 0;
    p--;
  }

  return s;
}  /*  end of trim()  */

/***************************  End of prtobj.c ****************************/
