/*++

Copyright (c) 1990-1998  Microsoft Corporation
All rights reserved


Abstract:

    Routines to facilitate printing of EMF jobs.

--*/

#include "local.h"
#include "stddef.h"
#include <windef.h>
#include <messages.h>


#include <winppi.h>

#define EMF_DUP_NONE 0
#define EMF_DUP_VERT 1
#define EMF_DUP_HORZ 2

#define EMF_DEGREE_90  1
#define EMF_DEGREE_270 2


//   PAGE_NUMBER is used to save a list of the page numbers to start new sides while
//   Reverse Printing.

typedef struct _PAGE_NUMBER {
    struct _PAGE_NUMBER *pNext;
    DWORD  dwPageNumber;
} PAGE_NUMBER, *PPAGE_NUMBER;

typedef struct _UpdateRect {
        double  top;
        double  bottom;
        double  left;
        double  right;
}  UpdateRect;

// The update factors for the different nup options. These factors when multiplied
// with the horizontal and vertical resolutions give the coordinates for the rectangle
// where the EMF page is to be played.

UpdateRect URect21[] = {{0, 0.5, 0, 1},
                        {0.5, 1, 0, 1}};

UpdateRect URect21R[] = {{0.5, 1, 0, 1},
                         {0, 0.5, 0, 1}};

UpdateRect URect22[] = {{0, 1, 0, 0.5},
                        {0, 1, 0.5, 1}};

UpdateRect URect4[] = {{0, 0.5, 0, 0.5},
                       {0, 0.5, 0.5, 1},
                       {0.5, 1, 0, 0.5},
                       {0.5, 1, 0.5, 1}};

UpdateRect URect61[] = {{0, 1.0/3.0, 0, 0.5},
                        {0, 1.0/3.0, 0.5, 1},
                        {1.0/3.0, 2.0/3.0, 0, 0.5},
                        {1.0/3.0, 2.0/3.0, 0.5, 1},
                        {2.0/3.0, 1, 0, 0.5},
                        {2.0/3.0, 1, 0.5, 1}};

UpdateRect URect61R[] = {{2.0/3.0, 1, 0, 0.5},
                         {1.0/3.0, 2.0/3.0, 0, 0.5},
                         {0, 1.0/3.0, 0, 0.5},
                         {2.0/3.0, 1, 0.5, 1},
                         {1.0/3.0, 2.0/3.0, 0.5, 1},
                         {0, 1.0/3.0, 0.5, 1}};

UpdateRect URect62[]  = {{0, 0.5, 0, 1.0/3.0},
                         {0, 0.5, 1.0/3.0, 2.0/3.0},
                         {0, 0.5, 2.0/3.0, 1},
                         {0.5, 1, 0, 1.0/3.0},
                         {0.5, 1, 1.0/3.0, 2.0/3.0},
                         {0.5, 1, 2.0/3.0, 1}};

UpdateRect URect62R[] = {{0.5, 1, 0, 1.0/3.0},
                         {0, 0.5, 0, 1.0/3.0},
                         {0.5, 1, 1.0/3.0, 2.0/3.0},
                         {0, 0.5, 1.0/3.0, 2.0/3.0},
                         {0.5, 1, 2.0/3.0, 1},
                         {0, 0.5, 2.0/3.0, 1}};

UpdateRect URect9[] = {{0, 1.0/3.0, 0, 1.0/3.0},
                       {0, 1.0/3.0, 1.0/3.0, 2.0/3.0},
                       {0, 1.0/3.0, 2.0/3.0, 1},
                       {1.0/3.0, 2.0/3.0, 0, 1.0/3.0},
                       {1.0/3.0, 2.0/3.0, 1.0/3.0, 2.0/3.0},
                       {1.0/3.0, 2.0/3.0, 2.0/3.0, 1},
                       {2.0/3.0, 1, 0, 1.0/3.0},
                       {2.0/3.0, 1, 1.0/3.0, 2.0/3.0},
                       {2.0/3.0, 1, 2.0/3.0, 1}};

UpdateRect URect16[] = {{0, 0.25, 0, 0.25},
                        {0, 0.25, 0.25, 0.5},
                        {0, 0.25, 0.5, 0.75},
                        {0, 0.25, 0.75, 1},
                        {0.25, 0.5, 0, 0.25},
                        {0.25, 0.5, 0.25, 0.5},
                        {0.25, 0.5, 0.5, 0.75},
                        {0.25, 0.5, 0.75, 1},
                        {0.5, 0.75, 0, 0.25},
                        {0.5, 0.75, 0.25, 0.5},
                        {0.5, 0.75, 0.5, 0.75},
                        {0.5, 0.75, 0.75, 1},
                        {0.75, 1, 0, 0.25},
                        {0.75, 1, 0.25, 0.5},
                        {0.75, 1, 0.5, 0.75},
                        {0.75, 1, 0.75, 1}};

BOOL
ValidNumberForNUp(
    DWORD  dwPages)

/*++
Function Description: Checks if the number of pages printed on a single side is Valid.

Parameters: dwPages - Number of pages printed on a single side

Return Values: TRUE if (dwPages = 1|2|4|6|9|16)
               FALSE otherwise.
--*/

{

    return ((dwPages == 1) || (dwPages == 2) || (dwPages == 4) ||
            (dwPages == 6) || (dwPages == 9) || (dwPages == 16));
}

VOID
GetPageCoordinatesForNUp(
    HDC    hPrinterDC,
    RECT   *rectDocument,
    RECT   *rectBorder,
    DWORD  dwTotalNumberOfPages,
    UINT   uCurrentPageNumber,
    DWORD  dwNupBorderFlags,
    LPBOOL pbRotate
    )

/*++
Function Description: GetPageCoordinatesForNUp computes the rectangle on the Page where the
                      EMF file is to be played. It also determines if the picture is to
                      rotated.

Parameters:  hPrinterDC           - Printer Device Context
             *rectDocument        - pointer to RECT where the coordinates to play the
                                     page will be returned.
             *rectBorder          - pointer to RECT where the page borders are to drawn.
             dwTotalNumberOfPages - Total number of pages on 1 side.
             uCurrentPageNumber   - 1 based page number on the side.
             dwNupBorderFlags     - flags to draw border along logical pages.
             pbRotate             - pointer to BOOL which indicates if the picture must be
                                    rotated.

Return Values:  NONE.
--*/

{

    UpdateRect  *URect;
    LONG        lXPrintPage,lYPrintPage,lXPhyPage,lYPhyPage,lXFrame,lYFrame,ltemp,ldX,ldY;
    LONG        lXNewPhyPage,lYNewPhyPage,lXOffset,lYOffset,lNumRowCol,lRowIndex,lColIndex;
    double      dXleft,dXright,dYtop,dYbottom;
    LONG        xResolution = GetDeviceCaps(hPrinterDC, LOGPIXELSX);
    LONG        yResolution = GetDeviceCaps(hPrinterDC, LOGPIXELSY);
    
    // Get the 0-based array index for the current page

    uCurrentPageNumber = uCurrentPageNumber - 1;

    if (dwTotalNumberOfPages==1 || xResolution==yResolution) 
    {
        xResolution = yResolution = 1;
    }

    rectDocument->top = rectDocument->bottom = lYPrintPage = (GetDeviceCaps(hPrinterDC, DESKTOPVERTRES)-1) * xResolution;
    rectDocument->left = rectDocument->right = lXPrintPage = (GetDeviceCaps(hPrinterDC, DESKTOPHORZRES)-1) * yResolution;

    lXPhyPage = GetDeviceCaps(hPrinterDC, PHYSICALWIDTH)  * yResolution;
    lYPhyPage = GetDeviceCaps(hPrinterDC, PHYSICALHEIGHT) * xResolution;

    *pbRotate = FALSE;

    // Select the array containing the update factors

    switch (dwTotalNumberOfPages) {

    case 1: rectDocument->top = rectDocument->left = 0;
            rectDocument->right += 1;
            rectDocument->bottom += 1;
            return;

    case 2: if (lXPrintPage > lYPrintPage) {  // cut vertically
                URect = URect22;
                lXFrame = (LONG) (lXPrintPage / 2.0);
                lYFrame = lYPrintPage;
            } else {                          // cut horizontally
                URect = URect21;
                lYFrame = (LONG) (lYPrintPage / 2.0);
                lXFrame = lXPrintPage;
            }
            break;


    case 4: URect = URect4;
            lXFrame = (LONG) (lXPrintPage / 2.0);
            lYFrame = (LONG) (lYPrintPage / 2.0);
            break;

    case 6: if (lXPrintPage > lYPrintPage) {  // cut vertically twice
                URect = URect62;
                lXFrame = (LONG) (lXPrintPage / 3.0);
                lYFrame = (LONG) (lYPrintPage / 2.0);
            } else {                          // cut horizontally twice
                URect = URect61;
                lYFrame = (LONG) (lYPrintPage / 3.0);
                lXFrame = (LONG) (lXPrintPage / 2.0);
            }
            break;

    case 9: URect = URect9;
            lXFrame = (LONG) (lXPrintPage / 3.0);
            lYFrame = (LONG) (lYPrintPage / 3.0);
            break;

    case 16: URect = URect16;
             lXFrame = (LONG) (lXPrintPage / 4.0);
             lYFrame = (LONG) (lYPrintPage / 4.0);
             break;

    default: // Should Not Occur.
             return;
    }

    // Set the flag if the picture has to be rotated
    *pbRotate = !((lXPhyPage >= lYPhyPage) && (lXFrame >= lYFrame)) &&
                !((lXPhyPage < lYPhyPage) && (lXFrame < lYFrame));
    

    // If the picture is to be rotated, modify the rectangle selected.

    if ((dwTotalNumberOfPages == 2) || (dwTotalNumberOfPages == 6)) {

       if (*pbRotate) {
          switch (dwTotalNumberOfPages) {

          case 2: if (lXPrintPage <= lYPrintPage) {
                      URect = URect21R;
                  } // URect22 = URect22R
                  break;

          case 6: if (lXPrintPage <= lYPrintPage) {
                      URect = URect61R;
                  } else {
                      URect = URect62R;
                  }
                  break;
          }
       }

    } else {

       if (*pbRotate) {

          // get the number of rows/columns. switch is faster than sqrt.
          switch (dwTotalNumberOfPages) {

          case 4: lNumRowCol = 2;
                  break;
          case 9: lNumRowCol = 3;
                  break;
          case 16: lNumRowCol = 4;
                  break;
          }

          lRowIndex  = (LONG) (uCurrentPageNumber / lNumRowCol);
          lColIndex  = (LONG) (uCurrentPageNumber % lNumRowCol);

          uCurrentPageNumber = (lNumRowCol - 1 - lColIndex) * lNumRowCol + lRowIndex;
       }

    }

    // Update the Page Coordinates.

    rectDocument->top    = (LONG) (rectDocument->top    * URect[uCurrentPageNumber].top);
    rectDocument->bottom = (LONG) (rectDocument->bottom * URect[uCurrentPageNumber].bottom);
    rectDocument->left   = (LONG) (rectDocument->left   * URect[uCurrentPageNumber].left);
    rectDocument->right  = (LONG) (rectDocument->right  * URect[uCurrentPageNumber].right);

    // If the page border has to drawn, return the corresponding coordinates in rectBorder.

    if (dwNupBorderFlags == BORDER_PRINT) {
        rectBorder->top    = rectDocument->top/xResolution;
        rectBorder->bottom = rectDocument->bottom/xResolution - 1;
        rectBorder->left   = rectDocument->left/yResolution;
        rectBorder->right  = rectDocument->right/yResolution - 1;
    }

    if (*pbRotate) {
        ltemp = lXFrame; lXFrame = lYFrame; lYFrame = ltemp;
    }

    // Get the new size of the rectangle to keep the X/Y ratio constant.
    if ( ((LONG) (lYFrame*((lXPhyPage*1.0)/lYPhyPage))) >= lXFrame) {
         ldX = 0;
         ldY = lYFrame - ((LONG) (lXFrame*((lYPhyPage*1.0)/lXPhyPage)));
    } else {
         ldY = 0;
         ldX = lXFrame - ((LONG) (lYFrame*((lXPhyPage*1.0)/lYPhyPage)));
    }

    // Adjust the position of the rectangle.

    if (*pbRotate) {
        if (ldX) {
            rectDocument->bottom -= (LONG) (ldX / 2.0);
            rectDocument->top    += (LONG) (ldX / 2.0);
        } else {
           rectDocument->right   -= (LONG) (ldY / 2.0);
           rectDocument->left    += (LONG) (ldY / 2.0);
        }
    } else {
        if (ldX) {
           rectDocument->left    += (LONG) (ldX / 2.0);
           rectDocument->right   -= (LONG) (ldX / 2.0);
        } else {
           rectDocument->top     += (LONG) (ldY / 2.0);
           rectDocument->bottom  -= (LONG) (ldY / 2.0);
        }
    }

    // Adjust to get the Printable Area on the rectangle

    lXOffset = GetDeviceCaps(hPrinterDC, PHYSICALOFFSETX) * yResolution;
    lYOffset = GetDeviceCaps(hPrinterDC, PHYSICALOFFSETY) * xResolution;

    dXleft = ( lXOffset * 1.0) / lXPhyPage;
    dYtop  = ( lYOffset * 1.0) / lYPhyPage;
    dXright =  ((lXPhyPage - (lXOffset + lXPrintPage)) * 1.0) / lXPhyPage;
    dYbottom = ((lYPhyPage - (lYOffset + lYPrintPage)) * 1.0) / lYPhyPage;

    lXNewPhyPage = rectDocument->right  - rectDocument->left;
    lYNewPhyPage = rectDocument->bottom - rectDocument->top;

    if (*pbRotate) {

       ltemp = lXNewPhyPage; lXNewPhyPage = lYNewPhyPage; lYNewPhyPage = ltemp;

       rectDocument->left   += (LONG) (dYtop    * lYNewPhyPage);
       rectDocument->right  -= (LONG) (dYbottom * lYNewPhyPage);
       rectDocument->top    += (LONG) (dXright  * lXNewPhyPage);
       rectDocument->bottom -= (LONG) (dXleft   * lXNewPhyPage);

    } else {

       rectDocument->left   += (LONG) (dXleft   * lXNewPhyPage);
       rectDocument->right  -= (LONG) (dXright  * lXNewPhyPage);
       rectDocument->top    += (LONG) (dYtop    * lYNewPhyPage);
       rectDocument->bottom -= (LONG) (dYbottom * lYNewPhyPage);
    }

    if (xResolution!=yResolution) 
    {
        rectDocument->left   = rectDocument->left   / yResolution;
        rectDocument->right  = rectDocument->right  / yResolution; 
        rectDocument->top    = rectDocument->top    / xResolution;
        rectDocument->bottom = rectDocument->bottom / xResolution; 
    }
    
    return;
}

BOOL
PlayEMFPage(
    HANDLE       hSpoolHandle,
    HDC          hPrinterDC,
    HANDLE       hEMF,
    DWORD        dwNumberOfPagesPerSide,
    DWORD        dwPageNumber,
    DWORD        dwPageIndex,
    DWORD        dwNupBorderFlags,
    DWORD        dwAngle)
    
/*++
Function Description: PlayEMFPage plays the EMF in the appropriate rectangle. It performs
                      the required scaling, rotation and translation.

Parameters:   hSpoolHandle           -- handle the spool file handle
              hPrinterDC             -- handle to the printer device context
              hEMF                   -- handle to the contents of the page in the spool file
              dwNumberOfPagesPerSide -- number of pages to be printed per side
              dwPageNumber           -- page number in the document
              dwPageIndex            -- page number in the side. (1 based)
              dwNupBorderFlags       -- border printing options for nup
              dwAngle                -- angle for rotation (if neccesary)

Return Values:  TRUE if successful
                FALSE otherwise
--*/
{
   BOOL         bReturn = FALSE, bRotate;
   RECT         rectDocument, rectPrinter, rectBorder = {-1, -1, -1, -1};
   RECT         *prectClip = NULL;
   XFORM        TransXForm = {1, 0, 0, 1, 0, 0}, RotateXForm = {0, -1, 1, 0, 0, 0};
   HPEN         hPen;
   HANDLE       hFormEMF;
   DWORD        dwPageType,dwFormPage;

   // Compute the rectangle for one page.
   GetPageCoordinatesForNUp(hPrinterDC,
                            &rectDocument,
                            &rectBorder,
                            dwNumberOfPagesPerSide,
                            dwPageIndex,
                            dwNupBorderFlags,
                            &bRotate);

   if (dwAngle == EMF_DEGREE_270) {
       RotateXForm.eM12 = 1;
       RotateXForm.eM21 = -1;
   }   // EMF_DEGREE_90 case is the initialization

   if (bRotate) {

       rectPrinter.top = 0;
       rectPrinter.bottom = rectDocument.right - rectDocument.left;
       rectPrinter.left = 0;
       rectPrinter.right = rectDocument.bottom - rectDocument.top;

       // Set the translation matrix
       if (dwAngle == EMF_DEGREE_270) {
           TransXForm.eDx = (float) rectDocument.right;
           TransXForm.eDy = (float) rectDocument.top;
       } else {
           // EMF_DEGREE_90
           TransXForm.eDx = (float) rectDocument.left;
           TransXForm.eDy = (float) rectDocument.bottom;
       }

       // Set the transformation matrix
       if (!SetWorldTransform(hPrinterDC, &RotateXForm) ||
           !ModifyWorldTransform(hPrinterDC, &TransXForm, MWT_RIGHTMULTIPLY)) {

            ODS(("Setting transformation matrix failed\n"));
            goto CleanUp;
       }
   }

   // Add clipping for Nup
   if (dwNumberOfPagesPerSide != 1) {

       prectClip = &rectDocument;
   }

   // Print the page.
   if (bRotate) {
       GdiPlayPageEMF(hSpoolHandle, hEMF, &rectPrinter, &rectBorder, prectClip);
   } else {
       GdiPlayPageEMF(hSpoolHandle, hEMF, &rectDocument, &rectBorder, prectClip);
   }

   bReturn = TRUE;

CleanUp:
   
   if (!ModifyWorldTransform(hPrinterDC, NULL, MWT_IDENTITY)) {

       ODS(("Setting Identity Transformation failed\n"));
       bReturn = FALSE;
   }

   return bReturn;
}

BOOL
SetDrvCopies(
    HDC          hPrinterDC,
    LPDEVMODEW   pDevmode,
    DWORD        dwNumberOfCopies)

/*++
Function Description: SetDrvCopies sets the dmCopies field in pDevmode and resets
                      hPrinterDC with this devmode

Parameters: hPrinterDC             -- handle to the printer device context
            pDevmode               -- pointer to devmode
            dwNumberOfCopies       -- value for dmCopies

Return Values:  TRUE if successful
                FALSE otherwise
--*/

{
    BOOL     bReturn;
    DWORD    dmFields;

    if ((pDevmode->dmFields & DM_COPIES) &&
        (pDevmode->dmCopies == (short) dwNumberOfCopies)) {

         return TRUE;
    }

    // Save the old fields structure
    dmFields = pDevmode->dmFields;
    pDevmode->dmFields |= DM_COPIES;
    pDevmode->dmCopies = (short) dwNumberOfCopies;

    if (!ResetDC(hPrinterDC, pDevmode))  {
        bReturn = FALSE;
    } else {
        bReturn = TRUE;
    }
    // Restore the fields structure
    pDevmode->dmFields = dmFields;

    if (!SetGraphicsMode(hPrinterDC,GM_ADVANCED)) {
        ODS(("Setting graphics mode failed\n"));
        bReturn = FALSE;
    }

    return bReturn;
}

BOOL
DifferentDevmodes(
    LPDEVMODE    pDevmode1,
    LPDEVMODE    pDevmode2
    )

/*++
Function Description: Compares the devmodes for differences other than dmTTOption

Parameters:  pDevmode1    -   devmode 1
             pDevmode2    -   devmode 2
             
Return Values: TRUE if different ; FALSE otherwise           
--*/

{
    DWORD   dwSize1, dwSize2, dwTTOffset, dwSpecOffset, dwLogOffset;

    // Same pointers are the same devmode
    if (pDevmode1 == pDevmode2) {
        return FALSE;
    }

    // Check for Null devmodes
    if (!pDevmode1 || !pDevmode2) {
        return TRUE;
    }

    dwSize1 = pDevmode1->dmSize + pDevmode1->dmDriverExtra;
    dwSize2 = pDevmode2->dmSize + pDevmode2->dmDriverExtra;

    // Compare devmode sizes
    if (dwSize1 != dwSize2) {
        return TRUE;
    }

    dwTTOffset = FIELD_OFFSET(DEVMODE, dmTTOption);
    dwSpecOffset = FIELD_OFFSET(DEVMODE, dmSpecVersion);
    dwLogOffset = FIELD_OFFSET(DEVMODE, dmLogPixels);

    if (wcscmp(pDevmode1->dmDeviceName,
               pDevmode2->dmDeviceName)) {
        // device names are different
        return TRUE;
    }

    if (dwTTOffset < dwSpecOffset ||
        dwSize1 < dwLogOffset) {

        // incorrent devmode offsets
        return TRUE;
    }

    if (memcmp((LPBYTE) pDevmode1 + dwSpecOffset,
               (LPBYTE) pDevmode2 + dwSpecOffset,
               dwTTOffset - dwSpecOffset)) {
        // Front half is different
        return TRUE;
    }

    // Ignore the dmTTOption setting.

    if ((pDevmode1->dmCollate != pDevmode2->dmCollate) ||
        wcscmp(pDevmode1->dmFormName, pDevmode2->dmFormName)) {
        
        // form name or collate option is different
        return TRUE;
    }

    if (memcmp((LPBYTE) pDevmode1 + dwLogOffset,
               (LPBYTE) pDevmode2 + dwLogOffset,
               dwSize1 - dwLogOffset)) {
        // Back half is different
        return TRUE;
    }

    return FALSE;
}


BOOL
ResetDCForNewDevmode(
    HANDLE       hSpoolHandle,
    HDC          hPrinterDC,
    DWORD        dwPageNumber,
    BOOL         bInsidePage,    
    DWORD        dwOptimization,
    LPBOOL       pbNewDevmode,
    LPDEVMODE    pDevmode
    )

/*++
Function Description: Determines if the devmode for the page is different from the 
                      current devmode for the printer dc and resets the dc if necessary.
                      The parameters allow dmTTOption to be ignored in devmode comparison.

Parameters: hSpoolHandle         -  spool file handle
            hPrinterDC           -  printer dc
            dwPageNumber         -  page number before which we search for the devmode
            bInsidePage          -  flag to ignore changes in TT options and call EndPage
                                       before ResetDC
            dwOptimization       -  optimization flags
            pbNewDevmode         -  pointer to flag to indicate if ResetDC was called
            pDevmode             -  devmode containing changed resolution settings

Return Values: TRUE if successful; FALSE otherwise
--*/

{
    BOOL           bReturn = FALSE;
    LPDEVMODE      pLastDM, pCurrDM;

    // Initialize OUT parameters
    *pbNewDevmode = FALSE;

    // Get the devmode just before the page
    if (!GdiGetDevmodeForPage(hSpoolHandle, 
                              dwPageNumber,
                              &pCurrDM,
                              &pLastDM)) {

        ODS(("GdiGetDevmodeForPage failed\n"));
        return bReturn;
    }

    // Check if the devmodes are different
    if (pLastDM != pCurrDM) {

        // If the pointers are different the devmodes are always different
        if (!bInsidePage ||
            DifferentDevmodes(pLastDM, pCurrDM)) {

            *pbNewDevmode = TRUE;
        }
    }

    // Call ResetDC on the hPrinterDC if necessary
    if (*pbNewDevmode) {

        if (bInsidePage &&
            !GdiEndPageEMF(hSpoolHandle, dwOptimization)) {

            ODS(("EndPage failed\n"));
            return bReturn;
        }

        if (pCurrDM) {
            pCurrDM->dmPrintQuality = pDevmode->dmPrintQuality;
            pCurrDM->dmYResolution = pDevmode->dmYResolution;
        }

        // Ignore the return values of ResetDC and SetGraphicsMode
        GdiResetDCEMF(hSpoolHandle, pCurrDM);
        SetGraphicsMode(hPrinterDC, GM_ADVANCED);       
    }

    bReturn = TRUE;

    return bReturn;
}

DWORD
PrintOneSideForwardEMF(
    HANDLE       hSpoolHandle,
    HDC          hPrinterDC,
    DWORD        dwNumberOfPagesPerSide,
    DWORD        dwDrvNumberOfPagesPerSide,
    DWORD        dwNupBorderFlags,
    BOOL         bDuplex,
    DWORD        dwOptimization,
    DWORD        dwPageNumber,
    LPBOOL       pbComplete,
    LPDEVMODE    pDevmode)

/*++
Function Description: PrintOneSideForwardEMF plays the next physical page in the same order
                      as the spool file.

Parameters: hSpoolHandle              -- handle the spool file handle
            hPrinterDC                -- handle to the printer device context
            dwNumberOfPagesPerSide    -- number of pages to be printed per side by the print processor
            dwDrvNumberOfPagesPerSide -- number of pages the driver will print per side
            dwNupBorderFlags          -- border printing options for nup
            bDuplex                   -- flag to indicate duplex printing
            dwOptimization            -- optimization flags
            dwPageNumber              -- pointer to the starting page number
            pbComplete                -- pointer to the flag to indicate completion
            pDevmode                  -- devmode with resolution settings
   
Return Values:  Last Page Number if successful
                0 on job completion (pbReturn set to TRUE) and
                  on failure (pbReturn remains FALSE)
--*/

{
    DWORD              dwPageIndex, dwPageType;
    DWORD              dwReturn = 0;
    LPDEVMODEW         pLastDM, pCurrDM;
    HANDLE             hEMF = NULL;
    DWORD              dwSides;
    BOOL               bNewDevmode;
    DWORD              cPagesToPlay;

    // set the number of sides on this page;
    dwSides = bDuplex ? 2 : 1;
    *pbComplete = FALSE;

    for ( ; dwSides && !*pbComplete ; --dwSides) {

       // loop for a single side
       for (dwPageIndex = 1;
            dwPageIndex <= dwNumberOfPagesPerSide;
            ++dwPageIndex, ++dwPageNumber) {

            if (!(hEMF = GdiGetPageHandle(hSpoolHandle,
                                          dwPageNumber,
                                          &dwPageType))) {

                if (GetLastError() == ERROR_NO_MORE_ITEMS) {
                     // End of the print job
                     *pbComplete = TRUE;
                     break;
                }

                ODS(("GdiGetPageHandle failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                goto CleanUp;
            }

            // Process new devmodes in the spool file that appear before this page
            if (!ResetDCForNewDevmode(hSpoolHandle,
                                      hPrinterDC,
                                      dwPageNumber,
                                      (dwPageIndex != 1),
                                      dwOptimization,
                                      &bNewDevmode,
                                      pDevmode)) {

                goto CleanUp;
            }

            // Reset page index if new page was started
            if (bNewDevmode) {
                dwPageIndex = 1;
            }

            // Call StartPage for each new page
            if ((dwPageIndex == 1) &&
                !GdiStartPageEMF(hSpoolHandle)) {

                ODS(("StartPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                goto CleanUp;
            }

            if (!PlayEMFPage(hSpoolHandle,
                             hPrinterDC,
                             hEMF,
                             dwNumberOfPagesPerSide,
                             dwPageNumber,
                             dwPageIndex,
                             dwNupBorderFlags,
                             EMF_DEGREE_90)) {

                ODS(("PlayEMFPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                goto CleanUp;
            }            
       }

       //
       // Explaination of the scinario set for the conditions on 
       // dwPageIndex1 , pbComplete and bDuplex.
       // N.B. we are naming them cond.1 and cond.2
       //     dwPageIndex!=1    pbComplete   bDuplex    Condition
       //           0               0           0       None    
       //           0               0           1       None
       //           0               1           0       None
       //           0               1           1       Cond2 on Second Side i.e. dwsides==1
       //           1               0           0       Cond1
       //           1               0           1       Cond1
       //           1               1           0       Cond1
       //           1               1           1       Cond1 & Cond2 on First Side i.e. dwsides==2
       //


       // cond.1
       if (dwPageIndex != 1) {

           // Call EndPage if we played any pages
           if (!GdiEndPageEMF(hSpoolHandle, dwOptimization)) {

               ODS(("EndPage failed\n"));
               *pbComplete = FALSE;
               goto CleanUp;
           }
       }

       // cond.2
       // play empty page on the back of duplex
       if (*pbComplete && bDuplex && dwDrvNumberOfPagesPerSide==1) {

           ODS(("PCL or PS with no N-up\n"));

           //
           // Checking dwsides against 2 or 1. 
           // depends on whether it is n-up or not.
           //
           if (((dwPageIndex!=1)?(dwSides==2):(dwSides==1))) {
          
               if (!GdiStartPageEMF(hSpoolHandle) ||
                   !GdiEndPageEMF(hSpoolHandle, dwOptimization)) {
 
                   ODS(("EndPage failed\n"));
                   *pbComplete = FALSE;
                   goto CleanUp;
               }
           }
        }
    }


    if (*pbComplete && 
        dwNumberOfPagesPerSide==1 && 
        dwDrvNumberOfPagesPerSide!=1 && 
        bDuplex &&
        (dwPageNumber-1)%(2*dwDrvNumberOfPagesPerSide)) 
    {
        //
        // Number of pages played on last phisical page
        //
        cPagesToPlay = 2*dwDrvNumberOfPagesPerSide - (dwPageNumber-1)%(2*dwDrvNumberOfPagesPerSide);

        ODS(("\nPS with N-up!\nMust fill in %u pages\n", cPagesToPlay));

        for (;cPagesToPlay;cPagesToPlay--) 
        {
            if (!GdiStartPageEMF(hSpoolHandle) || !GdiEndPageEMF(hSpoolHandle, dwOptimization)) 
            {
                ODS(("EndPage failed\n"));
                goto CleanUp;
            }
        }
    }
     

    if (!(*pbComplete)) dwReturn = dwPageNumber;

CleanUp:

    return dwReturn;
}

BOOL
PrintForwardEMF(
    HANDLE       hSpoolHandle,
    HDC          hPrinterDC,
    DWORD        dwNumberOfPagesPerSide,
    DWORD        dwDrvNumberOfPagesPerSide,
    DWORD        dwNupBorderFlags,
    DWORD        dwJobNumberOfCopies,
    DWORD        dwDrvNumberOfCopies,
    BOOL         bCollate,
    BOOL         bDuplex,
    DWORD        dwOptimization,
    LPDEVMODEW   pDevmode,
    PPRINTPROCESSORDATA pData)

/*++
Function Description: PrintForwardEMF plays the EMF files in the order in which they
                      were spooled.

Parameters: hSpoolHandle              -- handle the spool file handle
            hPrinterDC                -- handle to the printer device context
            dwNumberOfPagesPerSide    -- number of pages to be printed per side by the print processor
            dwDrvNumberOfPagesPerSide -- number of pages the driver will print per side
            dwNupBorderFlags          -- border printing options for nup
            dwJobNumberOfCopies       -- number of copies of the job to be printed
            dwDrvNumberOfCopies       -- number of copies that the driver can print
            bCollate                  -- flag for collating the copies
            bDuplex                   -- flag for duplex printing
            dwOptimization            -- optimization flags
            pDevmode                  -- pointer to devmode for changing the copy count
            pData                     -- needed for status and the handle of the event: pause, resume etc.

Return Values:  TRUE if successful
                FALSE otherwise
--*/

{
    DWORD              dwLastPageNumber = 1,dwPageNumber,dwPageIndex,dwRemainingCopies;
    BOOL               bReturn = FALSE;

    // Keep printing as long as the spool file contains EMF handles.
    while (dwLastPageNumber) {

        //
        // If the print processor is paused, wait for it to be resumed 
        //
        if (pData->fsStatus & PRINTPROCESSOR_PAUSED) {
            WaitForSingleObject(pData->semPaused, INFINITE);
        }

        dwPageNumber = dwLastPageNumber;

        if (bCollate) {

           dwLastPageNumber = PrintOneSideForwardEMF(hSpoolHandle,
                                                     hPrinterDC,
                                                     dwNumberOfPagesPerSide,
                                                     dwDrvNumberOfPagesPerSide,
                                                     dwNupBorderFlags,
                                                     bDuplex,
                                                     dwOptimization,
                                                     dwPageNumber,
                                                     &bReturn,
                                                     pDevmode);
        } else {

           dwRemainingCopies = dwJobNumberOfCopies;

           while (dwRemainingCopies) {

               if (dwRemainingCopies <= dwDrvNumberOfCopies) {
                  SetDrvCopies(hPrinterDC, pDevmode, dwRemainingCopies);
                  dwRemainingCopies = 0;
               } else {
                  SetDrvCopies(hPrinterDC, pDevmode, dwDrvNumberOfCopies);
                  dwRemainingCopies -= dwDrvNumberOfCopies;
               }

               //
               // If the print processor is paused, wait for it to be resumed 
               //
               if (pData->fsStatus & PRINTPROCESSOR_PAUSED) {
                    WaitForSingleObject(pData->semPaused, INFINITE);
               }

               if (!(dwLastPageNumber =  PrintOneSideForwardEMF(hSpoolHandle,
                                                                hPrinterDC,
                                                                dwNumberOfPagesPerSide,
                                                                dwDrvNumberOfPagesPerSide,
                                                                dwNupBorderFlags,
                                                                bDuplex,
                                                                dwOptimization,
                                                                dwPageNumber,
                                                                &bReturn,
                                                                pDevmode)) &&
                   !bReturn) {

                    goto CleanUp;
               }
           }
        }
    }

CleanUp:

    return bReturn;
}

BOOL
PrintOneSideReverseForDriverEMF(
    HANDLE       hSpoolHandle,
    HDC          hPrinterDC,
    DWORD        dwDrvNumberOfPagesPerSide,
    DWORD        dwTotalNumberOfPages,
    DWORD        dwNupBorderFlags,
    BOOL         bDuplex,
    DWORD        dwOptimization,
    DWORD        dwPageNumber,
    LPDEVMODE    pDevmode)

/*++
Function Description: PrintOneSideReverseForDriverEMF plays the EMF pages on the next
                      physical page, in the reverse order for the driver which does the
                      Nup transformations.

Parameters: hSpoolHandle           -- handle the spool file handle
            hPrinterDC             -- handle to the printer device context
            dwDrvNumberOfPagesPerSide -- number of pages the driver will print per side
            dwTotalNumberOfPages   -- total number of pages in the document
            dwNupBorderFlags       -- border printing options for nup
            bDuplex                -- flag to indicate duplex printing
            dwOptimization         -- optimization flags
            dwPageNumber           -- page number to start the side
            pDevmode               -- devmode with resolution settings

Return Values:  TRUE if successful
                FALSE otherwise
--*/
{
    DWORD       dwPageIndex, dwPageType, dwSides;
    BOOL        bReturn = FALSE, bNewDevmode,BeSmart;    
    LPDEVMODEW  pLastDM,pCurrDM;
    HANDLE      hEMF = NULL;
    DWORD       dwLimit;

    dwSides = bDuplex ? 2 : 1;

    //
    // If the document will fit on one phisical page, then this variable will prevent 
    // the printer from playing extra pages just to fill in one phisical page 
    // The exception is when the pages fit on a single phisical page, but they must
    // be collated. Then because of design, the printer will also draw borders for the
    // empty pages which are played so that the page gets ejected.
    //
    BeSmart =  (dwTotalNumberOfPages<=dwDrvNumberOfPagesPerSide) &&
               (pDevmode->dmCollate != DMCOLLATE_TRUE);
         
    for (; dwSides; --dwSides) {

       // This loop may play some empty pages in the last side, since the
       // driver is doing nup and it does not keep count of the page numbers
       //
       dwPageIndex=BeSmart?dwPageNumber:1;
       dwLimit    =BeSmart?dwTotalNumberOfPages:dwDrvNumberOfPagesPerSide;

       for (;dwPageIndex<=dwLimit; ++dwPageIndex,++dwPageNumber) {

             if (BeSmart || dwPageNumber <= dwTotalNumberOfPages) {

                 if (!(hEMF = GdiGetPageHandle(hSpoolHandle,
                                               dwPageNumber,
                                               &dwPageType))) {
                     ODS(("GdiGetPageHandle failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                     goto CleanUp;
                 }

                 // Process new devmodes in the spoolfile
                 if (!ResetDCForNewDevmode(hSpoolHandle,
                                           hPrinterDC,
                                           dwPageNumber,
                                           FALSE,
                                           dwOptimization,
                                           &bNewDevmode,
                                           pDevmode)) {
                 }
             }

             if (!GdiStartPageEMF(hSpoolHandle)) {
                 ODS(("StartPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                 goto CleanUp;
             }

             if (BeSmart || dwPageNumber <= dwTotalNumberOfPages) {
                if (!PlayEMFPage(hSpoolHandle,
                                  hPrinterDC,
                                  hEMF,
                                  1,
                                  dwPageNumber,
                                  1,
                                  dwNupBorderFlags,
                                  EMF_DEGREE_90)) {

                     ODS(("PlayEMFPage failed\n"));
                     goto CleanUp;
                 }
             }

             if (!GdiEndPageEMF(hSpoolHandle, dwOptimization)) {
                 ODS(("EndPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                 goto CleanUp;
             }
       }
    }

    bReturn = TRUE;

CleanUp:

    return bReturn;
}



BOOL
PrintReverseForDriverEMF(
    HANDLE     hSpoolHandle,
    HDC        hPrinterDC,
    DWORD      dwDrvNumberOfPagesPerSide,
    DWORD      dwTotalNumberOfPages,
    DWORD      dwNupBorderFlags,
    DWORD      dwJobNumberOfCopies,
    DWORD      dwDrvNumberOfCopies,
    BOOL       bCollate,
    BOOL       bDuplex,
    BOOL       bOdd,
    DWORD      dwOptimization,
    LPDEVMODEW pDevmode,
    PPAGE_NUMBER pHead,
    PPRINTPROCESSORDATA pData)

/*++
Function Description: PrintReverseForDriverEMF plays the EMF pages in the reverse order
                      for the driver which does the Nup transformations.

Parameters: hSpoolHandle           -- handle the spool file handle
            hPrinterDC             -- handle to the printer device context
            dwDrvNumberOfPagesPerSide -- number of pages the driver will print per side
            dwTotalNumberOfPages   -- total number of pages in the document
            dwNupBorderFlags       -- border printing options for nup
            dwJobNumberOfCopies    -- number of copies of the job to be printed
            dwDrvNumberOfCopies    -- number of copies that the driver can print
            bCollate               -- flag for collating the copies
            bDuplex                -- flag to indicate duplex printing
            bOdd                   -- flag to indicate odd number of sides to print
            dwOptimization         -- optimization flags
            pDevmode               -- pointer to devmode for changing the copy count
            pHead                  -- pointer to a linked list containing the starting
                                       page numbers for each of the sides
            pData                  -- needed for status and the handle of the event: pause, resume etc.                                     

Return Values:  TRUE if successful
                FALSE otherwise
--*/

{
    DWORD         dwPageIndex,dwPageNumber,dwRemainingCopies;
    BOOL          bReturn = FALSE;

    // select the correct page for duplex printing
    if (bDuplex && !bOdd) {
       if (pHead) {
          pHead = pHead->pNext;
       } else {
          bReturn = TRUE;
          goto CleanUp;
       }
    }

    // play the sides in reverse order
    while (pHead) {
        //
        // If the print processor is paused, wait for it to be resumed
        //
        if (pData->fsStatus & PRINTPROCESSOR_PAUSED) {
            WaitForSingleObject(pData->semPaused, INFINITE);
        }
        
        // set the page number
        dwPageNumber = pHead->dwPageNumber;

        if (bCollate) {
       
           if (!PrintOneSideReverseForDriverEMF(hSpoolHandle,
                                                hPrinterDC,
                                                dwDrvNumberOfPagesPerSide,
                                                dwTotalNumberOfPages,
                                                dwNupBorderFlags,
                                                bDuplex,
                                                dwOptimization,
                                                dwPageNumber,
                                                pDevmode)) {
               goto CleanUp;
           }
           
        } else {

           dwRemainingCopies = dwJobNumberOfCopies;

           while (dwRemainingCopies) {

               if (dwRemainingCopies <= dwDrvNumberOfCopies) {
                  SetDrvCopies(hPrinterDC, pDevmode, dwRemainingCopies);
                  dwRemainingCopies = 0;
               } else {
                  SetDrvCopies(hPrinterDC, pDevmode, dwDrvNumberOfCopies);
                  dwRemainingCopies -= dwDrvNumberOfCopies;
               }

               if (!PrintOneSideReverseForDriverEMF(hSpoolHandle,
                                                    hPrinterDC,
                                                    dwDrvNumberOfPagesPerSide,
                                                    dwTotalNumberOfPages,
                                                    dwNupBorderFlags,
                                                    bDuplex,
                                                    dwOptimization,
                                                    dwPageNumber,
                                                    pDevmode)) {
                   goto CleanUp;
               }
           }
        }

        pHead = pHead->pNext;

        // go to the next page for duplex printing
        if (bDuplex && pHead) {
            pHead = pHead->pNext;
        }
    }

    bReturn = TRUE;

CleanUp:

    return bReturn;
}

BOOL
PrintOneSideReverseEMF(
    HANDLE       hSpoolHandle,
    HDC          hPrinterDC,
    DWORD        dwNumberOfPagesPerSide,
    DWORD        dwNupBorderFlags,
    BOOL         bDuplex,
    DWORD        dwOptimization,
    DWORD        dwStartPage1,
    DWORD        dwEndPage1,
    DWORD        dwStartPage2,
    DWORD        dwEndPage2,
    LPDEVMODE    pDevmode)

/*++
Function Description: PrintOneSideReverseEMF plays the EMF pages for the next physical page.

Parameters: hSpoolHandle           -- handle the spool file handle
            hPrinterDC             -- handle to the printer device context
            dwNumberOfPagesPerSide -- number of pages to be printed per side by the print
                                       processor
            dwNupBorderFlags       -- border printing options for nup
            bDuplex                -- flag to indicate duplex printing
            dwOptimization         -- optimization flags
            dwStartPage1           -- page number of the first EMF page on 1st side
            dwEndPage1             -- page number of the last EMF page on 1st side
            dwStartPage2           -- page number of the first EMF page on 2nd side
            dwEndPage2             -- page number of the last EMF page on 2nd side
            pDevmode               -- devmode with resolution settings

Return Values:  TRUE if successful
                FALSE otherwise
--*/
{
    DWORD         dwPageNumber, dwPageIndex, dwPageType;
    BOOL          bReturn = FALSE, bNewDevmode;
    LPDEVMODEW    pLastDM,pCurrDM;
    HANDLE        hEMF = NULL;
    DWORD         dwEndPage, dwStartPage, dwSides;

    for (dwSides = bDuplex ? 2 : 1; 
         dwSides; 
         --dwSides) {

         if (bDuplex && (dwSides == 1)) {
             dwStartPage = dwStartPage2;
             dwEndPage = dwEndPage2;
         } else {
             dwStartPage = dwStartPage1;
             dwEndPage = dwEndPage1;
         }

         for (dwPageNumber = dwStartPage, dwPageIndex = 1;
              dwPageNumber <= dwEndPage;
              ++dwPageNumber, ++dwPageIndex) {

               if (!(hEMF = GdiGetPageHandle(hSpoolHandle,
                                             dwPageNumber,
                                             &dwPageType))) {

                    ODS(("GdiGetPageHandle failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                    goto CleanUp;                      
               }

               if (dwPageIndex == 1) {
                   
                   // Process devmodes in the spool file and call StartPage
                   if (!ResetDCForNewDevmode(hSpoolHandle,
                                             hPrinterDC,
                                             dwPageNumber,
                                             FALSE,
                                             dwOptimization,
                                             &bNewDevmode,
                                             pDevmode) ||

                       !GdiStartPageEMF(hSpoolHandle)) {
                        
                       goto CleanUp;
                   }
               }

               if (!PlayEMFPage(hSpoolHandle,
                                hPrinterDC,
                                hEMF,
                                dwNumberOfPagesPerSide,
                                dwPageNumber,
                                dwPageIndex,
                                dwNupBorderFlags,
                                EMF_DEGREE_90)) {

                   ODS(("PlayEMFPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                   goto CleanUp;
               }
         }

         if ((dwPageIndex == 1) && !GdiStartPageEMF(hSpoolHandle)) {
              ODS(("StartPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
              goto CleanUp;
         }

         if (!GdiEndPageEMF(hSpoolHandle, dwOptimization)) {
             ODS(("EndPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
             goto CleanUp;
         }
    }

    bReturn = TRUE;

CleanUp:

    return bReturn;
}

BOOL
PrintReverseEMF(
    HANDLE       hSpoolHandle,
    HDC          hPrinterDC,
    DWORD        dwTotalNumberOfPages,
    DWORD        dwNumberOfPagesPerSide,
    DWORD        dwNupBorderFlags,
    DWORD        dwJobNumberOfCopies,
    DWORD        dwDrvNumberOfCopies,
    BOOL         bCollate,
    BOOL         bDuplex,
    BOOL         bOdd,
    DWORD        dwOptimization,
    LPDEVMODEW   pDevmode,
    PPAGE_NUMBER pHead,
    PPRINTPROCESSORDATA pData)

/*++
Function Description: PrintReverseEMF plays the EMF pages in the reverse order and also
                      performs nup transformations.

Parameters: hSpoolHandle           -- handle the spool file handle
            hPrinterDC             -- handle to the printer device context
            dwTotalNumberOfPages   -- number of pages in the document
            dwNumberOfPagesPerSide -- number of pages to be printed per side by the print
                                       processor
            dwNupBorderFlags       -- border printing options for nup
            dwJobNumberOfCopies    -- number of copies of the job to be printed
            dwDrvNumberOfCopies    -- number of copies that the driver can print
            bCollate               -- flag for collating the copies
            bDuplex                -- flag to indicate duplex printing
            bOdd                   -- flag to indicate odd number of sides to print
            dwOptimization         -- optimization flags
            pDevmode               -- pointer to devmode for changing the copy count
            pHead                  -- pointer to a linked list containing the starting
                                       page numbers for each of the sides
            pData                  -- needed for status and the handle of the event: pause, resume etc.                                       

Return Values:  TRUE if successful
                FALSE otherwise
--*/

{
    DWORD         dwPageNumber,dwPageIndex,dwRemainingCopies;
    DWORD         dwStartPage1,dwStartPage2,dwEndPage1,dwEndPage2;
    BOOL          bReturn = FALSE;

    if (!pHead) {
        bReturn = TRUE;
        goto CleanUp;
    }

    // set the start and end page numbers for duplex and regular printing
    if (bDuplex) {
       if (bOdd) {
           dwStartPage1 = pHead->dwPageNumber;
           dwEndPage1   = dwTotalNumberOfPages;
           dwStartPage2 = dwTotalNumberOfPages+1;
           dwEndPage2   = 0;
       } else {
           dwStartPage2 = pHead->dwPageNumber;
           dwEndPage2   = dwTotalNumberOfPages;

           if (pHead = pHead->pNext) {
               dwStartPage1 = pHead->dwPageNumber;
               dwEndPage1   = dwStartPage2 - 1;
           }
       }
    } else {
       dwStartPage1 = pHead->dwPageNumber;
       dwEndPage1   = dwTotalNumberOfPages;
       dwStartPage2 = 0;
       dwEndPage2   = 0;
    }

    while (pHead) {
       //
       // If the print processor is paused, wait for it to be resumed 
       //
       if (pData->fsStatus & PRINTPROCESSOR_PAUSED) {
              WaitForSingleObject(pData->semPaused, INFINITE);
       }
         
       if (bCollate) {

          if (!PrintOneSideReverseEMF(hSpoolHandle,
                                      hPrinterDC,
                                      dwNumberOfPagesPerSide,
                                      dwNupBorderFlags,
                                      bDuplex,
                                      dwOptimization,
                                      dwStartPage1,
                                      dwEndPage1,
                                      dwStartPage2,
                                      dwEndPage2,
                                      pDevmode)) {

              goto CleanUp;
          }

       } else {

          dwRemainingCopies = dwJobNumberOfCopies;

          while (dwRemainingCopies) {

              if (dwRemainingCopies <= dwDrvNumberOfCopies) {
                 SetDrvCopies(hPrinterDC, pDevmode, dwRemainingCopies);
                 dwRemainingCopies = 0;
              } else {
                 SetDrvCopies(hPrinterDC, pDevmode, dwDrvNumberOfCopies);
                 dwRemainingCopies -= dwDrvNumberOfCopies;
              }

              if (!PrintOneSideReverseEMF(hSpoolHandle,
                                          hPrinterDC,
                                          dwNumberOfPagesPerSide,
                                          dwNupBorderFlags,
                                          bDuplex,
                                          dwOptimization,
                                          dwStartPage1,
                                          dwEndPage1,
                                          dwStartPage2,
                                          dwEndPage2,
                                          pDevmode)) {

                  goto CleanUp;
              }
          }
       }

       if (bDuplex) {
          if (pHead->pNext && pHead->pNext->pNext) {
              dwEndPage2 = pHead->dwPageNumber - 1;
              pHead = pHead->pNext;
              dwStartPage2 = pHead->dwPageNumber;
              dwEndPage1 = dwStartPage2 - 1;
              pHead = pHead->pNext;
              dwStartPage1 = pHead->dwPageNumber;
          } else {
              break;
          }
       } else {
          pHead = pHead->pNext;
          if (pHead) {
              dwEndPage1 = dwStartPage1 - 1;
              dwStartPage1 = pHead->dwPageNumber;
          }
       }

    }

    bReturn = TRUE;

CleanUp:

    return bReturn;
}

BOOL
PrintOneSideBookletEMF(
    HANDLE       hSpoolHandle,
    HDC          hPrinterDC,
    DWORD        dwNumberOfPagesPerSide,
    DWORD        dwNupBorderFlags,
    DWORD        dwTotalNumberOfPages,
    DWORD        dwTotalPrintPages,
    DWORD        dwStartPage,
    BOOL         bReverseOrderPrinting,
    DWORD        dwOptimization,
    DWORD        dwDuplexMode,
    LPDEVMODE    pDevmode)

/*++
Function Description: PrintOneSideBookletEMF prints one page of the booklet job.

Parameters: hSpoolHandle           -- handle the spool file handle
            hPrinterDC             -- handle to the printer device context
            dwNumberOfPagesPerSide -- number of pages to be printed per side by the print
                                       processor
            dwNupBorderFlags       -- border printing options for nup
            dwTotalNumberOfPages   -- number of pages in the document
            dwTotalPrintPages      -- number of pages to printed (multiple of 4)
            dwStartPage            -- number of the starting page for the side
            bReverseOrderPrinting  -- flag for reverse order printing
            dwOptimization         -- optimization flags
            dwDuplexMode           -- duplex printing mode (none|horz|vert)
            pDevmode               -- devmode with resolution settings

Return Values:  TRUE if successful
                FALSE otherwise
--*/

{
    DWORD       dwPageArray[4];
    DWORD       dwPagesPrinted = 0, dwPageIndex, dwAngle, dwPageType, dwLastPage;
    HANDLE      hEMF = NULL;
    LPDEVMODEW  pLastDM, pCurrDM;
    BOOL        bReturn = FALSE ,bNewDevmode;

    // set the order of the pages
    if (bReverseOrderPrinting) {
        dwPageArray[0] = dwStartPage + 1;
        dwPageArray[1] = dwTotalPrintPages - dwStartPage;
        if (dwDuplexMode == EMF_DUP_VERT) {
           dwPageArray[2] = dwStartPage;
           dwPageArray[3] = dwPageArray[1] + 1;
        } else { // EMF_DUP_HORZ
           dwPageArray[3] = dwStartPage;
           dwPageArray[2] = dwPageArray[1] + 1;
        }
    } else {
        dwPageArray[1] = dwStartPage;
        dwPageArray[0] = dwTotalPrintPages - dwStartPage + 1;
        if (dwDuplexMode == EMF_DUP_VERT) {
           dwPageArray[2] = dwPageArray[0] - 1;
           dwPageArray[3] = dwPageArray[1] + 1;
        } else { // EMF_DUP_HORZ
           dwPageArray[2] = dwPageArray[1] + 1;
           dwPageArray[3] = dwPageArray[0] - 1;
        }
    }

    // Set page number for ResetDC
    dwLastPage = (dwTotalNumberOfPages < dwPageArray[0]) ? dwTotalNumberOfPages
                                                         : dwPageArray[0];

    // Process devmodes in the spool file
    if (!ResetDCForNewDevmode(hSpoolHandle,
                              hPrinterDC,
                              dwLastPage,
                              FALSE,
                              dwOptimization,
                              &bNewDevmode,
                              pDevmode)) {
        goto CleanUp;
    }

    while (dwPagesPrinted < 4) {
       for (dwPageIndex = 1;
            dwPageIndex <= dwNumberOfPagesPerSide;
            ++dwPageIndex, ++dwPagesPrinted) {

            if (dwPageArray[dwPagesPrinted] <= dwTotalNumberOfPages) {

                if (!(hEMF = GdiGetPageHandle(hSpoolHandle,
                                              dwPageArray[dwPagesPrinted],
                                              &dwPageType))) {
                     ODS(("GdiGetPageHandle failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                     goto CleanUp;
                }
            }

            if (dwPageIndex == 1) {

               if (!GdiStartPageEMF(hSpoolHandle)) {
                   ODS(("StartPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                   goto CleanUp;
               }
            }

            if (dwPageArray[dwPagesPrinted] <= dwTotalNumberOfPages) {

                 if ((dwDuplexMode == EMF_DUP_VERT) &&
                     (dwPagesPrinted > 1)) {
                      dwAngle = EMF_DEGREE_270;
                 } else { // EMF_DUP_HORZ or 1st side
                      dwAngle = EMF_DEGREE_90;
                 }
  
                 if (!PlayEMFPage(hSpoolHandle,
                                  hPrinterDC,
                                  hEMF,
                                  dwNumberOfPagesPerSide,
                                  dwPageArray[dwPagesPrinted],
                                  dwPageIndex,
                                  dwNupBorderFlags,
                                  dwAngle)) {

                     ODS(("PlayEMFPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                     goto CleanUp;
                 }
            }

            if (dwPageIndex == dwNumberOfPagesPerSide) {

                if (!GdiEndPageEMF(hSpoolHandle, dwOptimization)) {
                     ODS(("EndPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                     goto CleanUp;
                }
            }
       }
    }

    bReturn = TRUE;

CleanUp:

    return bReturn;
}


BOOL
PrintBookletEMF(
    HANDLE       hSpoolHandle,
    HDC          hPrinterDC,
    DWORD        dwNumberOfPagesPerSide,
    DWORD        dwTotalNumberOfPages,
    DWORD        dwNupBorderFlags,
    DWORD        dwJobNumberOfCopies,
    DWORD        dwDrvNumberOfCopies,
    BOOL         bReverseOrderPrinting,
    BOOL         bCollate,
    DWORD        dwOptimization,
    DWORD        dwDuplexMode,
    LPDEVMODEW   pDevmode,
    PPRINTPROCESSORDATA pData)

/*++
Function Description: PrintBookletEMF prints the job in 2-up in booklet form.

Parameters: hSpoolHandle           -- handle the spool file handle
            hPrinterDC             -- handle to the printer device context
            dwNumberOfPagesPerSide -- number of pages to be printed per side by the print
                                       processor
            dwTotalNumberOfPages   -- number of pages in the document
            dwNupBorderFlags       -- border printing options for nup
            dwJobNumberOfCopies    -- number of copies of the job to be printed
            dwDrvNumberOfCopies    -- number of copies that the driver can print
            bReverseOrderPrinting  -- flag for reverse order printing
            bCollate               -- flag for collating the copies
            dwOptimization         -- optimization flags
            dwDuplexMode           -- duplex printing mode (none|horz|vert)
            pDevmode               -- pointer to devmode for changing the copy count
            pData                  -- needed for status and the handle of the event: pause, resume etc.

Return Values:  TRUE if successful
                FALSE otherwise
--*/

{
    BOOL              bReturn = FALSE;
    DWORD             dwTotalPrintPages, dwNumberOfPhyPages, dwRemainingCopies, dwIndex;

    // Get closest multiple of 4 greater than dwTotalNumberOfPages
    dwTotalPrintPages = dwTotalNumberOfPages - (dwTotalNumberOfPages % 4);
    if (dwTotalPrintPages != dwTotalNumberOfPages) {
        dwTotalPrintPages += 4;
    }
    dwNumberOfPhyPages = (DWORD) dwTotalPrintPages / 4;

    for (dwIndex = 0; dwIndex < dwNumberOfPhyPages; ++dwIndex) {
         //
         // If the print processor is paused, wait for it to be resumed 
         //
         if (pData->fsStatus & PRINTPROCESSOR_PAUSED) {
                WaitForSingleObject(pData->semPaused, INFINITE);
         }

         if (bCollate) {

            if (!PrintOneSideBookletEMF(hSpoolHandle,
                                        hPrinterDC,
                                        dwNumberOfPagesPerSide,
                                        dwNupBorderFlags,
                                        dwTotalNumberOfPages,
                                        dwTotalPrintPages,
                                        dwIndex * 2 + 1,
                                        bReverseOrderPrinting,
                                        dwOptimization,
                                        dwDuplexMode,
                                        pDevmode)) {
                 goto CleanUp;
            }

         } else {

            dwRemainingCopies = dwJobNumberOfCopies;

            while (dwRemainingCopies) {

                if (dwRemainingCopies <= dwDrvNumberOfCopies) {
                   SetDrvCopies(hPrinterDC, pDevmode, dwRemainingCopies);
                   dwRemainingCopies = 0;
                } else {
                   SetDrvCopies(hPrinterDC, pDevmode, dwDrvNumberOfCopies);
                   dwRemainingCopies -= dwDrvNumberOfCopies;
                }

                if (!PrintOneSideBookletEMF(hSpoolHandle,
                                            hPrinterDC,
                                            dwNumberOfPagesPerSide,
                                            dwNupBorderFlags,
                                            dwTotalNumberOfPages,
                                            dwTotalPrintPages,
                                            dwIndex * 2 + 1,
                                            bReverseOrderPrinting,
                                            dwOptimization,
                                            dwDuplexMode,
                                            pDevmode)) {
                     goto CleanUp;
                }
            }
         }
    }

    bReturn = TRUE;

CleanUp:

    return bReturn;
}

BOOL
PrintEMFSingleCopy(
    HANDLE       hSpoolHandle,
    HDC          hPrinterDC,
    BOOL         bReverseOrderPrinting,
    DWORD        dwDrvNumberOfPagesPerSide,
    DWORD        dwNumberOfPagesPerSide,
    DWORD        dwTotalNumberOfPages,
    DWORD        dwNupBorderFlags,
    DWORD        dwJobNumberOfCopies,
    DWORD        dwDrvNumberOfCopies,
    BOOL         bCollate,
    BOOL         bOdd,
    BOOL         bBookletPrint,
    DWORD        dwOptimization,
    DWORD        dwDuplexMode,
    LPDEVMODEW   pDevmode,
    PPAGE_NUMBER pHead,
    PPRINTPROCESSORDATA pData)

/*++
Function Description: PrintEMFSingleCopy plays one copy of the job on hPrinterDC.

Parameters: hSpoolHandle           -- handle the spool file handle
            hPrinterDC             -- handle to the printer device context
            bReverseOrderPrinting  -- flag for reverse order printing
            dwDrvNumberOfPagesPerSide -- number of pages the driver will print per side
            dwNumberOfPagesPerSide -- number of pages to be printed per side by the print
                                       processor
            dwTotalNumberOfPages   -- number of pages in the document
            dwNupBorderFlags       -- border printing options for nup
            dwJobNumberOfCopies    -- number of copies of the job to be printed
            dwDrvNumberOfCopies    -- number of copies that the driver can print
            bCollate               -- flag for collating the copies
            bOdd                   -- flag to indicate odd number of sides to print
            bBookletPrint          -- flag for booklet printing
            dwOptimization         -- optimization flags
            dwDuplexMode           -- duplex printing mode (none|horz|vert)
            pDevmode               -- pointer to devmode for changing the copy count
            pHead                  -- pointer to a linked list containing the starting
                                       page numbers for each of the sides
            pData                  -- needed for status and the handle of the event: pause, resume etc.                             

Return Values:  TRUE if successful
                FALSE otherwise
--*/

{
    BOOL  bDuplex = (dwDuplexMode != EMF_DUP_NONE);

    if (bBookletPrint) {

       // Booklet Printing
       return PrintBookletEMF(hSpoolHandle,
                              hPrinterDC,
                              dwNumberOfPagesPerSide,
                              dwTotalNumberOfPages,
                              dwNupBorderFlags,
                              dwJobNumberOfCopies,
                              dwDrvNumberOfCopies,
                              bReverseOrderPrinting,
                              bCollate,
                              dwOptimization,
                              dwDuplexMode,
                              pDevmode,
                              pData);
    }

    if (bReverseOrderPrinting) {
       if (dwDrvNumberOfPagesPerSide != 1 || dwNumberOfPagesPerSide == 1) {

          return PrintReverseForDriverEMF(hSpoolHandle,
                                          hPrinterDC,
                                          dwDrvNumberOfPagesPerSide,
                                          dwTotalNumberOfPages,
                                          dwNupBorderFlags,
                                          dwJobNumberOfCopies,
                                          dwDrvNumberOfCopies,
                                          bCollate,
                                          bDuplex,
                                          bOdd,
                                          dwOptimization,
                                          pDevmode,
                                          pHead,
                                          pData);
       } else {

          // Reverse printing and nup
          return PrintReverseEMF(hSpoolHandle,
                                 hPrinterDC,
                                 dwTotalNumberOfPages,
                                 dwNumberOfPagesPerSide,
                                 dwNupBorderFlags,
                                 dwJobNumberOfCopies,
                                 dwDrvNumberOfCopies,
                                 bCollate,
                                 bDuplex,
                                 bOdd,
                                 dwOptimization,
                                 pDevmode,
                                 pHead,
                                 pData);
       }

    } else {

       // Normal printing
       return PrintForwardEMF(hSpoolHandle,
                              hPrinterDC,
                              dwNumberOfPagesPerSide,
                              dwDrvNumberOfPagesPerSide,
                              dwNupBorderFlags,
                              dwJobNumberOfCopies,
                              dwDrvNumberOfCopies,
                              bCollate,
                              bDuplex,
                              dwOptimization,
                              pDevmode,
                              pData);
    }
}

BOOL
GetStartPageList(
    HANDLE       hSpoolHandle,
    PPAGE_NUMBER *pHead,
    DWORD        dwTotalNumberOfPages,
    DWORD        dwNumberOfPagesPerSide,
    LPBOOL       pbOdd)

/*++
Function Description: GetStartPageList generates a list of the page numbers which
                      should appear on the start of each side of the job. This takes
                      into consideration the ResetDC calls that may appear before the
                      end of the page. The list generated by GetStartPageList is used
                      to play the job in reverse order.

Parameters: hSpoolHandle           -- handle the spool file handle
            pHead                  -- pointer to a pointer to a linked list containing the
                                       starting page numbers for each of the sides
            dwTotalNumberOfPages   -- number of pages in the document
            dwNumberOfPagesPerSide -- number of pages to be printed per side by the print
                                       processor
            pbOdd                  -- pointer to flag indicating odd number of pages to
                                       print

Return Values:  TRUE if successful
                FALSE otherwise
--*/

{

    DWORD        dwPageIndex,dwPageNumber=1,dwPageType;
    LPDEVMODEW   pCurrDM, pLastDM;
    PPAGE_NUMBER pTemp=NULL;
    BOOL         bReturn = FALSE;
    BOOL         bCheckDevmode;

    bCheckDevmode = (dwNumberOfPagesPerSide != 1);

    while (dwPageNumber <= dwTotalNumberOfPages) {

       for (dwPageIndex = 1;
            (dwPageIndex <= dwNumberOfPagesPerSide) && (dwPageNumber <= dwTotalNumberOfPages);
            ++dwPageIndex, ++dwPageNumber) {

          if (bCheckDevmode) {

             // Check if the devmode has changed requiring a new page
             if (!GdiGetDevmodeForPage(hSpoolHandle, dwPageNumber,
                                               &pCurrDM, NULL)) {
                 ODS(("Get devmodes failed\n"));
                 goto CleanUp;
             }

             if (dwPageIndex == 1) {
                 // Save the Devmode for the first page on a side
                 pLastDM = pCurrDM;

             } else {
                 // If the Devmode changes in a side, start a new page
                 if (DifferentDevmodes(pCurrDM, pLastDM)) {

                     dwPageIndex = 1;
                     pLastDM = pCurrDM;
                 }
             }
          }

          // Create a node for the start of a side
          if (dwPageIndex == 1) {

              if (!(pTemp = AllocSplMem(sizeof(PAGE_NUMBER)))) {
                  ODS(("GetStartPageList - Run out of memory"));
                  goto CleanUp;
              }
              pTemp->pNext = *pHead;
              pTemp->dwPageNumber = dwPageNumber;
              *pHead = pTemp;

              // flip the bOdd flag
              *pbOdd = !*pbOdd;
          }
       }
    }

    bReturn = TRUE;

CleanUp:

    // Free up the memory in case of a failure.
    if (!bReturn) {
       while (pTemp = *pHead) {
          *pHead = (*pHead)->pNext;
          FreeSplMem(pTemp);
       }
    }
    return bReturn;
}


BOOL
CopyDevmode(
    PPRINTPROCESSORDATA pData,
    LPDEVMODEW *pDevmode)

/*++
Function Description: Copies the devmode in pData or the default devmode into pDevmode.

Parameters:   pData           - Data structure for the print job
              pDevmode        - pointer to devmode

Return Value:  TRUE  if successful
               FALSE otherwise
--*/

{
    HANDLE           hDrvPrinter = NULL;
    BOOL             bReturn = FALSE;
    fnWinSpoolDrv    fnList;
    LONG             lNeeded;

    if (pData->pDevmode) {

        lNeeded = pData->pDevmode->dmSize +  pData->pDevmode->dmDriverExtra;

        if (*pDevmode = (LPDEVMODEW) AllocSplMem(lNeeded)) {
            memcpy(*pDevmode, pData->pDevmode, lNeeded);
        } else {
            goto CleanUp;
        }

    } else {
        // Get the default devmode

        // Get the pointer to the client side functions from the router
        if (!SplInitializeWinSpoolDrv(&fnList)) {
            goto CleanUp;
        }

        // Get a client side printer handle to pass to the driver
        if (!(* (fnList.pfnOpenPrinter))(pData->pPrinterName, &hDrvPrinter, NULL)) {
            ODS(("Open printer failed\nPrinter %ws\n", pData->pPrinterName));
            goto CleanUp;
        }

        lNeeded = (* (fnList.pfnDocumentProperties))(NULL,
                                                     hDrvPrinter,
                                                     pData->pPrinterName,
                                                     NULL,
                                                     NULL,
                                                     0);

        if (lNeeded <= 0  ||
            !(*pDevmode = (LPDEVMODEW) AllocSplMem(lNeeded)) ||
            (* (fnList.pfnDocumentProperties))(NULL,
                                               hDrvPrinter,
                                               pData->pPrinterName,
                                               *pDevmode,
                                               NULL,
                                               DM_OUT_BUFFER) < 0) {

             if (*pDevmode) {
                FreeSplMem(*pDevmode);
                *pDevmode = NULL;
             }

             ODS(("DocumentProperties failed\nPrinter %ws\n",pData->pPrinterName));
             goto CleanUp;
        }
    }

    bReturn = TRUE;

CleanUp:

    if (hDrvPrinter) {
        (* (fnList.pfnClosePrinter))(hDrvPrinter);
    }

    return bReturn;
}

BOOL
PrintEMFJob(
    PPRINTPROCESSORDATA pData,
    LPWSTR pDocumentName)

/*++
Function Description: Prints out a job with EMF data type.

Parameters:   pData           - Data structure for this job
              pDocumentName   - Name of this document

Return Value:  TRUE  if successful
               FALSE if failed - GetLastError() will return reason.
--*/

{
    HANDLE             hSpoolHandle = NULL;
    HDC                hPrinterDC = NULL;

    BOOL               bReverseOrderPrinting, bReturn = FALSE, bSetWorldXform = TRUE;
    BOOL               bCollate, bDuplex, bBookletPrint, bStartDoc = FALSE, bOdd = FALSE;

    DWORD              dwNumberOfPagesPerSide, dwTotalNumberOfPages = 0, dwNupBorderFlags;
    DWORD              dwJobNumberOfPagesPerSide, dwDrvNumberOfPagesPerSide, dwDuplexMode;
    DWORD              dwJobNumberOfCopies, dwDrvNumberOfCopies,dwRemainingCopies;
    DWORD              dwJobOrder, dwDrvOrder, dwOptimization;

    DOCINFOW           DocInfo;
    XFORM              OldXForm;
    PPAGE_NUMBER       pHead = NULL,pTemp;
    ATTRIBUTE_INFO_3   AttributeInfo;
    LPDEVMODEW         pDevmode = NULL, pFirstDM = NULL, pCopyDM;

    
    // Copy the devmode into pDevMode
    if (!CopyDevmode(pData, &pDevmode)) {
        
        ODS(("CopyDevmode failed\nPrinter %ws\nDocument %ws\nJobID %u\n", pData->pDevmode->dmDeviceName, pData->pDocument, pData->JobId));
        goto CleanUp;
    }

    // Update resolution before CreateDC for monochrome optimization
    if (!GetJobAttributes(pData->pPrinterName,
                          pDevmode,
                          &AttributeInfo)) {
        ODS(("GetJobAttributes failed\nPrinter %ws\nDocument %ws\nJobID %u\n", pData->pDevmode->dmDeviceName, pData->pDocument, pData->JobId));
        goto CleanUp;
    } else {
        if (AttributeInfo.dwColorOptimization) {
            pDevmode->dmPrintQuality =  AttributeInfo.dmPrintQuality;
            pDevmode->dmYResolution =  AttributeInfo.dmYResolution;
        }
    }

    // Get spool file handle and printer device context from GDI
    try {

        hSpoolHandle = GdiGetSpoolFileHandle(pData->pPrinterName,
                                             pDevmode,
                                             pDocumentName);
        if (hSpoolHandle) {
            hPrinterDC = GdiGetDC(hSpoolHandle);
        }

    } except (EXCEPTION_EXECUTE_HANDLER) {

        ODS(("PrintEMFJob gave an exceptionPrinter %ws\nDocument %ws\nJobID %u\n", pData->pDevmode->dmDeviceName, pData->pDocument, pData->JobId));
        goto CleanUp;
    }

    if (!hPrinterDC || !hSpoolHandle) {
        goto CleanUp;
    }

    // Use the first devmode in the spool file to update the copy count
    // and the collate setting
    if (GdiGetDevmodeForPage(hSpoolHandle, 1, &pFirstDM, NULL) &&
        pFirstDM) {
        
        if (pFirstDM->dmFields & DM_COPIES) {
            pDevmode->dmFields |= DM_COPIES;
            pDevmode->dmCopies = pFirstDM->dmCopies;
        }
        if (pFirstDM->dmFields & DM_COLLATE) {
            pDevmode->dmFields |= DM_COLLATE;
            pDevmode->dmCollate = pFirstDM->dmCollate;
        }
    }

    // The number of copies of the print job is the product of the number of copies set
    // from the driver UI (present in the devmode) and the number of copies in pData struct
    dwJobNumberOfCopies = (pDevmode->dmFields & DM_COPIES) ? pData->Copies*pDevmode->dmCopies
                                                           : pData->Copies;
    pDevmode->dmCopies = (short) dwJobNumberOfCopies;
    pDevmode->dmFields |=  DM_COPIES;

    // Get the job attributes
    if (!GetJobAttributes(pData->pPrinterName,
                          pDevmode,
                          &AttributeInfo)) {
        ODS(("GetJobAttributes failed\nPrinter %ws\nDocument %ws\nJobID %u\n", pData->pDevmode->dmDeviceName, pData->pDocument, pData->JobId));
        goto CleanUp;
    }

    // Initialize bReverseOrderPrinting, dwJobNumberOfPagesPerSide,
    // dwDrvNumberOfPagesPerSide, dwNupBorderFlags, dwJobNumberOfCopies,
    // dwDrvNumberOfCopies and bCollate

    dwJobNumberOfPagesPerSide = AttributeInfo.dwJobNumberOfPagesPerSide;
    dwDrvNumberOfPagesPerSide = AttributeInfo.dwDrvNumberOfPagesPerSide;
    dwNupBorderFlags          = AttributeInfo.dwNupBorderFlags;
    dwJobNumberOfCopies       = AttributeInfo.dwJobNumberOfCopies;
    dwDrvNumberOfCopies       = AttributeInfo.dwDrvNumberOfCopies;

    dwJobOrder                = AttributeInfo.dwJobPageOrderFlags & ( NORMAL_PRINT | REVERSE_PRINT);
    dwDrvOrder                = AttributeInfo.dwDrvPageOrderFlags & ( NORMAL_PRINT | REVERSE_PRINT);
    bReverseOrderPrinting     = (dwJobOrder != dwDrvOrder);

    dwJobOrder                = AttributeInfo.dwJobPageOrderFlags & BOOKLET_PRINT;
    dwDrvOrder                = AttributeInfo.dwDrvPageOrderFlags & BOOKLET_PRINT;
    bBookletPrint             = (dwJobOrder != dwDrvOrder);

    bCollate                  = (pDevmode->dmFields & DM_COLLATE) &&
                                  (pDevmode->dmCollate == DMCOLLATE_TRUE);

    bDuplex                   = (pDevmode->dmFields & DM_DUPLEX) &&
                                  (pDevmode->dmDuplex != DMDUP_SIMPLEX);

    //
    // Color optimization may cause wrong output with duplex
    //
    dwOptimization            = (AttributeInfo.dwColorOptimization == COLOR_OPTIMIZATION && !bDuplex)
                                           ? EMF_PP_COLOR_OPTIMIZATION
                                           : 0;
    

    if (!dwJobNumberOfCopies) {
        //
        // Some applications can set the copy count to 0.
        // In this case we exit.
        //
        bReturn = TRUE;
        goto CleanUp;
    }

    if (bDuplex) {
        dwDuplexMode = (pDevmode->dmDuplex == DMDUP_HORIZONTAL) ? EMF_DUP_HORZ
                                                                : EMF_DUP_VERT;
    } else {
        dwDuplexMode = EMF_DUP_NONE;
    }

    if (bBookletPrint) {
        if (!bDuplex) {
            // Not supported w/o duplex printing. Use default settings.
            bBookletPrint = FALSE;
            dwDrvNumberOfPagesPerSide = 1;
            dwJobNumberOfPagesPerSide = 1;
        } else {
            // Fixed settings for pages per side.
            dwDrvNumberOfPagesPerSide = 1;
            dwJobNumberOfPagesPerSide = 2;
        }
    }

    // Number of pages per side that the print processor has to play
    dwNumberOfPagesPerSide = (dwDrvNumberOfPagesPerSide == 1)
                                               ? dwJobNumberOfPagesPerSide
                                               : 1;

    if (dwNumberOfPagesPerSide == 1) {
        // if the print processor is not doing nup, don't draw borders
        dwNupBorderFlags = NO_BORDER_PRINT;
    }

    // Check for Valid Option for n-up printing
    if (!ValidNumberForNUp(dwNumberOfPagesPerSide)) {
        ODS(("Invalid N-up option\nPrinter %ws\nDocument %ws\nJobID %u\n", pData->pDevmode->dmDeviceName, pData->pDocument, pData->JobId));
        goto CleanUp;
    }

    if (bReverseOrderPrinting || bBookletPrint) {

       // Get the number of pages in the job. This call waits till the
       // last page is spooled.
       try {

           dwTotalNumberOfPages= GdiGetPageCount(hSpoolHandle);

       } except (EXCEPTION_EXECUTE_HANDLER) {

           ODS(("PrintEMFJob gave an exceptionPrinter %ws\nDocument %ws\nJobID %u\n", pData->pDevmode->dmDeviceName, pData->pDocument, pData->JobId));
           goto CleanUp;
       }

       // Get start page list for reverse printing
       if (!GetStartPageList(hSpoolHandle,
                             &pHead,
                             dwTotalNumberOfPages,
                             dwJobNumberOfPagesPerSide,
                             &bOdd)) {
            goto CleanUp;
       }
    }

    // Save the old transformation on hPrinterDC
    if (!SetGraphicsMode(hPrinterDC,GM_ADVANCED) ||
        !GetWorldTransform(hPrinterDC,&OldXForm)) {

         bSetWorldXform = FALSE;
         ODS(("Transformation matrix can't be set\nPrinter %ws\nDocument %ws\nJobID %u\n", pData->pDevmode->dmDeviceName, pData->pDocument, pData->JobId));
         goto CleanUp;
    }

    // pCopyDM will be used for changing the copy count
    pCopyDM = pFirstDM ? pFirstDM : pDevmode;
    pCopyDM->dmPrintQuality = pDevmode->dmPrintQuality;
    pCopyDM->dmYResolution = pDevmode->dmYResolution;

    try {

        DocInfo.cbSize = sizeof(DOCINFOW);
        DocInfo.lpszDocName = pDocumentName;
        DocInfo.lpszOutput   = pData->pOutputFile;
        DocInfo.lpszDatatype = NULL;

        if (!GdiStartDocEMF(hSpoolHandle, &DocInfo)) goto CleanUp;
        bStartDoc = TRUE;

        if (bCollate) {

            dwRemainingCopies = dwJobNumberOfCopies & 0x0000FFFF ;

            while (dwRemainingCopies) {

               if (dwRemainingCopies <= dwDrvNumberOfCopies) {
                  SetDrvCopies(hPrinterDC, pCopyDM, dwRemainingCopies);
                  dwRemainingCopies = 0;
               } else {
                  SetDrvCopies(hPrinterDC, pCopyDM, dwDrvNumberOfCopies);
                  dwRemainingCopies -= dwDrvNumberOfCopies;
               }

               if (!PrintEMFSingleCopy(hSpoolHandle,
                                       hPrinterDC,
                                       bReverseOrderPrinting,
                                       dwDrvNumberOfPagesPerSide,
                                       dwNumberOfPagesPerSide,
                                       dwTotalNumberOfPages,
                                       dwNupBorderFlags,
                                       dwJobNumberOfCopies,
                                       dwDrvNumberOfCopies,
                                       bCollate,
                                       bOdd,
                                       bBookletPrint,
                                       dwOptimization,
                                       dwDuplexMode,
                                       pCopyDM,
                                       pHead,
                                       pData)) {
                   goto CleanUp;
               }
            }

        } else {

           if (!PrintEMFSingleCopy(hSpoolHandle,
                                   hPrinterDC,
                                   bReverseOrderPrinting,
                                   dwDrvNumberOfPagesPerSide,
                                   dwNumberOfPagesPerSide,
                                   dwTotalNumberOfPages,
                                   dwNupBorderFlags,
                                   dwJobNumberOfCopies,
                                   dwDrvNumberOfCopies,
                                   bCollate,
                                   bOdd,
                                   bBookletPrint,
                                   dwOptimization,
                                   dwDuplexMode,
                                   pCopyDM,
                                   pHead,
                                   pData)) {

               goto CleanUp;
           }
        }

        bStartDoc = FALSE;
        if (!GdiEndDocEMF(hSpoolHandle)) goto CleanUp;

    } except (EXCEPTION_EXECUTE_HANDLER) {

        ODS(("PrintEMFSingleCopy gave an exception\nPrinter %ws\nDocument %ws\nJobID %u\n", pData->pDevmode->dmDeviceName, pData->pDocument, pData->JobId));
        goto CleanUp;
    }

    bReturn = TRUE;
    
CleanUp:

    if (bStartDoc) {
       GdiEndDocEMF(hSpoolHandle);
    }

    if (bSetWorldXform && hPrinterDC) {
       SetWorldTransform(hPrinterDC, &OldXForm);
    }

    while (pTemp = pHead) {
       pHead = pHead->pNext;
       FreeSplMem(pTemp);
    }

    if (pDevmode) {
       FreeSplMem(pDevmode);
    }

    try {
        if (hSpoolHandle) {
           GdiDeleteSpoolFileHandle(hSpoolHandle);
        }

    } except (EXCEPTION_EXECUTE_HANDLER) {

        ODS(("GdiDeleteSpoolFileHandle failed\nPrinter %ws\nDocument %ws\nJobID %u\n", pData->pDevmode->dmDeviceName, pData->pDocument, pData->JobId));
    }

    return bReturn;
}



