//********************************************************************
//
//  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
//                   Dan Ruder
//
// File change history:
// Date      Author             Reason
// 12/07/92  Mark Bader         Ported to Win32
//  1/26/94  Hung H. Nguyen     finished port & addes strict type checking
//
//
// Written by Microsoft Product Support Services, Developer Support.
// Copyright (c) 1991 Microsoft Corporation. All rights reserved.
//********************************************************************

#define     STRICT      // enable strict type checking

#include <windows.h>
#include <string.h>
#include "dibdll.h"     // Header for printing dialog & DLL instance handle
#include "dibapi.h"     // Header for DIB functions
#include "dibutil.h"    // Auxiliary functions


extern HANDLE   ghDLLInst;      // Global handle to DLL's instance

/***************************************************************
 * 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 CALLBACK PrintAbortProc(HDC, int);
int CALLBACK PrintAbortDlg(HWND, UINT, WPARAM, LPARAM);
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
 *
 * History:
 *
 *     Date      Author       Reason
 *     9/15/91   Mark Bader   Created
 *
 **********************************************************************/


WORD PrintWindow(HWND hWnd, WORD fPrintArea, WORD fPrintOpt, WORD wXScale,
        WORD wYScale, LPSTR szJobName)
{
    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
 *
 * History:
 *
 *     Date      Author       Reason
 *     9/15/91   Mark Bader   Created
 *
 **********************************************************************/

WORD PrintScreen(LPRECT rRegion, WORD fPrintOpt, WORD wXScale, WORD wYScale,
        LPSTR szJobName)
{
    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
 *
 * History:
 *
 *     Date      Author         Reason
 *     9/15/91   Mark Bader     Created, based on DIBVIEW's DibPrint()
 *     2/1/94    Hung Nguyen    Changed Escape() to printing APIs
 *     2/4/94    Hung Nguyen    Change printing so that banding is used only
 *                              under Win32s
 *
 ********************************************************************/

WORD PrintDIB(HDIB hDib, WORD fPrintOpt, WORD wXScale, WORD wYScale,
        LPSTR szJobName)
{
    HDC                 hPrnDC;         // DC to the printer
    RECT                rect;           // Rect structure used for banding
    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;     // 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
    DOCINFO             DocInfo;        // structure for StartDoc
    int                 nTemp;          // used to check banding capability


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

        hDlgAbort = CreateDialog(ghDLLInst, szPrintDlg, GetFocus(),
                (DLGPROC)PrintAbortDlg);

        // ISet 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() procedure if the user selects the
        // CANCEL button in our dialog box

        bAbort = FALSE;

        // set up the Abort Procedure

        if (SetAbortProc(hPrnDC, (ABORTPROC)PrintAbortProc) < 0)
                return ERR_SETABORTPROC;

        // start print job

        DocInfo.cbSize = sizeof(DOCINFO);
        DocInfo.lpszDocName = (LPTSTR)szJobNameTrunc;
        DocInfo.lpszOutput = NULL;

        if (StartDoc(hPrnDC, &DocInfo) < 0)
        {
            // Oops, something happened, let's clean up here and return

             DestroyWindow(hDlgAbort);   // Remove abort dialog box
             DeleteDC(hPrnDC);
             GlobalUnlock(hDib);
             return ERR_STARTDOC;
        }

// Note: the following banding code applies to Windows 3.1.  With the new
//       printing architecture of Win32, send out both the graphics and
//       text in one band (like a non-banding device).  This code is used
//       for Win32s since Win32s depends on Windows 3.1 printing architecture.
//
        // 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!

        // If Wincap32 is running on Win32s, then use banding

        nTemp = NEXTBAND;
        if (Escape(hPrnDC, QUERYESCSUPPORT, sizeof(int), (LPSTR)&nTemp, NULL) &&
                (HIWORD(GetVersion()) & 0x8000))
        {
            BOOL                bBandInfoDevice;
            BANDINFOSTRUCT      biBandInfo;         // Used for banding

            // 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 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, 0, 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 (EndPage(hPrnDC) < 0)
                return ERR_NEWFRAME;
        }


        // End the print operation.  Only send the ENDDOC if
        //  we didn't abort or error.

        if (!bAbort)
        {
            // We errored out on ENDDOC, but don't return here - we still
            // need to close the dialog box, free proc instances, etc.

            if (EndDoc(hPrnDC) < 0)
                wErrorCode = ERR_ENDDOC;

            DestroyWindow(hDlgAbort);
        }

        // All done, clean up.

        DeleteDC(hPrnDC);
    }
    else
        wErrorCode = ERR_GETDC;   // Couldn't get Printer DC!

    GlobalUnlock(hDib);
    return wErrorCode;
}




// *******************************************************************
// Auxiliary 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.
 *
 * History:
 *
 *     Date      Author       Reason
 *     9/15/91   Mark Bader   Created based on DIBVIEW
 *
 ********************************************************************/

void CalculatePrintRect(HDC hDC, LPRECT lpPrintRect, WORD fPrintOpt,
        DWORD cxDIB, DWORD cyDIB)
{
    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.
 *
 * History:
 *
 *     Date      Author       Reason
 *     9/15/91   Mark Bader   Created based on DIBVIEW
 *
 ********************************************************************/

WORD PrintBand(HDC hDC, LPRECT lpRectOut, LPRECT lpRectClip, BOOL fDoText,
        BOOL fDoGraphics, LPBITMAPINFOHEADER lpDIBHdr, LPSTR lpDIBBits)
{
    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, rect.left, rect.top,
                    rect.right - rect.left, rect.bottom - rect.top,
                    rectIn.left, (int)(lpDIBHdr->biHeight) -
                    rectIn.top - (rectIn.bottom - rectIn.top),
                    rectIn.right - rectIn.left, rectIn.bottom - rectIn.top,
                    lpDIBBits, (LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS,
                    SRCCOPY))
                wReturn = ERR_STRETCHDIBITS; // StretchDIBits() failed!
        }
    }

    return wReturn;
}


/***********************************************************************
 *
 * GetPrinterDC()
 *
 * Uses PrinDlg common dialog for printer selection and creates a dc.
 * Returns NULL on error.
 *
 * History:
 *
 *     Date      Author       Reason
 *     9/15/91   Mark Bader   Created based on DIBVIEW
 *     2/1/94    Hung Nguyen  Changed to use PrintDlg common dialog
 ***********************************************************************/

HDC GetPrinterDC() {

    PRINTDLG pd;

    pd.lStructSize = sizeof(PRINTDLG);
    pd.hDevMode = (HANDLE) NULL;
    pd.hDevNames = (HANDLE) NULL;
    pd.Flags = PD_RETURNDC;
    pd.hwndOwner = NULL;
    pd.hDC = (HDC) NULL;
    pd.nFromPage = 1;
    pd.nToPage = 1;
    pd.nMinPage = 0;
    pd.nMaxPage = 0;
    pd.nCopies = 1;
    pd.hInstance = (HANDLE) NULL;
    pd.lCustData = 0L;
    pd.lpfnPrintHook = (LPPRINTHOOKPROC) NULL;
    pd.lpfnSetupHook = (LPSETUPHOOKPROC) NULL;

    pd.lpPrintTemplateName = (LPSTR) NULL;
    pd.lpSetupTemplateName = (LPSTR)  NULL;
    pd.hPrintTemplate = (HANDLE) NULL;
    pd.hSetupTemplate = (HANDLE) NULL;

    if (PrintDlg(&pd) == TRUE)
    {
        DEVNAMES    *pDevNames = GlobalLock(pd.hDevNames);

        lstrcpy((LPSTR)gszDevice,
                (LPSTR)((char *)pDevNames+pDevNames->wDeviceOffset));

        if(!lstrcmpi((LPSTR)((char *)pDevNames+pDevNames->wDeviceOffset),
                (LPSTR)((char *)pDevNames+pDevNames->wOutputOffset)))
            lstrcpy((LPSTR)gszOutput, "net:");
        else
            lstrcpy((LPSTR)gszOutput,
                    (LPSTR)((char *)pDevNames+pDevNames->wOutputOffset));

        GlobalUnlock(pd.hDevNames);
        return pd.hDC;
    }

    else
        return NULL;
}


/**********************************************************************
 * PrintAbortProc()
 *
 * Abort procedure - contains the message loop while printing is
 * in progress.  By using a PeekMessage() loop, multitasking
 * can occur during printing.
 *
 * History:
 *
 *     Date      Author       Reason
 *     9/15/91   Mark Bader   Created
 *
 **********************************************************************/

BOOL CALLBACK PrintAbortProc(HDC hDC, int 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.
 *
 * History:
 *
 *     Date      Author       Reason
 *     9/15/91   Mark Bader   Created
 *
 ***********************************************************************/

int CALLBACK PrintAbortDlg(HWND hWnd, UINT msg, WPARAM wParam, LPARAM 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, "to %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;
}
