/****************************************************************************
 * 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)
 *	       trim(s)
 *	       FixedPointsToTwips(fxPointSize)
 *
 * Concepts:   second thread, object window
 *
 * API's:       WinInitialize
 *		WinCreateMsgQueu
 *		WinRegisterClass
 *		WinCreateWindow
 *		WinPostMsg
 *		WinGetMsg
 *		WinDispatchMsg
 *		WinDestroyWindow
 *		WinDestroyMsgQueue
 *		WinTerminate
 *		DosExit
 *		WinQueryAnchorBlock
 *		WinQueryWindowULong
 *		WinSetWindowULong
 *		DosOpen
 *		DosQueryFileInfo
 *		DosRead
 *		DosClose
 *		DevOpenDC
 *		GpiCreatePS
 *		GpiCreateBitmap
 *		GpiSetBitmap
 *		GpiSetBitmapBits
 *		WinInvalidateRect
 *		GpiLoadMetaFile
 *		WinSendMsg
 *		GpiSetDrawingMode
 *		WinGetLastError
 *		DosSleep
 *		GpiErase
 *		GpiDeleteSegment
 *		GpiOpenSegment
 *		GpiSetCurrentPosition
 *		GpiBox
 *		GpiSetColor
 *		GpiCharString
 *		GpiCloseSegment
 *		WinQueryWindowRect
 *		GpiQueryBitmapParameters
 *		GpiBitBlt
 *		GpiQueryPS
 *		GpiPlayMetaFile
 *		GpiSetPS
 *		WinDefWindowProc
 *		GpiSetCharSet
 *		GpiDeleteSetId
 *		GpiCreateLogFont
 *		GpiQueryFontMetrics
 *		GpiSetCharBox
 *
 *   Files    :	 OS2.H, PRTSAMP.H, PRTSDLG.H, PMASSERT.H
 *
 *  Copyright (C) 1991 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_DOSFILEMGR
#define INCL_DOSPROCESS
#define INCL_WINSTDFILE
#define INCL_WINSTDFONT
#define INCL_WINWINDOWMGR
#define INCL_WINDIALOGS
#define INCL_WINMLE
#define INCL_WINERRORS
#define INCL_GPIPRIMITIVES
#define INCL_GPIMETAFILES
#define INCL_GPILCIDS
#define INCL_GPIBITMAPS
#define INCL_GPISEGMENTS
#define INCL_GPITRANSFORMS
#define INCL_GPIERRORS
#define INCL_GPICONTROL
#define INCL_SPL
#define INCL_SPLDOSPRINT
#include <os2.h>

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

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

/* local function prototypes */


FIXED FixedPointsToTwips(FIXED fxPointSize);



/*************************************************************************
 *
 * 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
 *		DosExit
 *
 * 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 );

   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 );

   /* exit this thread */
   DosExit(EXIT_THREAD, 0L);
}  /*  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:       WinQueryAnchorBlock
 *		WinQueryWindowULong
 *		WinPostMsg
 *		WinSetWindowULong
 *		DosOpen
 *		DosQueryFileInfo
 *		DosRead
 *		DosClose
 *		DevOpenDC
 *		GpiCreateBitmap
 *		GpiSetBitmap
 *		GpiSetBitmapBits
 *		WinInvalidateRect
 *		GpiLoadMetaFile
 *		WinSendMsg
 *		GpiSetDrawingMode
 *		WinGetLastError
 *		DosSleep
 *		GpiErase
 *		GpiDeleteSegment
 *		GpiOpenSegment
 *		GpiSetCurrentPosition
 *		GpiBox
 *		GpiSetColor
 *		GpiCharString
 *		GpiCloseSegment
 *		WinQueryWindowRect
 *		GpiSetCharSet
 *		GpiDeleteSetId
 *		GpiCreateLogFont
 *		GpiQueryFontMetrics
 *		GpiQueryBitmapParameters
 *		GpiBitBlt
 *		GpiQueryPS
 *		GpiPlayMetaFile
 *		GpiSetPS
 *		WinDefWindowProc
 *
 * 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 )
{
   BITMAPINFOHEADER   bmpinfo;
   BOOL		      bOK;
   BYTE		      abDesc[ LEN_PLAYMETAFILEDESCS ];
   CHAR		      szWork[ LEN_WORKSTRING ];
   FILESTATUS3	      filestatus3;
   FONTMETRICS	      fm;
   HAB		      hab;
   HBITMAP	      hbm;
   HDC		      hdcCompatible;
   HFILE	      hfile;
   HPS		      hps;
   HWND		      hwndToAck;
   IPT		      ipt;
   LONG		      alOptions[ LEN_PLAYMETAFILEOPTS ];
   LONG		      l, lRC, lCount;
   LONG		      lLeftMarginTwips, lLeading, cLinesOnPage;
   PBITMAPFILEHEADER2 pbmpfileheader2;
   PBITMAPFILEHEADER2 pbmpfileheader2Work;
   PBITMAPINFOHEADER2 pbmp2;
   PBYTE	      pBits;
   PBYTE	      pBitsAligned;
   PCHAR	      pch;
   PHBITMAP	      phbm;
   PHDC		      phdc;
   PHPS		      phps;
   PLONG	      pdxIncrement;
   PMAIN_PARM	      pmp;
   POINTL	      apointl[ 4 ];
   POINTL	      pointl;
   PPOINTL	      pptlPosition;
   RECTL	      rectl;
   SIZEF	      sizef;
   SIZEL	      sizel;
   ULONG	      rc, ulAction, ulPtr, cBytes, cScans, ulOptions, ulWork;
   ULONG	      cbImageData;
   ULONG	      ulPointSize;


   /* 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 );

     /*
      * Try to set the print destination to that which was stored in INI.
      * Parm 3 == FALSE means do not display a dialog
      */
     QueryPrintQueue( 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
      * see HDCMEM_COMPATIBILITY_*  in prtsamp.h
      */

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

     /* 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 );
       WinPostMsg( hwndToAck, WM_NACK_FILE_READING_ERROR,
			  (MPARAM)msg, (MPARAM)0L );
       return (MRESULT)NULL;
     }

     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 );
       WinPostMsg( hwndToAck, WM_NACK_BITMAP_NOT_SUPPORTED,
			 (MPARAM)msg, (MPARAM)0L );
       return (MRESULT)NULL;
     }

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

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

     case HDCMEM_COMPATIBILITY_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 HDCMEM_COMPATIBILITY_*" );
     }


     /* 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 */
     pmassert( hab, *phbm );

     /* 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.0 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 );

     /* 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)NULL;


   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 bitmap */
     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_PAGINATE_SCREEN );
     }

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


   case WM_USER_PAGINATE:
     /*
      * mp2 is a set of flags to govern pagination behavior
      * can return one of three result codes:
      * 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
      */
     pmassert( hab, pmp->f );
     pmassert( hab, pmp->ulMode == MODE_TEXT );

     /* check for end of file */
     if( feof( pmp->f ))
     {
       /* ack and return with indication of end-of-file */
       WinPostMsg( hwndToAck, WM_USER_ACK, (MPARAM)msg, (MPARAM)PAGINATE_EOF );
       return (MRESULT) PAGINATE_EOF;
     }

     /* prepare to draw on screen or printer */
     switch( (ULONG)mp2 )
     {
     case FLAGS_PAGINATE_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 );

	/*
	 * Draw a box the same size as the current printer page.
	 * If user picks a font too big, he'll know this visually.
	 */
	pointl.x = pointl.y = 0;
	bOK = GpiSetCurrentPosition( hps,  &pointl );
	pmassert( hab, bOK );

	pointl.x = pmp->sizelPage.cx;
	pointl.y = pmp->sizelPage.cy;
	lRC = GpiBox( hps, DRO_OUTLINE, &pointl, 0L, 0L );
	pmassert( hab, lRC != GPI_ERROR );

	break;

     case FLAGS_PAGINATE_PRINTER:
	pmassert( hab,	pmp->hpsPrinter );
	hps = pmp->hpsPrinter;
	break;

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

     /* use black color */
     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.)
     *
     *	Later versions of the print sample will allow the selection of printer
     *	device fonts and then emulate their appearance on the display using
     *	outline fonts.
     *
     *	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.
       *
       * A future release of the print sample is to demonstrate the
       * emulation of printer device fonts on the screen using outline fonts.
       */
       WinPostMsg( hwndToAck, WM_NACK_DEFAULT_FONT, 0, 0 );
    }

    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.
     */

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

    /* compute lines on page */
    cLinesOnPage = (pmp->sizelPage.cy / lLeading) - 1;

    /* left margin is zero */
    lLeftMarginTwips = 0;

    /* start a upper left corner of page */
    pointl.x = lLeftMarginTwips;
    pointl.y = pmp->sizelPage.cy  - lLeading;

    /*
     * Read from text file at current file position and draw as much of it
     * as will fit on the page.
     */
    while( fgets( szWork, LEN_WORKSTRING, pmp->f ))
    {
	/* 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;
	}
    }

    if( (ULONG)mp2 == FLAGS_PAGINATE_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 = feof( pmp->f ) ? PAGINATE_EOF_PART_PAGE : PAGINATE_NOT_EOF;

    /* ack and return with result code */
    WinPostMsg( hwndToAck, WM_USER_ACK,(MPARAM)msg,(MPARAM)lRC );
    return (MRESULT) lRC;


   case WM_USER_PAINT_BITMAP:
     pmassert( hab, pmp->hbm );

     /*
      * bitblt the bitmap into the client ps
      * set the target coordinates of the screen ps
      */
     WinQueryWindowRect( pmp->hwndClient, &rectl );
     apointl[0].x  = rectl.xLeft;
     apointl[0].y  = rectl.yBottom;
     apointl[1].x  = rectl.xRight;
     apointl[1].y  = rectl.yTop;

     /* set the source coordinates of the loaded bitmap */
     bOK = GpiQueryBitmapParameters( pmp->hbm, &bmpinfo );
     pmassert( hab, bOK );
     apointl[2].x  = 0;
     apointl[2].y  = 0;
     apointl[3].x  = bmpinfo.cx;
     apointl[3].y  = bmpinfo.cy;

     /* bit block transfer */
     lRC = GpiBitBlt( pmp->hpsClient, pmp->hpsMemory, 4L, apointl,
		      ROP_SRCCOPY, BBO_AND );
     pmassert( hab, lRC != GPI_ERROR );

     WinPostMsg( hwndToAck, WM_USER_ACK, (MPARAM)msg, 0 );
     return (MRESULT)NULL;


   case WM_USER_PAINT_METAFILE:
     /*
      * Reference: Graham C.E. Winn, "OS/2 PM GPI", VNR Computer Library
      * play metafile into client area
      */
     pmassert( hab, pmp->hmf );

     /* save state of hps */
     ulOptions = GpiQueryPS( pmp->hpsClient, &sizel );

     memset( alOptions, 0, sizeof( alOptions ));
     alOptions[ PMF_SEGBASE		] = 0;
     alOptions[ PMF_LOADTYPE		] = LT_ORIGINALVIEW;
     alOptions[ PMF_RESOLVE		] = 0;
     alOptions[ PMF_LCIDS		] = LC_LOADDISC;
     alOptions[ PMF_RESET		] = RES_RESET;
     alOptions[ PMF_SUPPRESS		] = SUP_NOSUPPRESS;
     alOptions[ PMF_COLORTABLES		] = CTAB_REPLACE;
     alOptions[ PMF_COLORREALIZABLE	] = CREA_NOREALIZE;
     alOptions[ PMF_DEFAULTS		] = DDEF_LOADDISC;
     lRC = GpiPlayMetaFile( pmp->hpsClient,
			    pmp->hmf,
			    LEN_PLAYMETAFILEOPTS,
			    alOptions,
			    &lCount,		/* segment count */
			    LEN_PLAYMETAFILEDESCS,
			    abDesc );
     pmassert( hab, lRC != GPI_ERROR );

     /* restore state of ps */
     bOK = GpiSetPS( pmp->hpsClient, &sizel, ulOptions );
     pmassert( hab, bOK );

     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: 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()  */


/*************************************************************************
 *
 * 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()  */

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

