/*
 *  print.c
 *
 *  Source file for Device-Independent Bitmap (DIB) API.  Provides
 *  the following functions:
 *
 *  PrintWindow()       - Prints all or part of a window
 *  PrintScreen()       - Prints the entire screen
 *  PrintDIB()          - Prints the specified DIB
 *
 * Development Team: Mark Bader
 *                   Patrick Schreiber
 *                   Garrett McAuliffe
 *                   Eric Flo
 *                   Tony Claflin
 *
 * Written by Microsoft Product Support Services, Developer Support.
 * Copyright (c) 1991 Microsoft Corporation. All rights reserved.
 */
#include <windows.h>
#include <string.h>
#include "dibapi.h"     // Header for DIB functions
#include "dibutil.h"    // Auxilirary functions
#include "dialogs.h"    // Header for "Now Printing" dialog box
#include "errors.h"     // Contains error numbers

extern HANDLE ghInst;     // Global handle to instance of main window

/***************************************************************
 * Typedefs
 **************************************************************/

/* Structure used for Banding */

typedef struct
{
   BOOL bGraphics;
   BOOL bText;
   RECT GraphicsRect;
} BANDINFOSTRUCT;


/****************************************************************
 * Variables
 ***************************************************************/

HWND hDlgAbort;                    // Handle to Abort Dialog
char szPrintDlg[] = "Printing";    // Name of Print dialog from .RC
BOOL bAbort = FALSE;               // Abort a print operation?
char gszDevice[50];                // Keeps track out device (e.g. "HP LaserJet")
char gszOutput[50];                // Output device (e.g. "LPT1:")

/***************************************************************
 * Function prototypes for functions local to this module
 **************************************************************/


BOOL FAR PASCAL PrintAbortProc(HDC, short);
int FAR PASCAL PrintAbortDlg(HWND, unsigned, WORD, LONG);
WORD PrintBand(HDC, LPRECT, LPRECT, BOOL, BOOL, LPBITMAPINFOHEADER, LPSTR);
HDC GetPrinterDC(void);
void CalculatePrintRect(HDC, LPRECT, WORD, DWORD, DWORD);


/**********************************************************************
 *
 * PrintWindow()
 *
 *
 * Description:
 *
 * This function prints the specified window on the default
 * printer.
 *
 * Parameters:
 *
 * HWND hWnd       - Specifies the window to print.  The window must
 *                   not be iconic and must be topmost on the display.
 *
 * WORD fPrintArea - Specifies the area of the window to print.  Must be
 *                   one of PW_ALL, PW_CLIENT, PW_CAPTION,  or PW_MENUBAR
 *
 * WORD fPrintOpt  - Print options (one of PW_BESTFIT, PW_STRETCHTOPAGE, or
 *                   PW_SCALE)
 *
 * WORD wXScale, wYScale - X and Y scaling factors if PW_SCALE is specified
 *
 * LPSTR szJobName - Name that you would like to give to this print job (this
 *                   name shows up in the Print Manager as well as the
 *                   "Now Printing..." dialog box).
 * Return Value:
 *      ERR_DIBFUNCTION or any return value from PrintDIB
 *
 **********************************************************************/


WORD PrintWindow(HWND hWnd,         // Window to be printed
                 WORD fPrintArea,   // Area of window to be printed
                 WORD fPrintOpt,    // Print options
                 WORD wXScale,      // X Scaling factor if PW_SCALE is used
                 WORD wYScale,      // Y Scaling factor if PW_SCALE is used
                 LPSTR szJobName)   // Name of print job
{
   HDIB hDib;          // Handle to the DIB
   WORD wReturn;       // our return value

   /*
    * Parameter validation
    */
   if (!hWnd)
      return (ERR_INVALIDHANDLE);  // Invalid Window

   /*
    * Copy the Window to a DIB and print it.
    */
   hDib = CopyWindowToDIB(hWnd, fPrintArea);
   if (!hDib)
      return (ERR_DIBFUNCTION); // CopyWindowToDIB failed!
   wReturn = PrintDIB(hDib, fPrintOpt, wXScale, wYScale, szJobName);

   /*
    * Call DestroyDIB to free the memory the dib takes up.
    */
   DestroyDIB(hDib);
   return wReturn;   // return the value from PrintDIB
}



/**********************************************************************
 *
 * PrintScreen()
 *
 *
 * Description:
 *
 * This function prints the specified portion of the display screen on the
 * default printer using the print options specified.  The print
 * options are listed in dibapi.h.
 *
 * Parameters:
 *
 * LPRECT rRegion  - Specifies the region of the screen (in screen
 *                   coordinates) to print
 *
 * WORD fPrintOpt  - Print options  (PW_BESTFIT, PW_STRETCHTOPAGE, or PW_SCALE)
 *
 * WORD wXScale, wYScale - X and Y scaling factors if PW_SCALE is specified
 *
 * LPSTR szJobName - Name that you would like to give to this print job (this
 *                   name shows up in the Print Manager as well as the
 *                   "Now Printing..." dialog box).
 *
 * Return Value:
 *      ERR_DIBFUNCTION or any return value from PrintDIB
 *
 **********************************************************************/


WORD PrintScreen(LPRECT rRegion,    // Region to print (in screen coords)
                 WORD fPrintOpt,    // print options
                 WORD wXScale,      // X scaling (used if PW_SCALE specified)
                 WORD wYScale,      // Y scaling (used if PW_SCALE specified)
                 LPSTR szJobName)   // Name of print job
{
   HDIB hDib;          // A Handle to our DIB
   WORD wReturn;       // Return value

   /*
    * Copy the screen contained in the specified rectangle to a DIB
    */
   hDib = CopyScreenToDIB(rRegion);
   if (!hDib)
      return (ERR_DIBFUNCTION);   // CopyScreenToDIB failed!
   wReturn = PrintDIB(hDib, fPrintOpt, wXScale, wYScale, szJobName);
   DestroyDIB(hDib);
   return wReturn; // Return the value that PrintDIB returned
}






/**********************************************************************
 *
 * PrintDIB()
 *
 * Description:
 *
 * This routine prints the specified DIB.  The actual printing is done
 * in the PrintBand() routine (see below), this procedure drives the
 * printing operation.  PrintDIB() has the code to handle both banding
 * and non-banding printers.  A banding printer can be distinguished by
 * the GetDeviceCaps() API (see the code below).  On banding devices,
 * must repeatedly call the NEXTBAND escape to get the next banding
 * rectangle to print into.  If the device supports the BANDINFO escape,
 * it should be used to determine whether the band "wants" text or
 * graphics (or both).  On non-banding devices, we can ignore all this
 * and call PrintBand() on the entire page.
 *
 * Parameters:
 *
 * HDIB hDib       - Handle to dib to be printed
 *
 * WORD fPrintOpt  - tells which print option to use (PW_BESTFIT,
 *                   PW_STRETCHTOPAGE, OR PW_SCALE)
 *
 * WORD wXScale, wYScale - X and Y scaling factors (integers) for
 *                   printed output if the PW_SCALE option is used.
 *
 * LPSTR szJobName - Name that you would like to give to this print job (this
 *                   name shows up in the Print Manager as well as the
 *                   "Now Printing..." dialog box).
 *
 * Return Value:  (see errors.h for description)
 *
 * One of: ERR_INVALIDHANDLE
 *         ERR_LOCK
 *         ERR_SETABORTPROC
 *         ERR_STARTDOC
 *         ERR_NEWFRAME
 *         ERR_ENDDOC
 *         ERR_GETDC
 *         ERR_STRETCHDIBITS
 *
 *
 ********************************************************************/


WORD PrintDIB(HDIB hDib,        // Handle to the DIB
              WORD fPrintOpt,   // Print Options
              WORD wXScale,     // X Scaling factor
              WORD wYScale,     // Y Scaling factor
              LPSTR szJobName)  // Name of print job
{
   HDC hPrnDC;                  // DC to the printer
   RECT rect;                   // Rect structure used for banding
   BANDINFOSTRUCT biBandInfo;   // Used for banding
   static FARPROC lpAbortProc;  // ProcInstance to the Abort Proc
   static FARPROC lpAbortDlg;   // ProcInstance to the Dialog Box Procedure
   int nTemp;                   // Temp number used to check banding capability
   LPSTR lpBits;                // pointer to the DIB bits
   LPBITMAPINFOHEADER lpDIBHdr; // Pointer to DIB header
   int nBandCount = 0;          // used for print dialog box to count bands
   WORD wErrorCode = 0;         // Error code to return
   RECT rPrintRect;             // Rect which specifies the area on the printer
                                // (in printer coordinates) which we
                                // want the DIB to go to
   char szBuffer[70];           // Buffer to hold message for "Printing" dlg box
   char szJobNameTrunc[35];     // szJobName truncated to 31 characters, since
                                // STARTDOC can't accept a string longer than 31

   /*
    * Paramter validation
    */
   if (!hDib)
      return (ERR_INVALIDHANDLE);

   /*
    * Get pointer to DIB header
    */
   lpDIBHdr = (LPBITMAPINFOHEADER)GlobalLock(hDib);
   if (!lpDIBHdr) // Check that we have a valid pointer
      return (ERR_LOCK);
   lpBits = FindDIBBits((LPSTR)lpDIBHdr); // Find pointer to DIB bits
   if (hPrnDC = GetPrinterDC())
   {
      SetStretchBltMode(hPrnDC, COLORONCOLOR);

      /*
       * Determine rPrintRect (printer area to print to) from the
       * fPrintOpt.  Fill in rPrintRect.left and .top from wXScale and
       * wYScale just in case we use PW_SCALE (see the function
       * CalculatePrintRect).
       */
      rPrintRect.left = wXScale;
      rPrintRect.top = wYScale;
      CalculatePrintRect(hPrnDC, &rPrintRect, fPrintOpt, lpDIBHdr->biWidth,
                         lpDIBHdr->biHeight);

      /*
       * Initialize the abort procedure.
       */
      lpAbortProc = MakeProcInstance(PrintAbortProc, ghInst);
      lpAbortDlg = MakeProcInstance(PrintAbortDlg, ghInst);
      hDlgAbort = CreateDialog(ghInst, szPrintDlg, GetFocus(), lpAbortDlg);

      /*
       * Set the text inside the dialog to the name of our print job
       */
      lstrcpy(szJobNameTrunc, szJobName);
      szJobNameTrunc[31] = '\0';           // Truncate string to 31 chars
      wsprintf(szBuffer, "Printing '%s'", (LPSTR)szJobNameTrunc);
      SetDlgItemText(hDlgAbort, IDC_PRINTTEXT1, (LPSTR)szBuffer);

      /*
       * Set global variable bAbort to FALSE.  This will get set to TRUE
       * in our PrintAbortDlg() proceudre if the user selects the
       * CANCEL button in our dialog box
       */
      bAbort = FALSE;

      /*
       * Call the Escape() which will set up the Abort Procedure
       */
      if (Escape(hPrnDC, SETABORTPROC, NULL, (LPSTR)(FARPROC)lpAbortProc, NULL
                 ) < 0)
         return (ERR_SETABORTPROC);

      /*
       * Call Escape() with STARTDOC -- starts print job
       */
      if (Escape(hPrnDC, STARTDOC, lstrlen((LPSTR)szJobNameTrunc), (LPSTR)
                 szJobNameTrunc, NULL) < 0)
      {

         // Oops, something happened, let's clean up here and return
         DestroyWindow(hDlgAbort);   // Remove abort dialog box
         FreeProcInstance(lpAbortProc);
         FreeProcInstance(lpAbortDlg);
         DeleteDC(hPrnDC);
         GlobalUnlock(hDib);
         return (ERR_STARTDOC);
      }

      /*
       * Fill in initial values for our BandInfo Structure to
       * tell driver we can want to do graphics and text, and
       * also which area we want the graphics to go in.
       */
      biBandInfo.bGraphics = TRUE;
      biBandInfo.bText = TRUE;
      biBandInfo.GraphicsRect = rPrintRect;

      /*
       * Check if need to do banding.  If we do, loop through
       *  each band in the page, calling NEXTBAND and BANDINFO
       *  (if supported) calling PrintBand() on the band.  Else,
       *  call PrintBand() with the entire page as our clipping
       *  rectangle!
       */
      nTemp = NEXTBAND;
      if (Escape(hPrnDC, QUERYESCSUPPORT, sizeof(int), (LPSTR)&nTemp, NULL))
      {
         BOOL bBandInfoDevice;

         /*
          * Check if device supports the BANDINFO escape.
          */

         nTemp = BANDINFO;
         bBandInfoDevice = Escape(hPrnDC, QUERYESCSUPPORT, sizeof(int), (LPSTR
                                  )&nTemp, NULL);

         /*
          * Do each band -- Call Escape() with NEXTBAND, then the
          * rect structure returned is the area where we are to
          * print in.  This loop exits when the rect area is empty.
          */
         while (Escape(hPrnDC, NEXTBAND, NULL, NULL, (LPSTR)&rect) && !
                IsRectEmpty(&rect))
         {
            char szTmpBuf[100];

            /*
             * Do the BANDINFO, if needed.
             */

            if (bBandInfoDevice)
               Escape(hPrnDC, BANDINFO, sizeof(BANDINFOSTRUCT), (LPSTR)&
                      biBandInfo, (LPSTR)&biBandInfo);
            wsprintf(szTmpBuf, "Printing Band Number %d", ++nBandCount);
            SetDlgItemText(hDlgAbort, IDC_PERCENTAGE, (LPSTR)szTmpBuf);

            /*
             * Call PrintBand() to do actual output into band.
             *  Pass in our band-info flags to tell what sort
             *  of data to output into the band.  Note that on
             *  non-banding devices, we pass in the default bandinfo
             *  stuff set above (i.e. bText=TRUE, bGraphics=TRUE).
             */
            wErrorCode = PrintBand(hPrnDC, &rPrintRect, &rect,
                                   biBandInfo.bText, biBandInfo.bGraphics,
                                   lpDIBHdr, lpBits);
         }
      }
      else
      {

         /*
          * Print the whole page -- non-banding device.
          */
         rect = rPrintRect;
         SetDlgItemText(hDlgAbort, IDC_PERCENTAGE, (LPSTR)
                        "Sending bitmap to printer...");
         wErrorCode = PrintBand(hPrnDC, &rPrintRect, &rect, TRUE, TRUE,
                                lpDIBHdr, lpBits);

         /*
          * Non-banding devices need a NEWFRAME
          */
         if (Escape(hPrnDC, NEWFRAME, NULL, NULL, NULL) < 0)
            return (ERR_NEWFRAME);
      }

      /*
       * End the print operation.  Only send the ENDDOC if
       *   we didn't abort or error.
       */
      if (!bAbort)
      {
         if (Escape(hPrnDC, ENDDOC, NULL, NULL, NULL) < 0)
         {
            /*
             * We errored out on ENDDOC, but don't return here - we still
             * need to close the dialog box, free proc instances, etc.
             */
            wErrorCode = ERR_ENDDOC;
         }
         DestroyWindow(hDlgAbort);
      }


      /*
       * All done, clean up.
       */
      FreeProcInstance(lpAbortProc);
      FreeProcInstance(lpAbortDlg);
      DeleteDC(hPrnDC);
   }
   else
      wErrorCode = ERR_GETDC;   // Couldn't get Printer DC!
   GlobalUnlock(hDib);
   return (wErrorCode);
}




// *******************************************************************
// Auxilirary Functions
//     -- Local to this module only
// *******************************************************************


/*********************************************************************
 *
 * CalculatePrintRect()
 *
 * Given fPrintOpt and a size of the DIB, return the area on the
 * printer where the image should go (in printer coordinates).  If
 * fPrintOpt is PW_SCALE, then lpPrintRect.left and .top should
 * contain WORDs which specify the scaling factor for the X and
 * Y directions, respecively.
 *
 ********************************************************************/


void CalculatePrintRect(HDC hDC,             // HDC to printer DC
                        LPRECT lpPrintRect,  // Returned PrintRect
                        WORD fPrintOpt,      // Options
                        DWORD cxDIB,         // Size of DIB - x
                        DWORD cyDIB)         // Size of DIB - y
{
   int cxPage, cyPage, cxInch, cyInch;

   if (!hDC)
      return;

   /*
    * Get some info from printer driver
    */
   cxPage = GetDeviceCaps(hDC, HORZRES);     // Width of printr page - pixels
   cyPage = GetDeviceCaps(hDC, VERTRES);     // Height of printr page - pixels
   cxInch = GetDeviceCaps(hDC, LOGPIXELSX);  // Printer pixels per inch - X
   cyInch = GetDeviceCaps(hDC, LOGPIXELSY);  // Printer pixels per inch - Y
   switch (fPrintOpt)
      {

   /*
    * Best Fit case -- create a rectangle which preserves
    * the DIB's aspect ratio, and fills the page horizontally.
    *
    * The formula in the "->bottom" field below calculates the Y
    * position of the printed bitmap, based on the size of the
    * bitmap, the width of the page, and the relative size of
    * a printed pixel (cyInch / cxInch).
    */
   case PW_BESTFIT:
      lpPrintRect->top = 0;
      lpPrintRect->left = 0;
      lpPrintRect->bottom = (int)(((double)cyDIB * cxPage * cyInch) / ((double
                            )cxDIB * cxInch));
      lpPrintRect->right = cxPage;
      break;

   /*
    * Scaling option -- lpPrintRect's top/left contain
    * multipliers to multiply the DIB's height/width by.
    */

   case PW_SCALE:
   {
      int cxMult, cyMult;

      cxMult = lpPrintRect->left;
      cyMult = lpPrintRect->top;
      lpPrintRect->top = 0;
      lpPrintRect->left = 0;
      lpPrintRect->bottom = (int)(cyDIB * cyMult);
      lpPrintRect->right = (int)(cxDIB * cxMult);
   }
      break;

   /*
    * Stretch To Page case -- create a rectangle
    * which covers the entire printing page (note that this
    * is also the default).
    */
   case PW_STRETCHTOPAGE:
   default:
      lpPrintRect->top = 0;
      lpPrintRect->left = 0;
      lpPrintRect->bottom = cyPage;
      lpPrintRect->right = cxPage;
      break;
      }
}



/*********************************************************************
 *
 * PrintBand()
 *
 * This routine does ALL output to the printer.  It is called from
 * the PrintDIB() routine.  It is called for both banding and non-
 * banding printing devices.  lpRectClip contains the rectangular
 * area we should do our output into (i.e. we should clip our output
 * to this area).  The flags fDoText and fDoGraphics should be set
 * appropriately (if we want any text output to the rectangle, set
 * fDoText to true).  Normally these flags are returned on banding
 * devices which support the BANDINFO escape.
 *
 ********************************************************************/


WORD PrintBand(HDC hDC,           // Handle to the Printer DC
               LPRECT lpRectOut,  // Rect where entire DIB is to go
               LPRECT lpRectClip, // Clippping rect where this portion goes
               BOOL fDoText,      // TRUE if this band is for text
               BOOL fDoGraphics,  // TRUE if this band is for graphics
               LPBITMAPINFOHEADER lpDIBHdr,   // Pointer to DIB header
               LPSTR lpDIBBits)   // Pointer to DIB bits
{
   RECT rect;                   // Temporary rectangle
   double dblXScaling,          // X and Y scaling factors
          dblYScaling;
   WORD wReturn = 0;            // Return code

   if (fDoGraphics)
   {
      dblXScaling = ((double)lpRectOut->right - lpRectOut->left) / (double)
                    lpDIBHdr->biWidth;
      dblYScaling = ((double)lpRectOut->bottom - lpRectOut->top) / (double)
                    lpDIBHdr->biHeight;
      /*
       * Now we set up a temporary rectangle -- this rectangle
       *  holds the coordinates on the paper where our bitmap
       *  WILL be output.  We can intersect this rectangle with
       *  the lpClipRect to see what we NEED to output to this
       *  band.  Then, we determine the coordinates in the DIB
       *  to which this rectangle corresponds (using dbl?Scaling).
       */
      IntersectRect(&rect, lpRectOut, lpRectClip);
      if (!IsRectEmpty(&rect))
      {
         RECT rectIn;

         rectIn.left = (int)((rect.left - lpRectOut->left) / dblXScaling + 0.5
                       );
         rectIn.top = (int)((rect.top - lpRectOut->top) / dblYScaling + 0.5);
         rectIn.right = (int)(rectIn.left + (rect.right - rect.left) /
                        dblXScaling + 0.5);
         rectIn.bottom = (int)(rectIn.top + (rect.bottom - rect.top) /
                         dblYScaling + 0.5);
         if (!StretchDIBits(hDC,                              // DestDC
                            rect.left,                        // DestX
                            rect.top,                         // DestY
                            rect.right - rect.left,           // DestWidth
                            rect.bottom - rect.top,           // DestHeight
                            rectIn.left,                      // SrcX
                            (int)(lpDIBHdr->biHeight) -       // SrcY
                            rectIn.top - (rectIn.bottom - rectIn.top),
                            rectIn.right - rectIn.left,       // SrcWidth
                            rectIn.bottom - rectIn.top,       // SrcHeight
                            lpDIBBits,                        // lpBits
                            (LPBITMAPINFO)lpDIBHdr,           // lpBitInfo
                            DIB_RGB_COLORS,                   // wUsage
                            SRCCOPY))                         // dwROP
            wReturn = ERR_STRETCHDIBITS; // StretchDIBits() failed!
      }
   }
   return wReturn;
}


/***********************************************************************
 *
 * GetPrinterDC()
 *
 * Return a DC to the currently selected printer.
 * Returns NULL on error.
 *
 ***********************************************************************/


HDC GetPrinterDC(void)
{
   static char szPrinter[64];
   char *szDevice, *szDriver, *szOutput;

   GetProfileString("windows", "device", "", szPrinter, 64);
   if ((szDevice = strtok(szPrinter, ",")) && (szDriver = strtok(NULL, ", "))
       && (szOutput = strtok(NULL, ", ")))
   {
      lstrcpy((LPSTR)gszDevice, (LPSTR)szDevice);    // Copy to global variables
      lstrcpy((LPSTR)gszOutput, (LPSTR)szOutput);
      return CreateDC(szDriver, szDevice, szOutput, NULL);
   }
   return NULL;
}


/**********************************************************************
 * PrintAbortProc()
 *
 * Abort procedure - contains the message loop while printing is
 * in progress.  By using a PeekMessage() loop, multitasking
 * can occur during printing.
 *
 **********************************************************************/


BOOL FAR PASCAL PrintAbortProc(HDC hDC, short code)
{
   MSG msg;

   while (!bAbort && PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
      if (!IsDialogMessage(hDlgAbort, &msg))
      {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
   return (!bAbort);
}

/***********************************************************************
 *
 * PrintAbortDlg()
 *
 *
 * This is the Dialog Procedure which will handle the "Now Printing"
 * dialog box.  When the user presses the "Cancel" button, the
 * global variable bAbort is set to TRUE, which causes the
 * PrintAbortProc to exit, which in turn causes the printing
 * operation to terminate.
 *
 ***********************************************************************/


int FAR PASCAL PrintAbortDlg(HWND hWnd,   /* Handle to dialog box */ unsigned
                             msg, /* Message */ WORD wParam, LONG lParam)
{
   switch (msg)
      {
   case WM_INITDIALOG:
   {
      char szBuffer[100];

      /*
       * Fill in the text which specifies where this bitmap
       * is going ("on HP LaserJet on LPT1", for example)
       */

      wsprintf(szBuffer, "on %s on %s", (LPSTR)gszDevice, (LPSTR)gszOutput);
      SetDlgItemText(hWnd, IDC_PRINTTEXT2, (LPSTR)szBuffer);
      SetFocus(GetDlgItem(hWnd, IDCANCEL));
   }
      return TRUE;     // Return TRUE because we called SetFocus()

   case WM_COMMAND:
      bAbort = TRUE;
      DestroyWindow(hWnd);
      return TRUE;
      break;
      }
   return FALSE;
}
