Sample Code

Windows Driver Samples/ GenPrint Print Processor Sample/ C++/ emf.cpp/

/*++

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


Abstract:

    Routines to facilitate printing of EMF jobs.

--*/

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

extern "C" {
#include <winppi.h>
}


#ifndef ASSERT

#include <assert.h>

#define ASSERT assert

#endif


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

#define EMF_DEGREE_90   0x0001
#define EMF_DEGREE_270  0x0002
#define EMF_DEGREE_SWAP 0x8000

// There are several features supported by new printers that cause additional
// pages to be added in the course of printing the job.
// Such a driver would not want the dmCollate field to be disabled for single page, multi copy jobs.
// To accomplish this, a driver should set this regkey by calling SetPrinterData API.
// Winprint in turn would honour this reg key and not disable the dmCollate field for single page, multi copy jobs

#define gszDriverKeepCollate  L"SinglePageKeepCollate"

//
// IS_DMSIZE_VALID returns TRUE if the size of the devmode is atleast as much as to
// be able to access field x in it without AV. It is assumed that the devmode is
// atleast the size pdm->dmSize
//
#define IS_DMSIZE_VALID(pdm,x)  ( ( (pdm)->dmSize >= (FIELD_OFFSET(DEVMODEW, x ) + sizeof((pdm)->x )))? TRUE:FALSE)


//
// function pointer to PrintOneSideReverseEMF. Used for booklet, reverse printing
//
typedef BOOL (*PFPRINTONESIDEREVERSEEMF)(HANDLE, \
                                         HDC,    \
                                         PEMF_ATTRIBUTE_INFO, \
                                         PPAGE_NUMBER, \
                                         BOOL, \
                                         DWORD, \
                                         LPDEVMODE);

//
// function pointer for forward printing. Used for drivers that do their own n-up for non-traditional nup
//
typedef DWORD (*PFPRINTONESIDEFORWARDEMF)(HANDLE, \
                                         HDC,    \
                                         PEMF_ATTRIBUTE_INFO, \
                                         BOOL, \
                                         DWORD, \
                                         DWORD, \
                                         LPBOOL, \
                                         LPDEVMODE);



#define MAX_NUP             16 //Max is 16-up
#define MAX_NUP_DIRECTIONS  4
#define MAX_NUP_OPTIONS     8  // MAX_NUP_DIRECTIONS * 2 (for Portrait, Landscape).
                               //the portrait and landscape used only for 2-up and 6-up.

const DWORD dwIndexIntoUpdateRect[MAX_NUP] = {           //npps=Number of Pages Per Side
                                            0xFFFFFFFF, //npps=1  //Nothing to do.
                                            0,          //npps=2
                                            0xFFFFFFFF, //npps=3  //Invalid Option
                                            1,          //npps=4
                                            0xFFFFFFFF, //npps=5  //Invalid Option
                                            2,          //npps=6
                                            0xFFFFFFFF, //npps=7  //Invalid Option
                                            0xFFFFFFFF, //npps=8  //Invalid Option
                                            3,          //npps=9
                                            0xFFFFFFFF, //npps=10  //Invalid Option
                                            0xFFFFFFFF, //npps=11  //Invalid Option
                                            0xFFFFFFFF, //npps=12  //Invalid Option
                                            0xFFFFFFFF, //npps=13  //Invalid Option
                                            0xFFFFFFFF, //npps=14  //Invalid Option
                                            0xFFFFFFFF, //npps=15  //Invalid Option
                                            4           //npps=16
                                        };



//
// Postscript based drivers prefer to handle n-up themselves. (i.e. they
// can do rotation, scaling etc. So the only thing Print Processor needs to
// do is to play back the pages in proper order.
// For example, if page order is down then right for 4-up, the pages
// should be played back in the order 1,3,2,4. PScript driver will place the pages
// as if the n-up direction is the traditional right then down direction. So
// it will place pages as follow
//    1   3
//    2   4
// thus achieving the down then right affect.
//
// Mostly the order of sending page is not dependent on portrait or landscape.
// But in 6 up, it is. See below
//        -------------
//        | 1 | 3 | 5 |
//        ============= Portrait for down then right in 6-up
//        | 2 | 4 | 6 | Pages played back in order 1,3,5,2,4,6
//        -------------
//
//        ---------
//        | 1 | 4 |
//        ---------
//        | 2 | 5 |   Landscape for down then right in 6-up
//        ---------  Pages played back in order 1,4,2,5,3,6
//        | 3 | 6 |
//        ---------

const DWORD gPageOrderPlayBackForDriver[][MAX_NUP_OPTIONS][MAX_NUP] =
    {
        { //2-up
            //Portrait
            { 1,2 },                                        // gPageOrderPlayBackForDriver[0][0]
            { 1,2 },                                        // [0][1]
            { 2,1 },                                        // [0][2]
            { 2,1 },                                        // [0][3]

            //Landscape
            { 1,2 },                                        // gPageOrderPlayBackForDriver[0][4]
            { 1,2 },                                        // [0][5]
            { 2,1 },                                        // [0][6]
            { 2,1 }                                         // [0][7]
        },

        { // 4up
            //Portrait
            { 1,2,3,4 },                                    // [1][0] //Right Then Down
            { 1,3,2,4 },                                    // [1][1] //Down Then Right
            { 2,1,4,3 },                                    // [1][2] //Left Then Down
            { 3,1,4,2 }                                     // [1][3] //Down Then Left
            //Landscape
            //Skipping entries since landscape portrait are same.
        },

        { // 6up
            //Portrait
            { 1,2,3,4,5,6 },                                // [2][0]
            { 1,3,5,2,4,6 },                                // [2][1]
            { 3,2,1,6,5,4 },                                // [2][2]
            { 5,3,1,6,4,2 },                                // [2][3]

            //Landscape
            { 1,2,3,4,5,6 },                                // [2][4]
            { 1,4,2,5,3,6 },                                // [2][5]
            { 2,1,4,3,6,5 },                                // [2][6]
            { 4,1,5,2,6,3 }                                 // [2][7]
        },

        { // 9up
            { 1,2,3,4,5,6,7,8,9 },                          // [3][0]
            { 1,4,7,2,5,8,3,6,9 },                          // [3][1]
            { 3,2,1,6,5,4,9,8,7 },                          // [3][2]
            { 7,4,1,8,5,2,9,6,3 }                           // [3][3]
        },

        { // 16up
            { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 },     // [4][0]
            { 1,5,9,13,2,6,10,14,3,7,11,15,4,8,12,16 },     // [4][1]
            { 4,3,2,1,8,7,6,5,12,11,10,9,16,15,14,13 },     // [4][2]
            { 13,9,5,1,14,10,6,2,15,11,7,3,16,12,8,4 }      // [4][3]
        }
    };

//
// Local function declaration
//
_Success_(return != FALSE)
BOOL GdiGetDevmodeForPagePvt(
    _In_            HANDLE              hSpoolHandle,
    _In_            DWORD               dwPageNumber,
    _Outptr_     PDEVMODEW           *ppCurrDM,
    _Outptr_opt_result_maybenull_ PDEVMODEW           *ppLastDM
  );

BOOL BIsDevmodeOfLeastAcceptableSize(
    _In_ PDEVMODE pdm) ;

_Success_(return)
BOOL
GetStartPageListForwardForDriver(
    _In_            PEMF_ATTRIBUTE_INFO    pEMFAttr,
    _In_            LPDEVMODEW             pDevmode,
    _Outptr_        PPAGE_NUMBER          *ppMemoryHead,
    _Outptr_        PPAGE_NUMBER          *ppPageListHead,
    _In_            DWORD                  dwSmallestPageOnSheet
    );


PDWORD
GetPlaybackPageOrderForDriverNup (
    _In_ DWORD                  dwNumberOfPagesPerSide,
    _In_ ENupDirection          eNupDirection,
    _In_ INT                    dmOrientation
    )
{
    DWORD dwNupDirection = (DWORD) eNupDirection;

    if ( DMORIENT_LANDSCAPE == dmOrientation &&
         ( 2                == dwNumberOfPagesPerSide ||
           6                == dwNumberOfPagesPerSide)
       )
    {
        dwNupDirection += MAX_NUP_DIRECTIONS;
    }

    return (PDWORD) gPageOrderPlayBackForDriver[dwIndexIntoUpdateRect[dwNumberOfPagesPerSide-1]][dwNupDirection];
}

BOOL
ValidNumberForNUp(
    _In_ 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));
}


//+------------------------------------------------------------------------------------------------------
//  Function:
//      CNupLayout::CNupLayout
//
//  Synopsis:
//      Calculate m_uRow/m_uColumn based on Nup pages per side and orientation
//
//-------------------------------------------------------------------------------------------------------

CNupLayout::CNupLayout(
    unsigned uPagePerSide,
    bool     fLandscape)
{
    m_uRow    = 1;
    m_uColumn = 1;

    // Switch can be extended or changed to add support for more Nup-cases
    switch (uPagePerSide)
    {
    case 1:
        break;

    case 2:
    case 4:
        m_uRow = 2;
        break;

    case 6:
    case 9:
        m_uRow = 3;
        break;

    case 16:
        m_uRow = 4;
        break;

    default: // Should Not Occur.
        ASSERT(FALSE);
        m_uRow = 0;
    }

    if (m_uRow >= 1)
    {
        if (fLandscape) // swap row and column for landscape mode
        {
            m_uRow = uPagePerSide / m_uRow;
        }

        m_uColumn = uPagePerSide / m_uRow;

        ASSERT((m_uColumn * m_uRow) == uPagePerSide);
    }
}


//+------------------------------------------------------------------------------------------------------
//  Function:
//      CNupLayout::GetPageCoordinate
//
//  Synopsis:
//      Calculate coordinate (x, y) for logical page uCurrentPage, under direction eNupDirection
//
//  Returns:
//      * pX in [0 .. m_uColumn -1]
//      * pY in [0 .. m_uRow -1]
//      true if succedded, false if invalid input
//-------------------------------------------------------------------------------------------------------

bool CNupLayout::GetPageCoordinate(
    _In_  unsigned       uCurrentPageNumber,
    _In_  ENupDirection  eNupDirection,
    _Out_ unsigned      *pX,
    _Out_ unsigned      *pY) const
{
    ASSERT( (pX != NULL) && (pY != NULL));
    ASSERT((m_uRow >= 1) && (m_uColumn >= 1));

    uCurrentPageNumber %= (m_uRow * m_uColumn);

    *pX = 0;
    *pY = 0;

    switch (eNupDirection)
    {
        // 0 1
        // 2 3
        // 4
    case kRightThenDown:
        *pX = uCurrentPageNumber % m_uColumn;
        *pY = uCurrentPageNumber / m_uColumn;
        break;

        // 0 3
        // 1 4
        // 2
    case kDownThenRight:
        *pX = uCurrentPageNumber / m_uRow;
        *pY = uCurrentPageNumber % m_uRow;
        break;

    case kLeftThenDown:
        // 1 0
        // 3 2
        // 4
        *pX = m_uColumn - 1 - uCurrentPageNumber % m_uColumn;
        *pY =                 uCurrentPageNumber / m_uColumn;
        break;

        // 3 0
        // 4 1
        //   2
    case kDownThenLeft:
        *pX = m_uColumn - 1 - uCurrentPageNumber / m_uRow;
        *pY =                 uCurrentPageNumber % m_uRow;
        break;

    default:
        return false;
    }

    ASSERT(*pX < m_uColumn);
    ASSERT(*pY < m_uRow);

    return true;
}


//+------------------------------------------------------------------------------------------------------
//  Function:
//      CNupLayout::GetRotatePageCoordinate
//
//  Synopsis:
//      Calculate coordinate (x, y) for rotated logical page uCurrentPage, under direction eNupDirection
//      Logical pages should be in the right direction after the page is rotated
//
//  Returns:
//      * pX in [0 .. m_uColumn -1]
//      * pY in [0 .. m_uRow -1]
//      true if succedded, false if invalid input
//-------------------------------------------------------------------------------------------------------

bool CNupLayout::GetRotatePageCoordinate(
    _In_  unsigned       uCurrentPageNumber,
    _In_  ENupDirection  eNupDirection,
    _Out_ unsigned      *pX,
    _Out_ unsigned      *pY) const
{
    ASSERT((pX != NULL) && (pY != NULL));
    ASSERT((m_uRow >= 1) && (m_uColumn >= 1));

    uCurrentPageNumber %= (m_uRow * m_uColumn);

    *pX = 0;
    *pY = 0;

    switch (eNupDirection)
    {
        // 2
        // 1 4
        // 0 3
    case kRightThenDown:
        *pX =              uCurrentPageNumber / m_uRow;
        *pY = m_uRow - 1 - uCurrentPageNumber % m_uRow;
        break;

        // 4
        // 2 3
        // 0 1
    case kDownThenRight:
        *pX =              uCurrentPageNumber % m_uColumn;
        *pY = m_uRow - 1 - uCurrentPageNumber / m_uColumn;
        break;

    case kLeftThenDown:
        // 0 3
        // 1 4
        // 2
        *pX = uCurrentPageNumber / m_uRow;
        *pY = uCurrentPageNumber % m_uRow;
        break;

        // 0 1
        // 2 3
        // 4
    case kDownThenLeft:
        *pX = uCurrentPageNumber % m_uColumn;
        *pY = uCurrentPageNumber / m_uColumn;
        break;

    default:
        return false;
    }

    ASSERT(*pX < m_uColumn);
    ASSERT(*pY < m_uRow);

    return true;
}


_Success_(return)
BOOL
GetPageCoordinatesForNUp(
    _In_        HDC                    hPrinterDC,
    _In_        PEMF_ATTRIBUTE_INFO    pEMFAttr,
    _Out_       RECT                  *rectDocument,
    _Inout_opt_ RECT                  *rectBorder,
    _In_        UINT                   uCurrentPageNumber,
    _Out_       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
             pEMFAttr             - Attributes like n-up, n-up Border flags. Assumed to be valid.
             *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.
             uCurrentPageNumber   - 1 based page number on the side.
             pbRotate             - pointer to BOOL which indicates if the picture must be
                                    rotated.

Return Values:  TRUE if successful
--*/

{
    LONG          ltemp,ldX,ldY;

    DWORD         dwTotalNumberOfPages = pEMFAttr->dwNumberOfPagesPerSide; //total pages on 1 side
    DWORD         dwNupBorderFlags     = pEMFAttr->dwNupBorderFlags;
    ENupDirection eNupDirection        = pEMFAttr->eNupDirection;

    *pbRotate = FALSE;

    // Return full printable are for single page
    if (dwTotalNumberOfPages == 1)
    {
        rectDocument->top    = 0;
        rectDocument->left   = 0;
        rectDocument->bottom = pEMFAttr->lYPrintArea;
        rectDocument->right  = pEMFAttr->lXPrintArea;

        return TRUE;
    }

    // Change to zero-based page number
    ASSERT(uCurrentPageNumber >= 1);
    uCurrentPageNumber --;

    const int lYPrintPage = pEMFAttr->lYPrintArea - 1;
    const int lXPrintPage = pEMFAttr->lXPrintArea - 1;

    const LONG lXPhyPage = pEMFAttr->lXPhyPage;
    const LONG lYPhyPage = pEMFAttr->lYPhyPage;

    //
    // Down in the code, we are dividing by these values, which can lead
    // to divide-by-zero errors.
    //
    if ((0 == lXPhyPage) || (0 == lYPhyPage))
    {
        return FALSE;
    }

    CNupLayout layout(dwTotalNumberOfPages, lXPrintPage > lYPrintPage);

    const unsigned uCol = layout.GetColumn();
    const unsigned uRow = layout.GetRow();

    if ((uCol < 1) || (uRow < 1))
    {
         ODS(("Unsupported nup page per side\n"));

         return FALSE;
    }

    // Size of each logical page on the physical page
    long lXFrame = lXPrintPage / uCol;
    long lYFrame = lYPrintPage / uRow;


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

    // Coordinate of current logical page
    unsigned x = 0;
    unsigned y = 0;

    bool rslt;

    if (*pbRotate)
    {
        rslt = layout.GetRotatePageCoordinate(uCurrentPageNumber, eNupDirection, & x, & y);
    }
    else
    {
        rslt = layout.GetPageCoordinate(uCurrentPageNumber, eNupDirection, & x, & y);
    }

    if (! rslt)
    {
        return FALSE;
    }

    // Set the Page Coordinates.

    rectDocument->left   = (int) (lXPrintPage * x       * 1.0 / uCol);
    rectDocument->right  = (int) (lXPrintPage * (x + 1) * 1.0 / uCol);

    rectDocument->top    = (int) (lYPrintPage * y       * 1.0 / uRow);
    rectDocument->bottom = (int) (lYPrintPage * (y + 1) * 1.0 / uRow);

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

    if ((dwNupBorderFlags == BORDER_PRINT) && (rectBorder != NULL))
    {
        rectBorder->top    = rectDocument->top;
        rectBorder->bottom = rectDocument->bottom - 1;
        rectBorder->left   = rectDocument->left;
        rectBorder->right  = rectDocument->right - 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 -= ldX / 2;
            rectDocument->top    += ldX / 2;
        }
        else
        {
           rectDocument->right   -= ldY / 2;
           rectDocument->left    += ldY / 2;
        }
    }
    else
    {
        if (ldX)
        {
           rectDocument->left    += ldX / 2;
           rectDocument->right   -= ldX / 2;
        }
        else
        {
           rectDocument->top     += ldY / 2;
           rectDocument->bottom  -= ldY / 2;
        }
    }

    // Adjust to get the Printable Area on the rectangle

    int lXOffset = GetDeviceCaps(hPrinterDC, PHYSICALOFFSETX);
    int lYOffset = GetDeviceCaps(hPrinterDC, PHYSICALOFFSETY);

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

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

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

    return TRUE;
}

BOOL
PlayEMFPage(
    _In_       HANDLE                hSpoolHandle,
    _In_       HDC                   hPrinterDC,
    _In_       HANDLE                hEMF,
    _In_ const PEMF_ATTRIBUTE_INFO   pEMFAttr,
    _In_       DWORD                 dwPageIndex,
    _In_       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
              pEMFAttr               -- Attributes like n-up, Border Flags etc. Assumed nonnull.
              dwPageIndex            -- page number in the side. (1 based)
              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};
    XFORM        ScaleXForm = {1, 0, 0, 1, 0, 0};

    DWORD        dwNumberOfPagesPerSide = pEMFAttr->dwNumberOfPagesPerSide;
    BOOL         bSetWorldTransformDone = FALSE;

    // Compute the rectangle for one page.
    if ( FALSE == GetPageCoordinatesForNUp(hPrinterDC,
                            pEMFAttr,
                            &rectDocument,
                            &rectBorder,
                            dwPageIndex,
                            &bRotate) )
    {
        goto CleanUp;
    }

    // If swap flag is set, reverse rotate flag
    //
    if (dwAngle & EMF_DEGREE_SWAP)
        bRotate = !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;
        }

        bSetWorldTransformDone = TRUE;
    }

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

        prectClip = &rectDocument;
    }

    // Scaling is done only if there is no n-up
    if ( 1 == dwNumberOfPagesPerSide )
    {
        // Since we support only square scaling, we assume x and
        // y scaling are equal. So we ignore the
        // y scaling factor.
        ScaleXForm.eM11 = pEMFAttr->fScalingFactorX;
        ScaleXForm.eM22 = ScaleXForm.eM11;

        if ( !bSetWorldTransformDone )
        {
            if (!SetWorldTransform(hPrinterDC, &ScaleXForm) )
            {
                {
                    ODS(("Setting transformation matrix for scaling failed\n"));
                    goto CleanUp;
                }
            }
        }
        else
        {
            if (!ModifyWorldTransform(hPrinterDC, &ScaleXForm, MWT_RIGHTMULTIPLY) )
            {
                {
                    ODS(("Modifying transformation matrix for scaling failed\n"));
                    goto CleanUp;
                }
            }
        }
    }

    // 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(
    _In_    HDC          hPrinterDC,
    _Inout_ LPDEVMODEW   pDevmode,
    _In_    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(
    _In_opt_ LPDEVMODE    pDevmode1,
    _In_opt_ 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;
}

_Success_(return)
BOOL
ResetDCForNewDevmode(
    _In_            HANDLE                hSpoolHandle,
    _In_            HDC                   hPrinterDC,
    _In_            PEMF_ATTRIBUTE_INFO   pEMFAttr,
    _In_            DWORD                 dwPageNumber,
    _In_            BOOL                  bInsidePage,
    _In_            DWORD                 dwOptimization,
    _Out_           LPBOOL                pbNewDevmode,
    _In_            LPDEVMODE             pDevmode,
    _Outptr_result_maybenull_ LPDEVMODE            *ppCurrentDevmode
    )

/*++
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
            pEMFAttr             -  Job Attributes
            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             -  The devmode active when this function called.
            ppCurrentDevmode     -  The devmode that should be active after exiting from this func.

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

{
    BOOL           bReturn = FALSE;
    LPDEVMODE      pLastDM = NULL, pCurrDM = NULL;

    // Initialize OUT parameters
    *pbNewDevmode = FALSE;

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

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

    // Save pointer to current devmode
    if (ppCurrentDevmode)
        *ppCurrentDevmode = pCurrDM;

    // Check if the devmodes are different
    // If pointers are same, devmodes are evidently same
    // If pointers are different, we test the contents of the devmode
    if (pLastDM != pCurrDM) {

        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;
            pCurrDM->dmCopies = pDevmode->dmCopies;


            if ( IS_DMSIZE_VALID ( pCurrDM, dmCollate ) )
            {
                if ( IS_DMSIZE_VALID ( pDevmode, dmCollate ) )
                {
                    pCurrDM->dmCollate = pDevmode->dmCollate;
                }
                else
                {
                    pCurrDM->dmCollate = DMCOLLATE_FALSE;
                }

            }
        }

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

        BUpdateAttributes(hPrinterDC, pEMFAttr);
    }

    bReturn = TRUE;

    return bReturn;
}

DWORD
PrintOneSideForwardEMF(
    _In_  HANDLE                  hSpoolHandle,
    _In_  HDC                     hPrinterDC,
    _In_  PEMF_ATTRIBUTE_INFO     pEMFAttr,
    _In_  BOOL                    bDuplex,
    _In_  DWORD                   dwOptimization,
    _In_  DWORD                   dwPageNumber,
    _Out_ LPBOOL                  pbComplete,
    _In_  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
            pEMFAttr                  --
            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         pCurrDM;
    HANDLE             hEMF = NULL;
    DWORD              dwSides;
    BOOL               bNewDevmode;
    DWORD              cPagesToPlay;
    DWORD              dwAngle;
    INT                dmOrientation = pDevmode->dmOrientation;
    DWORD              dwNumberOfPagesPerSide    = pEMFAttr->dwNumberOfPagesPerSide;
    DWORD              dwDrvNumberOfPagesPerSide = pEMFAttr->dwDrvNumberOfPagesPerSide;
    DWORD              dwJobNumberOfCopies       = pEMFAttr->dwJobNumberOfCopies;


    // 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))
                == NULL) {

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

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

            dwAngle = EMF_DEGREE_90;

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

                    goto CleanUp;
                }

                if (pCurrDM)
                    dmOrientation = pCurrDM->dmOrientation;
            }
            // in case of orientation switch we need to keep track of what
            // we started with and what it is now
            else if (dwNumberOfPagesPerSide > 1)
            {
                if (GdiGetDevmodeForPagePvt(hSpoolHandle,
                                            dwPageNumber,
                                            &pCurrDM,
                                            NULL))
                {
                    if (pCurrDM && pCurrDM->dmOrientation != dmOrientation)
                    {
                        dwAngle = EMF_DEGREE_SWAP | EMF_DEGREE_90;
                        BUpdateAttributes(hPrinterDC, pEMFAttr);
                    }
                }
            }

            // 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,
                             pEMFAttr,
                             dwPageIndex,
                             dwAngle)) {

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

            //
           // Checking dwsides against 2 or 1.
           // depends on whether it is n-up or not.
           //
           if (((dwPageIndex!=1)?(dwSides==2):(dwSides==1))) {

                if ( !BAnyReasonNotToPrintBlankPage(pEMFAttr, dwPageNumber - 1) )
                {
                   if (!GdiStartPageEMF(hSpoolHandle) ||
                       !GdiEndPageEMF(hSpoolHandle, dwOptimization)) {

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

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

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

_Success_(return || *pbComplete != 0)
DWORD
PrintOneSideForwardForDriverEMF(
    _In_  HANDLE                  hSpoolHandle,
    _In_  HDC                     hPrinterDC,
    _In_  PEMF_ATTRIBUTE_INFO     pEMFAttr,
    _In_  BOOL                    bDuplex,
    _In_  DWORD                   dwOptimization,
    _In_  DWORD                   dwSmallestPageOnSheet,
    _Out_ LPBOOL                  pbComplete,
    _In_  LPDEVMODE               pDevmode)

/*++
Function Description:
    PrintOneSideForwardForDriverEMF plays the next physical page in an order determined
    by the order of pages in spool file and the nupDirection. If the nup direction
    is kRightThenDown, then the order of play back is same as the order in spool
    file. For that this function shouldn't be called. PrintOneSideForwardEMF should
    work well for that case. This is limited to the case where driver does nup,
    nup>1 and nupDirection is not kRightThenDown

Parameters: hSpoolHandle              -- handle the spool file handle
            hPrinterDC                -- handle to the printer device context
            pEMFAttr                  --
            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 (pbComplete set to TRUE) and
                  on failure (pbComplete remains FALSE)
--*/

{
    DWORD              dwReturn                  = 0;
    DWORD              dwSides                   = 1;
    PPAGE_NUMBER       pMemoryHead               = NULL, pHeadNewSide = NULL;
    BOOL               bAtleastOneEmptyPageFound = FALSE;
    DWORD              dwNumberOfPagesPerSide    = 0;

    *pbComplete = FALSE;

    // This function written just for the case where driver does nup,
    // nup>1 and nupDirection is not kRightThenDown
    if ( pEMFAttr->dwDrvNumberOfPagesPerSide <= 1             ||
         pEMFAttr->dwNumberOfPagesPerSide    >  1             ||
         pEMFAttr->eNupDirection             == kRightThenDown )
    {
        ODS(("This function should not be called for non-driver nup and traditional nup"));
        return 0;
    }

    dwNumberOfPagesPerSide = pEMFAttr->dwDrvNumberOfPagesPerSide;

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

    if (!GetStartPageListForwardForDriver(
                                          pEMFAttr,
                                          pDevmode,
                                          &pMemoryHead,   //Where memory for the list starts.
                                          &pHeadNewSide, //where the actual head of list starts.
                                          dwSmallestPageOnSheet
                                          )
       )
    {
        ODS(("Cannot get Page Order for Forward Printing"));
        goto CleanUp;
    }

    if ( PrintOneSheetPreDeterminedForDriverEMF (
                                    hSpoolHandle,
                                    hPrinterDC,
                                    pEMFAttr,
                                    bDuplex,
                                    pHeadNewSide,
                                    dwOptimization,
                                    &bAtleastOneEmptyPageFound,
                                    pDevmode)
        )
    {
        if ( bAtleastOneEmptyPageFound )
        {
            // We found one empty page on the side, so that means no more pages to print.
            *pbComplete = TRUE;
        }
        else
        {
            dwSmallestPageOnSheet += dwNumberOfPagesPerSide*dwSides; //Prepare for the next time this function is called.
            dwReturn = dwSmallestPageOnSheet;
        }
    }

CleanUp:

    FreeSplMem(pMemoryHead);
    return dwReturn;
}


BOOL
PrintForwardEMF(
    _In_  HANDLE                  hSpoolHandle,
    _In_  HDC                     hPrinterDC,
    _In_  PEMF_ATTRIBUTE_INFO     pEMFAttr,
    _In_  BOOL                    bDuplex,
    _In_  DWORD                   dwOptimization,
    _In_  LPDEVMODEW              pDevmode,
    _In_  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
            pEMFAttr                  --
            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, dwRemainingCopies;
    BOOL               bReturn = FALSE;

    BOOL               bCollate                   = pEMFAttr->bCollate;
    DWORD              dwJobNumberOfCopies        = pEMFAttr->dwJobNumberOfCopies;
    DWORD              dwDrvNumberOfCopies        = pEMFAttr->dwDrvNumberOfCopies;
    DWORD              dwDrvNumberOfPagesPerSide  = pEMFAttr->dwDrvNumberOfPagesPerSide;
    PFPRINTONESIDEFORWARDEMF   pfPrintOneSideForwardEMF = NULL;

    if ( 1              != dwDrvNumberOfPagesPerSide &&
         kRightThenDown != pEMFAttr->eNupDirection )
    {
        //
        // Special treatment for postscript based printers for non-traditional nup.
        //
        pfPrintOneSideForwardEMF = PrintOneSideForwardForDriverEMF;
    }
    else
    {
        pfPrintOneSideForwardEMF = PrintOneSideForwardEMF;
    }


    // 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 = pfPrintOneSideForwardEMF(hSpoolHandle,
                                                     hPrinterDC,
                                                     pEMFAttr,
                                                     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 (((dwLastPageNumber = pfPrintOneSideForwardEMF(hSpoolHandle,
                                                                hPrinterDC,
                                                                pEMFAttr,
                                                                bDuplex,
                                                                dwOptimization,
                                                                dwPageNumber,
                                                                &bReturn,
                                                                pDevmode))
                       == 0) &&
                   !bReturn) {

                    goto CleanUp;
               }
           }
        }
    }

CleanUp:

    return bReturn;
}

BOOL
PrintOneSideReverseForDriverEMF(
    _In_  HANDLE                hSpoolHandle,
    _In_  HDC                   hPrinterDC,
    _In_  PEMF_ATTRIBUTE_INFO   pEMFAttr,
    _In_  PPAGE_NUMBER          pHead,
    _In_  BOOL                  bDuplex,
    _In_  DWORD                 dwOptimization,
    _In_  LPDEVMODE             pDevmode)

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

Parameters: hSpoolHandle           -- handle the spool file handle
            hPrinterDC             -- handle to the printer device context
            pEMFAttr               --
            pHead                  -- Linked list of page numbers
            bDuplex                -- flag to indicate duplex printing
            dwOptimization         -- optimization flags
            pDevmode               -- devmode with resolution settings

Return Values:  TRUE if successful
                FALSE otherwise
--*/
{
    return PrintOneSheetPreDeterminedForDriverEMF (
                                    hSpoolHandle,
                                    hPrinterDC,
                                    pEMFAttr,
                                    bDuplex,
                                    pHead,
                                    dwOptimization,
                                    NULL,
                                    pDevmode);
}



BOOL
PrintEMFInPredeterminedOrder(
    _In_        HANDLE                  hSpoolHandle,
    _In_        HDC                     hPrinterDC,
    _In_        PEMF_ATTRIBUTE_INFO     pEMFAttr,
    _In_opt_    PPAGE_NUMBER            pHead,
    _In_        BOOL                    bDuplex,
    _In_        DWORD                   dwOptimization,
    _In_        LPDEVMODEW              pDevmode,
    _In_        PPRINTPROCESSORDATA     pData)

/*++
Function Description: PrintEMFInPredeterminedOrder plays the EMF pages in the order dictated by pHead.

Parameters: hSpoolHandle           -- handle the spool file handle
            hPrinterDC             -- handle to the printer device context
            pEMFAttr               -- Job Attributes
            pHead                  -- pointer to a linked list containing the starting
                                       page numbers for each of the sides
            bDuplex                -- flag to indicate 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         dwPageNumber, dwRemainingCopies;
    BOOL          bReturn = FALSE;

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

    DWORD   dwDrvNumberOfCopies        = pEMFAttr->dwDrvNumberOfCopies;
    DWORD   dwJobNumberOfCopies        = pEMFAttr->dwJobNumberOfCopies;
    BOOL    bCollate                   = pEMFAttr->bCollate;
    DWORD   dwDrvNumberOfPagesPerSide  = pEMFAttr->dwDrvNumberOfPagesPerSide;
    PFPRINTONESIDEREVERSEEMF   pfPrintOneSideReverseEMF = NULL;

    if (!pHead) {
        ODS(("pHead is NULL.\n"));
        bReturn = FALSE;
        goto CleanUp;
    }

    //
    // This function is called for Booklet, Reverse Printing in which printer does
    // n-up, and reverse printing in which print processor simulates n-up. So lets
    // select which of the cases it is called for, and setup the appropriate functions.
    //
    if ( pEMFAttr->bBookletPrint )
    {
        pfPrintOneSideReverseEMF = PrintOneSideBookletEMF;
    }
    else if ( dwDrvNumberOfPagesPerSide != 1 )
    {
        //
        // If the printer can handle n-up
        //
        pfPrintOneSideReverseEMF = PrintOneSideReverseForDriverEMF;
    }
    else
    {
        pfPrintOneSideReverseEMF = PrintOneSideReverseEMF;
    }


    // 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 (!pfPrintOneSideReverseEMF(hSpoolHandle,
                                         hPrinterDC,
                                         pEMFAttr,
                                         pHead,
                                         bDuplex,
                                         dwOptimization,
                                         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 (!pfPrintOneSideReverseEMF(hSpoolHandle,
                                             hPrinterDC,
                                             pEMFAttr,
                                             pHead,
                                             bDuplex,
                                             dwOptimization,
                                             pDevmode)) {
                   goto CleanUp;
               }
           }
        }

        //
        // go to the next node(page) in the linked list. For duplex printing
        // we go ahead 2 nodes.
        //
        pHead = pHead->pNextSide;
        if (bDuplex && pHead) {
            pHead = pHead->pNextSide;
        }
    } //while pHead

    bReturn = TRUE;

CleanUp:

    return bReturn;
}

BOOL
PrintOneSideReverseEMF(
    _In_  HANDLE                hSpoolHandle,
    _In_  HDC                   hPrinterDC,
    _In_  PEMF_ATTRIBUTE_INFO   pEMFAttr,
    _In_  PPAGE_NUMBER          pHead,
    _In_  BOOL                  bDuplex,
    _In_  DWORD                 dwOptimization,
    _In_  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
            pEMFAttr               -- Job Attributes
            pHead                  -- List of pages in the order in which they have to be printed.
            bDuplex                -- flag to indicate duplex printing
            dwOptimization         -- optimization flags
            pDevmode               -- devmode with resolution settings

Return Values:  TRUE if successful
                FALSE otherwise
--*/
{
    DWORD         dwPageNumber, dwPageIndex, dwPageType;
    DWORD         dwSides, dwAngle;
    BOOL          bStartPageInitiated     = FALSE;

    BOOL          bReturn                 = FALSE, bNewDevmode;
    LPDEVMODEW    pCurrDM                 = NULL;
    HANDLE        hEMF                    = NULL;
    INT           dmOrientation           = pDevmode->dmOrientation;

    DWORD         dwNumberOfPagesPerSide  = pEMFAttr->dwNumberOfPagesPerSide;
    DWORD         dwTotalNumberOfPages    = pEMFAttr->dwTotalNumberOfPages;
    PPAGE_NUMBER  pHeadLogical            = pHead;


    if ( !pHead )
    {
        return TRUE;
    }

    dwSides = bDuplex ? 2 : 1;

    //
    // For each side, print all the logical pages.
    //
    for (;  dwSides;  --dwSides, pHead = pHead->pNextSide) {

        pHeadLogical = pHead;

        if ( NULL == pHeadLogical )
        {
            ODS(("Unexpected end of ReversePrintList %ws\n", pDevmode->dmDeviceName));
            goto CleanUp;
        }

        dwPageNumber = pHeadLogical->dwPageNumber;

        for (dwPageIndex = 1;
              ( (pHeadLogical != NULL) && (dwPageIndex <= dwNumberOfPagesPerSide));
              pHeadLogical = pHeadLogical->pNextLogPage, ++dwPageIndex)
        {
            dwPageNumber = pHeadLogical->dwPageNumber;

            if ( dwPageNumber == 0 )
            {
                dwPageNumber = 0xFFFFFFFF; //some big number.
            }

            //
            // Get PageHandle only if the page number is less than or equal to
            // total number of pages. A bigger  page number indicates
            // empty/non-existent page for which there can be no handle.
            //
            if (dwPageNumber <= dwTotalNumberOfPages )
            {
                if ((hEMF = GdiGetPageHandle(hSpoolHandle,
                                                 dwPageNumber,
                                                 &dwPageType))
                    == NULL) {

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

            dwAngle = EMF_DEGREE_90;
            if (dwPageIndex == 1)
            {
                //
                // We now need to give a ResetDC call. ResetDC should be given
                // at most once on begining of each side. If side is empty, no
                // ResetDC is done. If side is half filled (e.g. n-up = 2 but
                // only one page has to be printed), then we do ResetDC for
                // first available valid page.
                //
                DWORD dwPageNumberForResetDC = 0xFFFFFFFF;
                PPAGE_NUMBER pHeadMoving  = NULL; //moves thru the list.

                if ( dwPageNumber <= dwTotalNumberOfPages )
                {
                    dwPageNumberForResetDC = dwPageNumber;
                }
                else
                {
                    //
                    // Go to first non-empty logical page.
                    // Note: Empty page means a blank page generated by print processor.
                    //
                    for ( pHeadMoving = pHeadLogical; pHeadMoving; pHeadMoving = pHeadMoving->pNextLogPage )
                    {
                        dwPageNumberForResetDC = pHeadMoving->dwPageNumber;

                        if ( dwPageNumberForResetDC > 0 && dwPageNumberForResetDC <= dwTotalNumberOfPages )
                        {
                            break; //Valid page number found.
                        }
                    } //for
                }

                if ( dwPageNumberForResetDC > 0 && dwPageNumberForResetDC <= dwTotalNumberOfPages )
                {
                    // Process devmodes in the spool file
                    if (!ResetDCForNewDevmode(hSpoolHandle,
                                                 hPrinterDC,
                                                 pEMFAttr,
                                                 dwPageNumberForResetDC,
                                                 FALSE,
                                                 dwOptimization,
                                                 &bNewDevmode,
                                                 pDevmode,
                                                 &pCurrDM) )
                    {
                        goto CleanUp;
                    }
                }

                //
                // Lets do a startPage. This has to be done even if side is empty.
                // Except if printer doesn't want needless blank pages
                //
                if ( dwPageNumber >  dwTotalNumberOfPages    && // A quick check to see if printing a blank page.
                     TRUE         == BAnyReasonNotToPrintBlankPage(pEMFAttr, dwTotalNumberOfPages) &&
                     TRUE         == BIsEveryPageOnThisSideBlank(pHeadLogical, dwTotalNumberOfPages)
                   )
                {
                    // All conditions indicate we should not print blank page. So no reason to do a StartPage
                }
                else
                {
                    if ( !GdiStartPageEMF(hSpoolHandle)) {

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

                    bStartPageInitiated = TRUE;
                }

                if (pCurrDM)
                    dmOrientation = pCurrDM->dmOrientation;
            }

            else if (dwNumberOfPagesPerSide > 1 && dwPageNumber <= dwTotalNumberOfPages)
            {
                // in case of orientation switch we need to keep track of what
                // we started with and what it is now
                if (GdiGetDevmodeForPagePvt(hSpoolHandle,
                                            dwPageNumber,
                                            &pCurrDM,
                                            NULL))
                {
                    if (pCurrDM && pCurrDM->dmOrientation != dmOrientation)
                    {
                        dwAngle = EMF_DEGREE_SWAP | EMF_DEGREE_90;
                        BUpdateAttributes(hPrinterDC, pEMFAttr);
                    }
                }
            }

            if ( dwPageNumber <= dwTotalNumberOfPages )
            {
                _Analysis_assume_(hEMF != NULL);

                if (!PlayEMFPage(hSpoolHandle,
                                    hPrinterDC,
                                    hEMF,
                                    pEMFAttr,
                                    dwPageIndex,
                                    dwAngle)) {

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


        //
        // All pages on the side have been printed. If some sheets are blank
        // a StartPageEMF was sent but PlayEMFPage was not done.
        //
        if (bStartPageInitiated)
        {
            if (!GdiEndPageEMF(hSpoolHandle, dwOptimization)) {
                ODS(("EndPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                goto CleanUp;
            }
            bStartPageInitiated = FALSE;
        }

    } //for dwSides

    bReturn = TRUE;

CleanUp:

    return bReturn;
}


BOOL
PrintOneSideBookletEMF(
    _In_  HANDLE                hSpoolHandle,
    _In_  HDC                   hPrinterDC,
    _In_  PEMF_ATTRIBUTE_INFO   pEMFAttr,
    _In_  PPAGE_NUMBER          pHead,
    _In_  BOOL                  bDuplex,
    _In_  DWORD                 dwOptimization,
    _In_  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
            pEMFAttr               -- Job Attributes
            pHead                  -- List of pages in the order in which they have to be printed.
            bDuplex                -- Whether duplex is supported in hardware
            dwOptimization         -- optimization flags
            pDevmode               -- devmode with resolution settings

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

{
    DWORD       dwPageIndex, dwAngle = 0, dwPageType, dwPageNumber = 0;
    HANDLE      hEMF = NULL;
    LPDEVMODEW  pCurrDM;
    BOOL        bReturn = FALSE ,bNewDevmode;
    INT         dmOrientation;

    DWORD       dwSides;
    PPAGE_NUMBER pHeadLogical, pHeadMoving;

    DWORD       dwNumberOfPagesPerSide    = pEMFAttr->dwNumberOfPagesPerSide;
    DWORD       dwDrvNumberOfPagesPerSide = pEMFAttr->dwDrvNumberOfPagesPerSide;
    DWORD       dwTotalNumberOfPages      = pEMFAttr->dwTotalNumberOfPages;
    DWORD       dwDuplexMode              = pEMFAttr->dwDuplexMode;
    BOOL        bDeviceNup                = dwDrvNumberOfPagesPerSide > 1 ? TRUE: FALSE; //hware supported nup
    DWORD       dwLimit                   = min(dwTotalNumberOfPages,dwDrvNumberOfPagesPerSide);

    if ( !pHead )
    {
        return TRUE;
    }

    //
    // There are 2 n-up options here. One is when the printer supports it
    // (dwDrvNumberOfPagesPerSide > 1) and the other is when print processor
    // simulates it (dwNumberOfPagesPerSide > 1). If both of them are
    // present, then it is an unexpected case. For the time being, I'll let
    // the printer n-up be superior.
    //
    if ( dwDrvNumberOfPagesPerSide > 1 ) //cud also hvae used bDeviceNup
    {
        if ( dwNumberOfPagesPerSide > 1)
        {
            ODS(("PrintOneSideBookletEMF... Both printer hardware n-up and simulated n-up present\n"));
        }

        dwNumberOfPagesPerSide           = dwDrvNumberOfPagesPerSide;
    }

    dwSides = bDuplex ? 2 : 1;

    //
    // Lets reset the devmode for the page number that is
    // first in the pHead list. Note that sometimes,
    // this page number is larger than dwTotalNumberOfPages
    // (to indicate an empty page).
    // Therefore, we need to keep moving pHead to next node
    // till we get a valid dwPageNumber
    //
    for ( pHeadMoving = pHead; pHeadMoving ; pHeadMoving = pHeadMoving->pNextSide)
    {
        for ( pHeadLogical = pHeadMoving; pHeadLogical; pHeadLogical = pHeadLogical->pNextLogPage )
        {
            dwPageNumber = pHeadLogical->dwPageNumber;

            if ( dwPageNumber > 0 && dwPageNumber <= dwTotalNumberOfPages )
            {
                break; //Valid page number found.
            }
        }

        if ( pHeadLogical != NULL )
        {
            break;
        }
    }

    if ( pHeadMoving == NULL )
    {
        ODS (("pHeadMoving is NULL.\n"));
        return FALSE;
    }

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

    if (pCurrDM)
        dmOrientation = pCurrDM->dmOrientation;
    else
        dmOrientation = pDevmode->dmOrientation;

    for (;  dwSides && pHead;  --dwSides, pHead = pHead->pNextSide)
    {
        pHeadLogical = pHead;
        dwPageNumber = pHeadLogical->dwPageNumber;

        for (dwPageIndex = 1;
              ( (pHeadLogical != NULL) && (dwPageIndex <= dwNumberOfPagesPerSide));
              pHeadLogical = pHeadLogical->pNextLogPage, ++dwPageIndex)
        {
            dwPageNumber = pHeadLogical->dwPageNumber;

            if ( dwPageNumber == 0 )
            {
                dwPageNumber = 0xFFFFFFFF; //some big number.
            }

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

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

            //
            // Simulating n-up in print processor. Does not support
            // device n-up.
            //
            if (dwPageNumber <= dwTotalNumberOfPages)
            {
                _Analysis_assume_(hEMF != NULL);

                dwAngle = 0; //reset it for every page.

                //
                // in case of orientation switch we need to keep track of what
                // we started with and what it is now
                //
                if (GdiGetDevmodeForPagePvt(hSpoolHandle,
                                            dwPageNumber,
                                            &pCurrDM,
                                            NULL))
                {
                    if (pCurrDM && pCurrDM->dmOrientation != dmOrientation)
                    {
                        dwAngle |= EMF_DEGREE_SWAP;
                        BUpdateAttributes(hPrinterDC, pEMFAttr);
                    }
                }

                //
                // For flip on long edge, the reverse side has to be rotated 180 degree
                // w.r.t. the 1st side
                //
                if ( (dwSides == 1) && (dwDuplexMode == EMF_DUP_VERT) )
                {
                    dwAngle |= EMF_DEGREE_270;

                    if ( bDeviceNup && (pDevmode->dmOrientation == DMORIENT_LANDSCAPE) )
                    {
                        dwAngle |= EMF_DEGREE_SWAP;
                    }
                }
                else
                {
                    // EMF_DUP_HORZ or 1st side
                    dwAngle |= EMF_DEGREE_90;
                }

                if (!PlayEMFPage(hSpoolHandle,
                                  hPrinterDC,
                                  hEMF,
                                  pEMFAttr,
                                  dwPageIndex,
                                  dwAngle)) {

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

            if (dwPageIndex == dwNumberOfPagesPerSide || bDeviceNup) {

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

            if ( bDeviceNup && pHeadLogical == NULL && dwPageIndex<=dwLimit )
            {
                //
                // Suppose the n-up is 4 (dwLimit is 4) and the n-up is done by the device
                // ( dwDrvNumberOfPagesPerSide > 1)
                // i.e. device needs 4 pages to finish printing a side and eject paper.
                // Suppose pHeadLogical list has only 2 pages (i.e. the pHeadLogical is now NULL after
                // going thru above loop).
                // So we need to send the printer 2 empty pages (logical pages)
                //
                BPlayEmptyPages(hSpoolHandle, (dwLimit-dwPageIndex+1), dwOptimization);
            }
       }
    }

    //
    // dwSides == 0 implies that there were as many sides in the list
    // as we expected. If dwSides != 0, then the linked list ran out
    // of nodes before we expect. This should never happen.
    //
    ASSERT(dwSides == 0);

    bReturn = TRUE;

CleanUp:

    return bReturn;
}

BOOL
PrintEMFSingleCopy(
    _In_        HANDLE                hSpoolHandle,
    _In_        HDC                   hPrinterDC,
    _In_        PEMF_ATTRIBUTE_INFO   pEMFAttr,
    _In_opt_    PPAGE_NUMBER          pHead,
    _In_        DWORD                 dwOptimization,
    _In_        LPDEVMODEW            pDevmode,
    _In_        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
            pEMFAttr               -- Job Attributes
            pHead                  -- pointer to a linked list containing the starting
                                       page numbers for each of the sides
                                      (valid for booklet,reverse 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
--*/

{
    /*++
            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
            bCollate               -- flag for collating the copies
            bBookletPrint          -- flag for booklet printing
            dwDuplexMode           -- duplex printing mode (none|horz|vert)
    --*/
    BOOL  bReverseOrderPrinting      = pEMFAttr->bReverseOrderPrinting;
    BOOL  bBookletPrint              = pEMFAttr->bBookletPrint;
    DWORD dwDuplexMode               = pEMFAttr->dwDuplexMode;

    BOOL  bDuplex = (dwDuplexMode != EMF_DUP_NONE);

    if (bBookletPrint || bReverseOrderPrinting) {

          // Reverse printing or booklet
          return PrintEMFInPredeterminedOrder(hSpoolHandle,
                                 hPrinterDC,
                                 pEMFAttr,
                                 pHead,
                                 bDuplex,
                                 dwOptimization,
                                 pDevmode,
                                 pData);
    } else {

       // Normal printing
       return PrintForwardEMF(hSpoolHandle,
                              hPrinterDC,
                              pEMFAttr,
                              bDuplex,
                              dwOptimization,
                              pDevmode,
                              pData);
    }
}

_Success_(return)
BOOL
GetStartPageListForwardForDriver(
    _In_            PEMF_ATTRIBUTE_INFO    pEMFAttr,
    _In_            LPDEVMODEW             pDevmode,
    _Outptr_        PPAGE_NUMBER          *ppMemoryHead,
    _Outptr_        PPAGE_NUMBER          *ppPageListHead,
    _In_            DWORD                  dwSmallestPageOnSheet
    )
{

    DWORD           dwPageIndex;
    PPAGE_NUMBER    pHeadMoving                 = NULL; //moves thru the list.
    PPAGE_NUMBER    pHeadNewSide                = NULL; //ptr keeps track of new sides.
    BOOL            bReturn                     = FALSE;
    DWORD           dwNumberOfPagesPerSide      = pEMFAttr->dwNumberOfPagesPerSide;
    DWORD           dwDrvNumberOfPagesPerSide   = pEMFAttr->dwDrvNumberOfPagesPerSide;
    DWORD           dwDuplexMode                = pEMFAttr->dwDuplexMode;
    DWORD           dwNumberSides               = 1;
    DWORD           dwMaxLogicalPages           = 0;                       // MaxLogicalPagesPerSheet
    ENupDirection   eNupDirection               = pEMFAttr->eNupDirection;
    PDWORD          pNupDirectionBasedPageOrder = NULL;
    DWORD           dwMemoryForPageList         = 0;
    DWORD           dwSide                      = 1; //Loop counter

    //
    // This function assumes that dwTotalNumberOfPages is set to a valid
    // value.
    //
    if ( ppMemoryHead         == NULL       ||
         ppPageListHead       == NULL
       )
    {
        return FALSE;
    }

    *ppMemoryHead = NULL;
    *ppPageListHead = NULL;

    //
    // There are 2 n-up options here. One is when the printer supports it
    // (dwDrvNumberOfPagesPerSide) and the other is when print processor
    // simulates it (dwNumberOfPagesPerSide). If both of them are
    // present, then it is an unexpected case. For the time being, I'll let
    // the printer n-up be superior.
    //
    if ( dwDrvNumberOfPagesPerSide <= 1 )
    {
        ODS(("This function is only to be called for driver doing n-up, not for simulated n-up"));
    }

    dwNumberOfPagesPerSide  = dwDrvNumberOfPagesPerSide;
    dwNumberSides           = (dwDuplexMode == EMF_DUP_NONE) ?1:2 ;

    if ( ! SUCCEEDED( DWordMult(dwNumberOfPagesPerSide, dwNumberSides      , &dwMaxLogicalPages   ) ) ||
         ! SUCCEEDED( DWordMult(dwMaxLogicalPages     , sizeof(PAGE_NUMBER), &dwMemoryForPageList ) )
       )
    {
        ODS(("GetStartPageListForwardForDriver - Possible Arithmetic overflow "));
        goto CleanUp;
    }

    //
    // allocate the memory for all logical pages. AllocSplMem also zero inits it.
    //
    if ((pHeadMoving = (PPAGE_NUMBER)AllocSplMem( dwMemoryForPageList ))
        == NULL)
    {
        ODS(("GetStartPageListForwardForDriver - Run out of memory"));
        goto CleanUp;
    }

    *ppMemoryHead               = pHeadMoving;
    *ppPageListHead             = pHeadMoving;
    pHeadNewSide                = pHeadMoving;
    pNupDirectionBasedPageOrder = GetPlaybackPageOrderForDriverNup(dwNumberOfPagesPerSide,
                                                                   eNupDirection,
                                                                   pDevmode->dmOrientation);

    //
    // The following loop will arrange pages as below.
    // Assuming
    //      1) 5 pages with 4-up kDownThenLeft
    //
    //
    //        ------------------pNextSide
    //       |
    //       |
    //       \/
    //     3 -->  7
    //     |      | <-- pNextLogicalPage
    //     |      |
    //     \/     \/
    //     1      5
    //     |      | <-- pNextLogicalPage
    //     |      |
    //     \/     \/
    //     4      8
    //     |      | <-- pNextLogicalPage
    //     |      |
    //     \/     \/
    //     2      6
    // Pages 7,8,6 are marked but they should be treated as empty pages.
    // since maximum pages are 5. But note that in forward printing, we don't
    // know the maximum pages. So it should be assumed that when print processor
    // asks for a page n and Gdi can't provide it, it means that page doesn't exist
    // i.e. the total number of pages in job is less than n.
    //


    //
    // While loop advances a side of the sheet.
    //
    for ( dwSide = 1; dwSide <= dwNumberSides; dwSide++, dwSmallestPageOnSheet += dwNumberOfPagesPerSide )
    {
        //
        // For loop fills in information for each page in the side.
        //
        for (dwPageIndex = 1;
            (dwPageIndex <= dwNumberOfPagesPerSide) ;
            ++dwPageIndex)
        {
            // Create a node for the start of a side
            if (dwPageIndex == 1)
            {
                //
                // A new side starts here. i.e. this page has to
                // be printed on a new side.
                //

                pHeadMoving->pNextSide    = NULL;

                if ( 2 == dwSide )
                {
                    pHeadNewSide->pNextSide   = pHeadMoving;
                }
            }
            else
            {
                //
                // This is a new logical page to be printed
                // on the same side as the previous logical page.
                //
                (pHeadMoving-1)->pNextLogPage = pHeadMoving;
            }

            //
            // Note page number here can exceed max pages in the document (dwTotalNumberOfPages).
            // The max. page possible here is dwMaxLogicalPages which can be
            // greater than dwTotalNumberOfPages. e.g. in cond 2 above, dwTotalNumberOfPages = 5
            // but dwMaxLogicalPages = 6.
            //

            pHeadMoving->dwPageNumber = dwSmallestPageOnSheet + pNupDirectionBasedPageOrder[dwPageIndex-1] - 1;

            pHeadMoving++;
        } //end of for.

     } //end of while

    bReturn = TRUE;

CleanUp:

    if ( FALSE == bReturn )
    {
        if ( *ppMemoryHead )
        {
            FreeSplMem (*ppMemoryHead);
        }

        *ppMemoryHead   = NULL;
        *ppPageListHead = NULL;
    }

    return bReturn;
}

_Success_(return)
BOOL
GetStartPageListBooklet(
    _In_            PEMF_ATTRIBUTE_INFO    pEMFAttr,
    _Outptr_result_maybenull_ PPAGE_NUMBER          *ppMemoryHead,
    _Outptr_result_maybenull_ PPAGE_NUMBER          *ppPageListHead)

/*++
Function Description: GetStartPageListBooklet generates an ordered list of page numbers which
                      should appear on 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 GetStartPageListBooklet is used
                      to play the job in BookletMode.

Parameters: hSpoolHandle           -- handle the spool file handle
            pEMFAttr               -- Job Attributes
            ppPageListHead         -- pointer to a pointer to a linked list containing the
                                       starting page numbers for each of the sides
            ppMemoryHead           -- pointer to pointer to memory that has to be releazed by
                                      calling function after it is done processing the pages in
                                      ppPageListHead

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

{
    PPAGE_NUMBER pMemoryHead        = NULL;
    PPAGE_NUMBER pHeadMoving        = NULL;
    BOOL         bReturn            = FALSE;
    DWORD        dwTotalSheets      = 1;
    DWORD        dwMaxLogicalPages  = 4; // = dwTotalSheets * 4.
    DWORD        dwMemoryForPageList= 0;


    DWORD        dwTotalNumberOfPages  = pEMFAttr->dwTotalNumberOfPages;
    EBookletMode eBookletMode          = pEMFAttr->eBookletMode;
    DWORD        dwDuplexMode          = pEMFAttr->dwDuplexMode;
    ULONG        ulNodeNumber          = 0;


    //
    // Total number of pages in the job should have been initialized properly.
    // Also ppHead should be valid.
    //
    if ( dwTotalNumberOfPages == 0          ||
         dwTotalNumberOfPages == 0xFFFFFFFF ||
         ppMemoryHead         == NULL       ||
         ppPageListHead       == NULL   )
    {
        ODS(("Parameters to GetStartPageListBooklet are not proper.\n"));
        return FALSE;
    }

    *ppMemoryHead   = NULL;
    *ppPageListHead = NULL;

    //
    // 1) Booklet printing needs some special page ordering.
    // 2) The n-up is always 2.
    // 3) There are 2 n-up options. One is when the printer supports it
    // (dwDrvNumberOfPagesPerSide) and the other is when print processor
    // simulates it (dwNumberOfPagesPerSide). If both of them are
    // present, then it is an unexpected case. For the time being, I'll let
    // the printer n-up be superior.
    //

    //
    // Assume dwTotalSheets is the minimum number of sheets required for doing booklet
    // We print 2 logical pages on each side and we print on
    // both sides. i.e. on a single sheet of paper we print 4 logical
    // pages. So let dwMaxLogicalPages = dwTotalSheets*4.
    // Not all the dwMaxLogicalPages need to be printed (e.g a book can have 3 pages,
    // the 4th page is just empty page).
    // So we need a linked list with dwMaxLogicalPages nodes.
    //
    // The book can be left edge binding (for latin and most other languages)
    // or right edge binding (for hebrew).
    //

    dwTotalSheets = dwTotalNumberOfPages/4;

    if ( dwTotalSheets * 4 != dwTotalNumberOfPages )
    {
        dwTotalSheets++;
    }

    if ( ! SUCCEEDED( DWordMult(dwTotalSheets    , 4                  , &dwMaxLogicalPages  ) ) ||
         ! SUCCEEDED( DWordMult(dwMaxLogicalPages, sizeof(PAGE_NUMBER), &dwMemoryForPageList) )
       )
    {
        ODS(("GetStartPageListBooklet - Possible Arithmetic overflow."));
        goto CleanUp;
    }

    //
    // AllocSplMem initializes the memory to 0.
    //
    if ((pMemoryHead = (PPAGE_NUMBER)AllocSplMem( dwMemoryForPageList )) == NULL) {
        ODS(("GetStartPageListBooklet - Run out of memory"));
        goto CleanUp;
    }

    //
    // Now lets start populating the linked list.
    // For booklet printing of 11 pages, we need 3 sheets. The ordering is
    // (Assuming left edge binding)
    // blank (page 12), 1 - first sheet
    // 11, 2              - back of 1st sheet
    // 10,3               - second sheet
    // 9 ,4               - back of 2nd sheet
    // 8 ,5               - third sheet
    // 7, 6               - back of 3rd sheet.
    //
    // The following loop arranges page numbers. The arrangement can be visualized
    // as follows. (for left edge binding, job with 7 pages, to be printed on 2 sheets).
    // pages 8,1 on 1st side 1st sheet
    // pages 7,2 on reverse side of 1st sheet.
    // pages 6,3 on 1st side of 2nd sheet.
    // pages 5,4 on reverse side of 2nd sheet.
    //
    //                  -----------------------------------(pNextSide)
    //                  |     |
    //                  |     |
    // *ppPageListHead  |     |
    //        |         |     |
    //       \/        \/    \/
    //        8 --> 7  --->6 ----> 5
    //        |     |      |       |  <---- (pNextLogicalPage)
    //       \/    \/      \/      \/
    //        1     2      3       4
    //

    *ppMemoryHead = *ppPageListHead = pHeadMoving = pMemoryHead;

    if ( eBookletMode == kNoBooklet ||
         eBookletMode == kBookletLeftEdge)
    {
        for ( ulNodeNumber = 0 ; ulNodeNumber < dwMaxLogicalPages/2; ulNodeNumber++)
        {
            pHeadMoving->dwPageNumber = dwMaxLogicalPages - ulNodeNumber;
            pHeadMoving->pNextLogPage = pHeadMoving + 1;
            pHeadMoving->pNextSide    = pHeadMoving + 2;
            pHeadMoving++;

            pHeadMoving->dwPageNumber = ulNodeNumber+1;
            pHeadMoving->pNextSide = NULL;
            pHeadMoving->pNextLogPage = NULL;
            pHeadMoving++;
        }

        (pHeadMoving-2)->pNextSide = NULL;
    }
    else if ( eBookletMode == kBookletRightEdge ) //Right Edge binding.
    {
        for ( ulNodeNumber = 0 ; ulNodeNumber < dwMaxLogicalPages/2; ulNodeNumber++)
        {
            pHeadMoving->dwPageNumber = ulNodeNumber+1;
            pHeadMoving->pNextLogPage = pHeadMoving + 1;
            pHeadMoving->pNextSide    = pHeadMoving + 2;
            pHeadMoving++;

            pHeadMoving->dwPageNumber = dwMaxLogicalPages - ulNodeNumber;
            pHeadMoving->pNextSide    = NULL;
            pHeadMoving->pNextLogPage = NULL;
            pHeadMoving++;
        }

        (pHeadMoving-2)->pNextSide = NULL;
    }


    if ( dwDuplexMode == EMF_DUP_HORZ ) //Flip on Short Edge.
    {
        // Q. In booklet, there is no concept of long edge or short edge,
        //    why do we even need this piece of code.
        // A. The driver UI gives user option to select both long/short edge with booklet.
        //    Whatever edge is selected by user, the printer is initialized appropriately
        //    by the renderer. So print processor has to play pages depending
        //    on how printer is rotates its pages.
        //    If driver config would not allow user to select the edge, then
        //    this code may not be required.
        //
        // The pages need to be re-arranged as follows.
        // If the pages are not flipped, the printout is not proper.
        // The sheet that has the first page should not be flipped.
        // For Forward printing, the *ppPageListHead points to the first
        // logical page. So we start switching from *ppPageListHead->pNextSide
        // For Reverse Printing, *ppPageListHead points to the last sheet.'
        // The first sheet has the logical page number 1. There are even
        // logical pages (numofsheets * 4) and therefore there are even sides
        // ( (numofsheets * 4)/2). The last side has page number 1. This side
        // cannot be switched. So we have to start switching right from
        // *ppPageListHead.
        //
        pHeadMoving = *ppPageListHead;


            //                  -----------------------------------(pNextSide)
            //                  |     |
            //                  |     |
            // *ppPageListHead  |     |
            //        |         |     |
            //       \/        \/    \/
            //        8 --> 2  --->6 ----> 4
            //        |     |      |       |  <---- (pNextLogicalPage)
            //       \/    \/      \/      \/
            //        1     7      3       5
            // Note pages 2,7 have been flipped, and so have 4,5
            //
            while ( pHeadMoving && pHeadMoving->pNextSide )
            {
                pHeadMoving                 = pHeadMoving->pNextSide;

                if ( pHeadMoving->pNextLogPage )
                {
                    DWORD      dwTempPageNumber = pHeadMoving->dwPageNumber;
                    pHeadMoving->dwPageNumber   = pHeadMoving->pNextLogPage->dwPageNumber;
                    pHeadMoving->pNextLogPage->dwPageNumber = dwTempPageNumber;
                }

                pHeadMoving = pHeadMoving->pNextSide;
            }
    }

    bReturn = TRUE;

CleanUp:

    if ( bReturn == FALSE)
    {
        if ( pMemoryHead )
        {
            FreeSplMem (pMemoryHead);
        }

        *ppMemoryHead   = NULL;
        *ppPageListHead = NULL;
    }

    return bReturn;
}

_Success_(return)
BOOL
GetStartPageListReverseOrder(
    _In_            HANDLE                 hSpoolHandle,
    _In_            PEMF_ATTRIBUTE_INFO    pEMFAttr,
    _In_            LPDEVMODEW             pDevmode,
    _Outptr_result_maybenull_        PPAGE_NUMBER          *ppMemoryHead,
    _Outptr_result_maybenull_        PPAGE_NUMBER          *ppPageListHead)

/*++
Function Description:
    GetStartPageListReverseOrder 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 GetStartPageListReverseOrder is used
    to play the job in reverse order.

Parameters:
    hSpoolHandle           -- handle the spool file handle
    pEMFAttr               -- Job Attributes
    ppMemoryHead           -- pointer to a pointer to allocated memory. This memory holds the page list.
    ppPageListHead         -- 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

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

{
    DWORD         dwPageIndex, dwPageNumber   = 1;
    LPDEVMODEW    pCurrDM, pLastDM            = NULL;
    PPAGE_NUMBER  pHeadMoving                 = NULL; //moves thru the list.
    PPAGE_NUMBER  pHeadNewSide                = NULL; //ptr keeps track of new sides.
    BOOL          bReturn                     = FALSE;
    BOOL          bCheckDevmode               = FALSE;
    DWORD         dwTotalNumberOfPages        = pEMFAttr->dwTotalNumberOfPages;
    DWORD         dwNumberOfPagesPerSide      = pEMFAttr->dwNumberOfPagesPerSide;
    DWORD         dwDrvNumberOfPagesPerSide   = pEMFAttr->dwDrvNumberOfPagesPerSide;
    DWORD         dwDuplexMode                = pEMFAttr->dwDuplexMode;
    ULONG         ulNumNodes                  = 0;
    DWORD         dwMaxLogicalPagesPerSheet   = 1;
    DWORD         dwTotalSheets               = 0;
    DWORD         dwMaxLogicalPages           = 0;
    ENupDirection eNupDirection               = pEMFAttr->eNupDirection;
    PDWORD        pNupDirectionBasedPageOrder = NULL;
    DWORD         dwMemoryForPageList         = 0;

    //
    // This function assumes that dwTotalNumberOfPages is set to a valid
    // value.
    //
    if ( dwTotalNumberOfPages == 0 ||
         dwTotalNumberOfPages == 0xFFFFFFFF ||
         ppMemoryHead         == NULL       ||
         ppPageListHead       == NULL    )
    {
        return FALSE;
    }

    *ppMemoryHead   = NULL;
    *ppPageListHead = NULL;

    //
    // There are 2 n-up options here. One is when the printer supports it
    // (dwDrvNumberOfPagesPerSide) and the other is when print processor
    // simulates it (dwNumberOfPagesPerSide). If both of them are
    // present, then it is an unexpected case. For the time being, I'll let
    // the printer n-up be superior.
    //
    if ( dwDrvNumberOfPagesPerSide > 1 )
    {
        if ( dwNumberOfPagesPerSide > 1)
        {
            ODS(("GetStartPageListReverseOrder..Both printer hardware n-up and simulated n-up present\n"));
        }
        dwNumberOfPagesPerSide           = dwDrvNumberOfPagesPerSide;
    }


    //
    // First see how many sheets of paper will be required. Some examples are.
    // Cond. Id       Condition.              Sheets required.
    // --------     ---------------         -------------------
    // 1.          5 pg, 1-up, no duplex        5
    // 2.          5 pg, 2-up, no duplex        3
    // 3.          5 pg, 2-up, duplex           2
    // 4.          7 pg, 4-up, duplex           1
    //
    // If dwDuplexMode is not zero, i.e. duplex is on, then we can fit double pages per sheet of paper.

    if ( ! SUCCEEDED ( DWordMult(dwNumberOfPagesPerSide,
                                 ((dwDuplexMode == EMF_DUP_NONE) ? 1 : 2) ,
                                 &dwMaxLogicalPagesPerSheet) )
       )
    {
        goto CleanUp;
    }

    dwTotalSheets             = dwTotalNumberOfPages/dwMaxLogicalPagesPerSheet;

    if ( dwTotalSheets * dwMaxLogicalPagesPerSheet != dwTotalNumberOfPages )
    {
        dwTotalSheets++;
    }

    if ( ! SUCCEEDED ( DWordMult(dwTotalSheets    , dwMaxLogicalPagesPerSheet, &dwMaxLogicalPages  ) ) ||
         ! SUCCEEDED ( DWordMult(dwMaxLogicalPages, sizeof(PAGE_NUMBER)      , &dwMemoryForPageList) )
       )
    {
        ODS(("GetStartPageListForwardForDriver - Possible Arithmetic overflow"));
        goto CleanUp;
    }

    //
    // allocate the memory for all logical pages. AllocSplMem also zero inits it.
    //
    if ((pHeadMoving = (PPAGE_NUMBER)AllocSplMem(dwMemoryForPageList))
        == NULL)
    {
        ODS(("GetStartPageListBooklet - Run out of memory"));
        goto CleanUp;
    }

    *ppMemoryHead     = pHeadMoving;
    pHeadMoving += (dwMaxLogicalPages - 1) ; //Go to the last block.
    pHeadNewSide = NULL;

    //
    // The following loop will arrange pages as below.
    // Assuming
    //      1) 5 pages with 2-up
    //      2) DifferentDevmodes() always fails.
    //
    //
    //                  ------------------pNextSide
    //                  |
    //                  |
    //                  \/
    //     7 -->  5  -->  3  -->  1
    //     |      |       |       |  <-- pNextLogicalPage
    //     |      |       |       |
    //     \/     \/      \/      \/
    //     8      6       4       2
    //
    // Pages 7,8,6 are marked but they should be treated as empty pages.
    // since maximum pages are 5.
    //


    pNupDirectionBasedPageOrder = GetPlaybackPageOrderForDriverNup(dwNumberOfPagesPerSide, eNupDirection, pDevmode->dmOrientation);

    //
    // While loop advances a side of the sheet.
    //
    while (dwPageNumber <= dwMaxLogicalPages && pHeadMoving >= *ppMemoryHead) {

        //
        // For loop fills in information for each page in the side.
        //
        for (dwPageIndex = 1;
            (dwPageIndex <= dwNumberOfPagesPerSide) && (dwPageNumber <= dwMaxLogicalPages);
            ++dwPageIndex, ++dwPageNumber)
        {

            if (bCheckDevmode && (dwPageNumber <= dwTotalNumberOfPages) )
            {
                // Check if the devmode has changed requiring a new page
                if (!GdiGetDevmodeForPagePvt(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;
                        }
                }
            } //if (bCheckDevmode)

            // Create a node for the start of a side
            if (dwPageIndex == 1)
            {
                //
                // A new side starts here. i.e. this page has to
                // be printed on a new side.
                //

                pHeadMoving->pNextSide    = pHeadNewSide;
                pHeadNewSide              = pHeadMoving;
                ulNumNodes++;
            }
            else
            {
                //
                // This is a new logical page to be printed
                // on the same side as the previous logical page.
                //
                (pHeadMoving+1)->pNextLogPage = pHeadMoving;
            }

            //
            // Note page number here can exceed max pages in the document (dwTotalNumberOfPages).
            // The max. page possible here is dwMaxLogicalPages which can be
            // greater than dwTotalNumberOfPages. e.g. in cond 2 above, dwTotalNumberOfPages = 5
            // but dwMaxLogicalPages = 6.
            // Also note that one when n-up is not RIGHT_THEN_DOWN and if the printer
            // can do its own n-up (i.e. print processor doesn't need to simulate it)
            // we have to order the pages in a specific way (Read the comment before
            // gPageOrderPlayBackForDriver for more info.

            if ( dwDrvNumberOfPagesPerSide > 1 &&
                 eNupDirection != kRightThenDown )
            {
                DWORD dwSideNumber        = (dwPageNumber-1) / dwNumberOfPagesPerSide;
                DWORD dwNewPageIndex      = pNupDirectionBasedPageOrder[dwPageIndex-1]; //dwPageIndex is 1 based
                DWORD dwNewPageNumber     = (dwSideNumber * dwNumberOfPagesPerSide) + dwNewPageIndex;
                pHeadMoving->dwPageNumber = dwNewPageNumber;

            }
            else
            {
                pHeadMoving->dwPageNumber = dwPageNumber;
            }

            pHeadMoving--;
        } //end of for.

     } //end of while


    //
    // If we are printing duplex, then do some specific page
    // order manipulation for specific types of printers.
    //
    if ( EMF_DUP_NONE         != pEMFAttr->dwDuplexMode &&
         kFaceUp_NewPageUnder == pEMFAttr->ePaperFace )
    {
        pHeadNewSide = BReverseSidesOnSheet(pEMFAttr, pHeadNewSide);
    }

    *ppPageListHead = pHeadNewSide;

    bReturn = TRUE;

CleanUp:

    if ( FALSE == bReturn )
    {
        if ( *ppMemoryHead )
        {
            FreeSplMem (*ppMemoryHead);
        }

        *ppMemoryHead   = NULL;
        *ppPageListHead = NULL;
    }

    return bReturn;
}

/*++
Function Description:
    This function switches the order in which the 2 sides are printed on the sheet.

Parameters:
     pEMFAttr               -- Job Attributes
     pPageList              -- pointer to the page order

Return Values:  The new pointer to page order.
                NULL if some error is encountered
--*/
_Success_(return != NULL)
PPAGE_NUMBER
BReverseSidesOnSheet(
        _In_         PEMF_ATTRIBUTE_INFO    pEMFAttr,
        _Inout_opt_  PPAGE_NUMBER           pPageList)
{
    PPAGE_NUMBER  pHeadMoving = pPageList;

    //
    // First make sure the job is duplex. If not,
    // this function was called in error.
    //
    if ( NULL          == pEMFAttr               ||
         NULL          == pPageList              ||
         EMF_DUP_NONE  == pEMFAttr->dwDuplexMode )
    {
        return NULL;
    }

    // INPUT
    // Assuming
    //      1) 5 pages with 2-up
    //
    //
    // pPageList        ------------------pNextSide (equiv to NextSheet)
    //     |            |
    //     |            |
    //     \/           \/
    //     7 -->  5  -->  3  -->  1
    //     |      |       |       |  <-- pNextLogicalPage
    //     |      |       |       |
    //     \/     \/      \/      \/
    //     8      6       4       2
    //
    // Pages 7,8,6 are marked but they should be treated as empty pages.
    // since maximum pages are 5.
    //

    // OUTPUT
    //
    // pPageList        ------------------pNextSide (equiv to NextSheet)
    //     |            |
    //     |            |    |------------pNextSide (other side of same sheet)
    //     \/           \/  \/
    //     5 -->  7  -->  1  -->  3
    //     |      |       |       |  <-- pNextLogicalPage
    //     |      |       |       |
    //     \/     \/      \/      \/
    //     6      8       2       4
    //
    // Pages 7,8,6 are marked but they should be treated as empty pages.
    // since maximum pages are 5.
    //

    //
    // Reset the pPageList to the new Head
    //
    if ( pPageList && pPageList->pNextSide )
    {
        pPageList = pPageList->pNextSide;
    }


    // Switch the two sides
    //
    while ( pHeadMoving && pHeadMoving->pNextSide )
    {
        PPAGE_NUMBER pFirstSide  = pHeadMoving;
        PPAGE_NUMBER pSecondSide = pFirstSide->pNextSide;

        //
        // Make pHeadMoving point to the first side of next sheet.
        // Then pHeadMoving will be equivalent to saying pNextSheet.
        //
        pHeadMoving = pSecondSide->pNextSide;

        //
        // Now switch first side and second side.
        //
        if ( NULL == pHeadMoving ) // If there is no next sheet (i.e. this is last sheet).
        {
            pFirstSide->pNextSide = NULL;
        }
        else
        {
            //
            // Point current first side to 2nd side of the next sheet
            //
            pFirstSide->pNextSide  = pHeadMoving->pNextSide;
        }
        pSecondSide->pNextSide = pFirstSide;

    }


    return pPageList;

}

BOOL
CopyDevmode(
    _In_            PPRINTPROCESSORDATA pData,
    _Outptr_result_maybenull_ 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;
    LONG             lNeeded;

    *pDevmode = NULL;

    if (pData->pDevmode) {

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

        if ((*pDevmode = (LPDEVMODEW) AllocSplMem(lNeeded)) != NULL) {
            memcpy(*pDevmode, pData->pDevmode, lNeeded);
        } else {
            goto CleanUp;
        }
    } else {
        // Get the default devmode
        // Get a client side printer handle to pass to the driver
        if (!OpenPrinter(pData->pPrinterName, &hDrvPrinter, NULL)) {
            ODS(("Open printer failed\nPrinter %ws\n", pData->pPrinterName));
            goto CleanUp;
        }

        lNeeded = DocumentProperties(NULL, hDrvPrinter, pData->pPrinterName, NULL, NULL, 0);

        if ((lNeeded <= 0) ||
            ((*pDevmode = (LPDEVMODEW) AllocSplMem(lNeeded)) == NULL) ||
            (DocumentProperties(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) {
        ClosePrinter(hDrvPrinter);
    }

    return bReturn;
}

BOOL
PrintEMFJob(
    _In_ PPRINTPROCESSORDATA pData,
    _In_ 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;
    DWORD              LastError = ERROR_SUCCESS;
    HDC                hPrinterDC = NULL;

    BOOL               bReverseOrderPrinting, bReturn = FALSE, bSetWorldXform = TRUE;
    BOOL               bDuplex, bBookletPrint;
    BOOL               bUpdateAttributes = FALSE;
    SHORT              dmCollate,dmCopies;

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

    XFORM              OldXForm = {0};
    PPAGE_NUMBER       pMemoryHead = NULL, pPageListHead = NULL;
    ATTRIBUTE_INFO_4   AttributeInfo;
    LPDEVMODEW         pDevmode = NULL, pFirstDM = NULL, pCopyDM;
    EMF_ATTRIBUTE_INFO EMFAttr;

    DWORD dwData = 0;
    DWORD cbSize = 0;
    HANDLE   hPrinter = NULL;

    memset(&EMFAttr, 0, sizeof(EMF_ATTRIBUTE_INFO));
    EMFAttr.dwTotalNumberOfPages = 0xFFFFFFFF; //since this attribute is not always
                                               //valid, 0xfffffff means uninitialized.
    EMFAttr.dwSignature  = EMF_ATTRIBUTE_INFO_SIGNATURE;


    // 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;
    }

    if ( ! BIsDevmodeOfLeastAcceptableSize (pDevmode) )
    {
        ODS(("Devmode not big enough. Failing job.\nPrinter %ws\nDocument %ws\nJobID %u\n", pData->pDevmode->dmDeviceName, pData->pDocument, pData->JobId));
        goto CleanUp;
    }

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

        if (pDevmode->dmFields & DM_COLLATE)
            dmCollate = pDevmode->dmCollate;
        else
            dmCollate = DMCOLLATE_FALSE;

        if (pDevmode->dmFields & DM_COPIES)
            dmCopies = pDevmode->dmCopies;
        else
            dmCopies = 0;
    }

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

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

#pragma prefast(suppress:__WARNING_EXCEPTIONEXECUTEHANDLER, "Here we handle all exceptions.")
    } __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;
    }


    BUpdateAttributes(hPrinterDC, &EMFAttr);


    //
    // Use the first devmode in the spool file to update the copy count
    // and the collate setting
    //
    if (GdiGetDevmodeForPagePvt(hSpoolHandle, 1, &pFirstDM, NULL) &&
        pFirstDM) {

        if (pFirstDM->dmFields & DM_COPIES) {
            pDevmode->dmFields |= DM_COPIES;
            pDevmode->dmCopies = pFirstDM->dmCopies;
        }

        if ( (pFirstDM->dmFields & DM_COLLATE) &&
             IS_DMSIZE_VALID ( pDevmode, dmCollate) )
        {
            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;

    // If collate is true this limits the ability of the driver to do multiple copies
    // and causes the driver (PS) supported n-up to print blank page borders for reverse printing.
    // Therefore we disable collate for 1 page multiple copy jobs or no copies but n-up since
    // collate has no meaning in those cases.
    //
    if ((pDevmode->dmFields & DM_COLLATE) && pDevmode->dmCollate == DMCOLLATE_TRUE)
    {
        if (dwJobNumberOfCopies > 1)
        {
            // Get the number of pages in the job. This call waits till the
            // last page is spooled.
            __try {

                dwTotalNumberOfPages = GdiGetPageCount(hSpoolHandle);

#pragma prefast(suppress:__WARNING_EXCEPTIONEXECUTEHANDLER, "Here we handle all exceptions.")
            } __except (EXCEPTION_EXECUTE_HANDLER) {

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

            if (dwTotalNumberOfPages > AttributeInfo.dwDrvNumberOfPagesPerSide)
                goto SkipCollateDisable;
        }
        // if copies == 1 and driver n-up we will disable collate
        //
        else if (AttributeInfo.dwDrvNumberOfPagesPerSide <= 1 && dmCollate == DMCOLLATE_TRUE)
            goto SkipCollateDisable;

        if( OpenPrinter(pData->pPrinterName,
                        &hPrinter,
                        NULL))
        {
               if( GetPrinterData( hPrinter,
                                const_cast<LPTSTR>(gszDriverKeepCollate),
                                NULL,
                                reinterpret_cast<LPBYTE>( &dwData ),
                                sizeof(dwData),
                                &cbSize ) == ERROR_SUCCESS )
               {

                    if( dwData == 1)
                    {
                        goto SkipCollateDisable;
                    }
               }
        }

        pDevmode->dmCollate = DMCOLLATE_FALSE;

        if (pFirstDM &&
            IS_DMSIZE_VALID ( pFirstDM, dmCollate) )
        {
            pFirstDM->dmCollate = DMCOLLATE_FALSE;
        }
    }

SkipCollateDisable:
    // Update the job attributes but only if something has changed. This is an expensive
    // call so we only make a second call to GetJobAttributes if something has changed.
    //
    if (bUpdateAttributes || pDevmode->dmCopies != dmCopies ||
            ((pDevmode->dmFields & DM_COLLATE) && (pDevmode->dmCollate != dmCollate)))
    {
        if (!PrintProcGetJobAttributesEx(pData->pPrinterName, pDevmode, &AttributeInfo))
        {
            ODS(("PrintProcGetJobAttributesEx 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);

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

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

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

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

    // 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;
    }

    //
    // From local variables, put the values in EMFAttr.
    // Note: The value for dwTotalNumberOfPages has been initialized
    // to 0xFFFFFFFF which means undetermined. Later on, this value
    // may be overwritten.
    //
    EMFAttr.eNupDirection             = ExtractNupDirectionFromAttributeInfo(&AttributeInfo, bBookletPrint);
    EMFAttr.eBookletMode              = ExtractBookletModeFromAttributeInfo(&AttributeInfo, bBookletPrint);
    EMFAttr.bBookletPrint             = bBookletPrint;
    EMFAttr.bReverseOrderPrinting     = bReverseOrderPrinting;
    EMFAttr.dwDrvNumberOfPagesPerSide = dwDrvNumberOfPagesPerSide;
    EMFAttr.dwNupBorderFlags          = dwNupBorderFlags;
    EMFAttr.dwJobNumberOfCopies       = dwJobNumberOfCopies;
    EMFAttr.dwDuplexMode              = dwDuplexMode;
    EMFAttr.dwNumberOfPagesPerSide    = dwNumberOfPagesPerSide;
    EMFAttr.dwDrvNumberOfCopies       = dwDrvNumberOfCopies;
    ExtractScalingFactorFromAttributeInfo(&AttributeInfo, &EMFAttr);
    ExtractDuplexModeInformationFromAttributeInfo(&AttributeInfo, &EMFAttr);


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

    //
    // For reverse, booklet printing, we need to know the total number
    // of pages. So we have to wait till all the pages are spooled.
    //
    if (bReverseOrderPrinting || bBookletPrint) {

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

           dwTotalNumberOfPages= GdiGetPageCount(hSpoolHandle);

#pragma prefast(suppress:__WARNING_EXCEPTIONEXECUTEHANDLER, "Here we handle all exceptions.")
       } __except (EXCEPTION_EXECUTE_HANDLER) {

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

        EMFAttr.dwTotalNumberOfPages      = dwTotalNumberOfPages;

        //
        // GetStartPageList for reverse/booklet printing
        // Check for a change of devmode between pages only if Nup and PCL driver
        // If both booklet and reverse printing are present,
        // we choose booklet.
        //
        if ( bBookletPrint )
        {
            if (!GetStartPageListBooklet(
                                         &EMFAttr,
                                         &pMemoryHead,   //Where memory for the list starts.
                                         &pPageListHead  //where the actual head of list starts.
                                         )) {
                 goto CleanUp;
            }
        }
        else if ( bReverseOrderPrinting )
        {
            //
            // Get start page list. For certain "normal" printing scenarios,
            // we dont really need the list. So pHead will be returned as NULL.
            // (We could still poplulate pHead, but that will mean needlessly
            // going through memory allocation code etc...).
            // But for others ( like reverse printing/booklet printing)
            // it is good to get the page order here and make things simpler
            // in the future.
            //
            if (!GetStartPageListReverseOrder(hSpoolHandle,
                                  &EMFAttr,
                                  pCopyDM,
                                  &pMemoryHead, //Where memory for the list starts.
                                  &pPageListHead //where the actual head of list starts.
                                  )) {
                 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;
    }


    bReturn = BPrintEMFJobNow (
                            hSpoolHandle,
                            hPrinterDC,
                            &EMFAttr,
                            dwOptimization,
                            pCopyDM,
                            pPageListHead,
                            pData );

    //
    // Preserve the last error
    //
    LastError = bReturn ? ERROR_SUCCESS : GetLastError();


CleanUp:

    if(hPrinter)
    {
        ClosePrinter(hPrinter);
    }

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

    if (pMemoryHead)
    {
        FreeSplMem(pMemoryHead);
    }

    if (pDevmode)  {
       FreeSplMem(pDevmode);
    }

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

#pragma prefast(suppress:__WARNING_EXCEPTIONEXECUTEHANDLER, "Here we handle all exceptions.")
    } __except (EXCEPTION_EXECUTE_HANDLER) {

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

    SetLastError(LastError);

    return bReturn;
}

/*++
Function Name
    BPrintEMFJobNow

Function Description.

Parameters:
            hSpoolHandle       -- the handle to the spool file
            hPrinterDC         -- the devmode related to this page number is requested.
            pEMFAttr           -- the devmode for the dwPageNumber is placed here.
                               -- devmode for dwPageNumber-1 is placed here. Can be NULL. (if n
            dwOptimization
            pDevmode
            pPageList
            pData

Return Values:  TRUE if the emf is printed
                FALSE otherwise
--*/

BOOL
BPrintEMFJobNow (
        _In_      HANDLE                hSpoolHandle,
        _In_      HDC                   hPrinterDC,
        _In_      PEMF_ATTRIBUTE_INFO   pEMFAttr,
        _In_      DWORD                 dwOptimization,
        _In_      LPDEVMODEW            pDevmode,
        _In_opt_  PPAGE_NUMBER          pPageList,
        _In_      PPRINTPROCESSORDATA   pData )
{
    DOCINFOW    DocInfo;
    DWORD       LastError;
    BOOL        bRetVal              = FALSE;
    BOOL        bStartDoc            = FALSE;
    DWORD       dwRemainingCopies    = 0;
    DWORD       dwJobNumberOfCopies  = pEMFAttr->dwJobNumberOfCopies;
    DWORD       dwDrvNumberOfCopies  = pEMFAttr->dwDrvNumberOfCopies;
    BOOL        bStartNewDocOnCopies = pEMFAttr->dwDivideCopiesIntoMultipleDocuments;

    static INT  iThrowDriverException = -1;

    //
    // Refer to KB article KB934885 for more details on the usage of the "ThrowDriverException" registry value.
    //
    if (iThrowDriverException == -1)
    {
        HKEY hKey;

        if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                                          TEXT("System\\CurrentControlSet\\Control\\Print"),
                                          0,
                                          KEY_READ,
                                          &hKey))
        {
            DWORD dwData, cbData;

            cbData = sizeof(dwData);
            if (ERROR_SUCCESS == RegQueryValueEx(hKey,
                                                 TEXT("ThrowDriverException"),
                                                 0,
                                                 NULL,
                                                 (LPBYTE)&dwData,
                                                 &cbData)                         &&
                dwData == 1)
            {
                iThrowDriverException = 1;
            }
            else
            {
                iThrowDriverException = 0;
            }

            RegCloseKey(hKey);
        }
    }

    __try {

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

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

/*++ Only For OSes later than Windows Server 2003. This is an example of how
     IHVs may use the multiple start doc feature

        //
        // If for some reason, we need to break a job into multiple documents,
        // then we have to do something special. e.g. In manual duplex
        // the user may take some time to walk over to the printer
        // and invert the pages, this may cause a TCP timeout and the
        // the job will abandon. To prevent a TCP timeout, we have to
        // first set JOB_CONTROL_RETAIN flag for the job and then
        // do JOB_CONTROL_RELEASE once everything is printed.
        //
        if ( <Multiple StartDoc needs to be done>)

        {
            if (! OpenPrinterW(pData->pPrinterName,&hPrinter,&Defaults) )
            {
                goto CleanUp;
            }
            SetJobW(hPrinter, pData->JobId, 0, NULL, JOB_CONTROL_RETAIN);
        }
--*/

        if (pEMFAttr->bCollate) {

            dwRemainingCopies = dwJobNumberOfCopies & 0x0000FFFF ;

            while (dwRemainingCopies) {
                //
                // We do a new startDoc only if this is not the first time we are going thru this loop,
                //
                if ( bStartNewDocOnCopies && ( dwRemainingCopies != (dwJobNumberOfCopies & 0x0000FFFF ) ) )
                {
                    //
                    // End the previous job and start a new one.
                    //
                    if (!GdiEndDocEMF(hSpoolHandle) || !GdiStartDocEMF(hSpoolHandle, &DocInfo) )
                    {
                        bStartDoc = FALSE;
                        goto CleanUp;
                    }
                }

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

               if (!PrintEMFSingleCopy(hSpoolHandle,
                                       hPrinterDC,
                                       pEMFAttr,
                                       pPageList,
                                       dwOptimization,
                                       pDevmode,
                                       pData)) {
                   goto CleanUp;
               }

            } //while

        } else {

           if (!PrintEMFSingleCopy(hSpoolHandle,
                                   hPrinterDC,
                                   pEMFAttr,
                                   pPageList,
                                   dwOptimization,
                                   pDevmode,
                                   pData)) {

               goto CleanUp;
           }
        }

    } __except (iThrowDriverException == 1 ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER) {

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

    bRetVal = TRUE;

CleanUp:

    //
    // Preserve the last error
    //
    LastError = bRetVal ? ERROR_SUCCESS : GetLastError();

    if ( bStartDoc ) {

/*++ Only for OSes after Windows 2003. IHV's may uncomment it if they are doing multiple start docs.
        if ( hPrinter && bMultipleStartDoc ) //hPrinter could be NULL if OpenPrinter failed.
        {
            SetJobW(hPrinter, pData->JobId, 0, NULL, JOB_CONTROL_RELEASE);
        }
--*/
        GdiEndDocEMF(hSpoolHandle);
    }

    SetLastError(LastError);

    return bRetVal;
}


/*++
Function Name
    GdiGetDevmodeForPagePvt

Function Description.
    In some cases, GDI's GdiGetDevmodeForPage returns a devmode
    that is based on an old format of devmode. e.g. Win3.1 format. The size of such a devmode
    can be smaller than the latest Devmode. This can lead to unpredictable issues.
    Also, sometimes the devmode returned is even smaller than Win3.1 format (due to possible
    corruption).
    This function is a wrapper around GDI's GdiGetDevmodeForPage and partially takes care of this
    situation by doing an extra checking for devmode.

Parameters:
            hSpoolHandle           -- the handle to the spool file
            dwPageNumber           -- the devmode related to this page number is requested.
            ppCurrDM               -- the devmode for the dwPageNumber is placed here.
            ppLastDM               -- devmode for dwPageNumber-1 is placed here. Can be NULL. (if n
ot NULL)

Return Values:  TRUE if a valid devmode was obtained from GDI
                FALSE otherwise
--*/

_Success_(return != FALSE)
BOOL GdiGetDevmodeForPagePvt(
    _In_            HANDLE              hSpoolHandle,
    _In_            DWORD               dwPageNumber,
    _Outptr_     PDEVMODEW           *ppCurrDM,
    _Outptr_opt_result_maybenull_ PDEVMODEW           *ppLastDM
  )
{
    if ( NULL == ppCurrDM )
    {
        return FALSE;
    }

    *ppCurrDM = NULL;

    if ( ppLastDM )
    {
        *ppLastDM = NULL;
    }

    if (!GdiGetDevmodeForPage(hSpoolHandle,
                              dwPageNumber,
                              ppCurrDM,
                              ppLastDM) )
    {
        ODS(("GdiGetDevmodeForPage failed\n"));
        return FALSE;
    }
    //
    // If GdiGetDevmodeForPage has succeeded, then *ppCurrDM should have valid values
    //
    // GDI guarantees that the size of the devmode is atleast dmSize+dmDriverExtra.
    // So we dont need to check for that. But we still need to check some other dependencies
    //
    //

    if ( NULL  == *ppCurrDM ||
         FALSE == BIsDevmodeOfLeastAcceptableSize (*ppCurrDM)
       )
    {
        return FALSE;
    }

    //
    // It is possible for GdiGetDevmodeForPage to return TRUE (i.e. success)
    // but still not fill in the *ppLastDM. So NULL *ppLastDM is not an error
    //

    if ( ppLastDM && *ppLastDM &&
         FALSE == BIsDevmodeOfLeastAcceptableSize (*ppLastDM)
       )
    {

        return FALSE;
    }

    return TRUE;
}


/*++
Function Name
    BIsDevmodeOfLeastAcceptableSize

Function Description.
Parameters:
    pdm  -- the pointer to the devmode.

Return Values:  TRUE if devmode is of least acceptable size.
                FALSE otherwise
--*/

BOOL BIsDevmodeOfLeastAcceptableSize(
    _In_ PDEVMODE pdm)
{
    if ( NULL == pdm )
    {
        return FALSE;
    }

    if ( IS_DMSIZE_VALID((pdm),dmYResolution) )
    {
        return TRUE;
    }
    return FALSE;
}

/*++
Function Name
    BPlayEmptyPages

Function Description.
    Plays empty logical pages. e.g. n-up = 4 but
    app plays only one page. So we'll play 3 empty pages so that printer thinks
    4 pages have been recieved.

Parameters:

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

BOOL BPlayEmptyPages(
        _In_  HANDLE                 hSpoolHandle,
        _In_  DWORD                  dwNumPages,
        _In_  DWORD                  dwOptimization)
{
    ULONG ulNPCtr = 0;

    for ( ulNPCtr = 0; ulNPCtr < dwNumPages; ulNPCtr++)
    {
        //
        // To play back an empty page, we need to call
        // GdiStartPageEMF and GdiEndPageEMF.
        //
        if (!GdiStartPageEMF(hSpoolHandle) || !GdiEndPageEMF(hSpoolHandle, dwOptimization))
        {
            ODS(("Start Page / EndPage failed\n"));
        }
    }

    return TRUE;
}


/*++
Function Name
    BUpdateAttributes

Function Description.
    Sometimes when orientation of pages change within the job, the x,y printable area changes.
    So we need to get the new values.
    Note: Currently this is used only to get the x,y resolution and x,y printable area.
    It may need to be expaned in the future.

Parameters:

    pEMFAttr : The Job Attributes.

Return Values:  TRUE if successful
                FALSE otherwise

Assumption:
    1. pEMFAttr will always be valid.
    2. GetDeviceCaps will not return random values. This is a good assumption because MSDN
       does not define any error return values.
--*/

BOOL
BUpdateAttributes(
    _In_     HDC                   hPrinterDC,
    _Inout_  PEMF_ATTRIBUTE_INFO   pEMFAttr)
{
    pEMFAttr->lXResolution = GetDeviceCaps(hPrinterDC, LOGPIXELSX)     ; //dpi in x direction.
    pEMFAttr->lYResolution = GetDeviceCaps(hPrinterDC, LOGPIXELSY)     ; //dpi in y direction.
    pEMFAttr->lXPrintArea  = GetDeviceCaps(hPrinterDC, DESKTOPHORZRES) ; //numpixels in printable area in x direction.
    pEMFAttr->lYPrintArea  = GetDeviceCaps(hPrinterDC, DESKTOPVERTRES) ; //numpixels in printable area in y direction

    pEMFAttr->lXPhyPage    = GetDeviceCaps(hPrinterDC, PHYSICALWIDTH)  ;
    pEMFAttr->lYPhyPage    = GetDeviceCaps(hPrinterDC, PHYSICALHEIGHT) ;

    return TRUE;
}

ENupDirection
ExtractNupDirectionFromAttributeInfo(
        _In_  PATTRIBUTE_INFO_4     pAttributeInfo,
        _In_  BOOL                  bBookletPrint
        )
{
    ENupDirection eNupDirection = kRightThenDown; // Default

    //
    // If doing booklet, ignore user specified n-up direction because
    // some directions invert the booklet edge.
    // e.g. If nup direction is kLeftThenDown(or kDownThenLeft)
    // and booklet edge is Left, the output comes as if booklet edge is right.
    //
    if ( !bBookletPrint )
    {
        switch (pAttributeInfo->dwNupDirection)
        {
            case DOWN_THEN_RIGHT:
                eNupDirection = kDownThenRight;
                break;

            case LEFT_THEN_DOWN:
                eNupDirection = kLeftThenDown;
                break;

            case DOWN_THEN_LEFT:
                eNupDirection = kDownThenLeft;
                break;

            case RIGHT_THEN_DOWN:
            case 0:
                break; // kRightThenDown already set

            default:
                ODS(("Unrecognized N-up Direction. Using default"));
                break;
        }
    }

    return eNupDirection;
}


EBookletMode
ExtractBookletModeFromAttributeInfo(
        _In_  PATTRIBUTE_INFO_4     pAttributeInfo,
        _In_  BOOL                  bBookletPrint
        )
{
    EBookletMode eBookletMode = kNoBooklet;

    if ( bBookletPrint )
    {
        switch ( pAttributeInfo->dwBookletFlags )
        {
            case BOOKLET_EDGE_LEFT:
                eBookletMode = kBookletLeftEdge;
                break;

            case BOOKLET_EDGE_RIGHT:
                eBookletMode = kBookletRightEdge;
                break;

            default:
                ODS(("Unrecognized Booklet Option. Using default i.e Booklet Left Edge."));
                eBookletMode = kBookletLeftEdge;
                break;
        } // end of switch
    }

    return eBookletMode;
}

DWORD
EnsureProperValuesForScalingPercent(
        _In_ DWORD dwScalingPercent
        )
{
#define LEAST_SCALING_PERCENT_ALLOWED 1
#define MAX_SCALING_PERCENT_ALLOWED   1000

    if ( 0 == dwScalingPercent )
    {
        dwScalingPercent = 100;
    }

    if ( dwScalingPercent > MAX_SCALING_PERCENT_ALLOWED )
    {
        dwScalingPercent = MAX_SCALING_PERCENT_ALLOWED;
    }

    if ( dwScalingPercent < LEAST_SCALING_PERCENT_ALLOWED )
    {
        dwScalingPercent = LEAST_SCALING_PERCENT_ALLOWED;
    }

    return dwScalingPercent;
}

VOID
ExtractScalingFactorFromAttributeInfo(
        _In_     const PATTRIBUTE_INFO_4 pAttributeInfo,
        _Inout_  PEMF_ATTRIBUTE_INFO   pEMFAttr
        )
{
    DWORD dwScalingPercentX = pAttributeInfo->dwScalingPercentX;
    DWORD dwScalingPercentY = pAttributeInfo->dwScalingPercentY;

    dwScalingPercentX = EnsureProperValuesForScalingPercent(dwScalingPercentX);
    dwScalingPercentY = EnsureProperValuesForScalingPercent(dwScalingPercentY);

    pEMFAttr->fScalingFactorX = (FLOAT)dwScalingPercentX/100;
    pEMFAttr->fScalingFactorY = (FLOAT)dwScalingPercentY/100;
}

/*++
Function Name:
    ExtractDuplexModeInformationFromAttributeInfo

Function Description:
    This function looks at the pAttributeInfo->dwDuplexFlags and accordingly
    fills in some values in the pEMFAttr structure.

Parameters:
    pAttributeInfo : ATTRIBUTE_INFO_4 that has been previously filled by call to RetrieveJobAttributes.
    pEMFAttr       : Print Processors local settings and state maintenance structure.


Return Values:  <Nothing>
--*/

VOID
ExtractDuplexModeInformationFromAttributeInfo(
        _In_     PATTRIBUTE_INFO_4     pAttributeInfo,
        _Inout_  PEMF_ATTRIBUTE_INFO   pEMFAttr
        )
{
    pEMFAttr->ePaperFace = kFaceDown_NewPageOver; //Default

    if ( pAttributeInfo->dwDuplexFlags & REVERSE_PAGES_FOR_REVERSE_DUPLEX )
    {
        pEMFAttr->ePaperFace = kFaceUp_NewPageUnder;
    }

    if ( pAttributeInfo->dwDuplexFlags & DONT_SEND_EXTRA_PAGES_FOR_DUPLEX )
    {
        pEMFAttr->dwDuplexModeFlags |=  DONT_SEND_EXTRA_PAGES_FOR_DUPLEX ;
    }
}


/*++
Function Name:
    PrintOneSheetPreDeterminedForDriverEMF

Function Description:

Parameters: hSpoolHandle              -- handle the spool file handle
            hPrinterDC                -- handle to the printer device context
            pEMFAttr                  --
            bDuplex                   -- flag to indicate duplex printing
            dwOptimization            -- optimization flags
            pbEmptyPageEncountered    -- whether we queried for a page number bigger than the number of pages in the EMF
            pDevmode                  -- devmode with resolution settings

Return Values:  TRUE  if success
                FALSE on failure
--*/
_Success_(return)
BOOL
PrintOneSheetPreDeterminedForDriverEMF (
    _In_      HANDLE                  hSpoolHandle,
    _In_      HDC                     hPrinterDC,
    _In_      PEMF_ATTRIBUTE_INFO     pEMFAttr,
    _In_      BOOL                    bDuplex,
    _In_      PPAGE_NUMBER            pHead,
    _In_      DWORD                   dwOptimization,
    _Out_opt_ LPBOOL                  pbEmptyPageEncountered,
    _In_      LPDEVMODE               pDevmode)

{
    HANDLE      hEMF                        = NULL;
    BOOL        BeSmart                     = FALSE;
    DWORD       dwPageType;
    DWORD       dwDrvNumberOfPagesPerSide = pEMFAttr->dwDrvNumberOfPagesPerSide;
    DWORD       dwSides                   = bDuplex ? 2 : 1;
    DWORD       dwNumberOfPagesPerSheet   = dwDrvNumberOfPagesPerSide * dwSides;
    DWORD       dwTotalNumberOfPages      = pEMFAttr->dwTotalNumberOfPages;
    BOOL        bRetVal                   = TRUE;
    BOOL        bNewDevmode               = FALSE;
    LPDEVMODEW  pCurrDM                   = NULL;
    BOOL        bAtleastOneEmptyPageFound = FALSE;
    BOOL        bReverseOrderPrinting     = pEMFAttr->bReverseOrderPrinting;
    DWORD       dwEmptyPagesToPrintBeforeNextValidPage = 0;

    for ( ; dwSides && NULL != pHead ; dwSides--, pHead = pHead->pNextSide )
    {
        PPAGE_NUMBER pHeadLogical = NULL;

        for (pHeadLogical = pHead;NULL != pHeadLogical; pHeadLogical = pHeadLogical->pNextLogPage)
        {
            DWORD dwPageNumber = pHeadLogical->dwPageNumber;
            //
            // if dwPageNumber is 0 or 0xFFFFFFFF, it means empty page.
            //
            dwPageNumber = pHeadLogical->dwPageNumber;

            if ( dwPageNumber == 0 )
            {
                dwPageNumber = 0xFFFFFFFF; //big number
            }

            if ((hEMF = GdiGetPageHandle(hSpoolHandle,
                                          dwPageNumber,
                                          &dwPageType)) == NULL)
            {
                if (GetLastError() == ERROR_NO_MORE_ITEMS) {
                     // End of the print job
                    dwEmptyPagesToPrintBeforeNextValidPage++;
                    bAtleastOneEmptyPageFound = TRUE;
                }
                else
                {
                    ODS(("GdiGetPageHandle failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                    bRetVal = FALSE;
                    goto CleanUp;
                }
            }


            // Suppose a job has 4 pages and we print 4 up in down then left configuration.
            // Since it is forward printing, we don't know in advance that there are only 4 pages
            // So we try to print page 7th (because it is down then left, the pages
            // are printed in 7,5,8,6 order). We would also try to print page 7 if the job
            // were a 5 page job. But difference is that in 5 page job, we can cycle
            // through all pages and print a sheet with only one page. But for a 4 page job,
            // we don't even want to print an empty sheet. So we try to take care of that situation
            // here.
            //

            if ( hEMF )
            {
                if ( dwEmptyPagesToPrintBeforeNextValidPage )
                {
                    if ( !BPlayEmptyPages(hSpoolHandle, dwEmptyPagesToPrintBeforeNextValidPage, dwOptimization) )
                    {
                        ODS(("BPlayEmptyPages returned error"));
                        bRetVal = FALSE;
                        goto CleanUp;
                    }
                    dwEmptyPagesToPrintBeforeNextValidPage = 0;
                }

                // Process new devmodes in the spool file that appear before this page
                if (!ResetDCForNewDevmode(hSpoolHandle,
                                      hPrinterDC,
                                      pEMFAttr,
                                      dwPageNumber,
                                      FALSE,        //Not within pages
                                      dwOptimization,
                                      &bNewDevmode,
                                      pDevmode,
                                      &pCurrDM)) {

                    bRetVal = FALSE;
                    goto CleanUp;
                }

                // Call StartPage for each new page
                if (!GdiStartPageEMF(hSpoolHandle))
                {
                    ODS(("StartPage failed\nPrinter %ws\n", pDevmode->dmDeviceName));
                    bRetVal = FALSE;
                    goto CleanUp;
                }

                if ( hEMF)
                {
                    if (!PlayEMFPage(hSpoolHandle,
                                     hPrinterDC,
                                     hEMF,
                                     pEMFAttr,
                                     1,
                                     EMF_DEGREE_90)) {

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

                // Call EndPage irrespective of whether we played any pages
                if ( !GdiEndPageEMF(hSpoolHandle, dwOptimization))
                {
                   ODS(("EndPage failed\n"));
                   bRetVal = FALSE;
                   goto CleanUp;
                }
            }
        } //for pHeadLogical i.e. Within a side.

        //
        // Now we've reached the end of the page. Assuming we are doing 6-up, we
        // may have sent only 3 pages to driver. But printer is expecting 6 pages, so
        // we send remaining 3. But we need to print those extra 3 pages only if we there are
        // multiple copies to be printed OR we are doing duplex OR numerous other conditions.
        //

        if ( 0 != dwEmptyPagesToPrintBeforeNextValidPage )
        {
            BOOL bPrintEmptyPages = FALSE;
            BOOL bCollate =  IS_DMSIZE_VALID(pDevmode, dmCollate)            &&
                             (pDevmode->dmCollate != DMCOLLATE_TRUE);

            //
            // If printing forward, if all the pages are empty, then no need to print them.
            // (because in forward printing, total number of pages are not known before hand
            //  so we may be called to print pages that don't exist).
            //
            if ( !bReverseOrderPrinting ) // Forward Printing.
            {
                if ( 1 == dwSides ) //i.e. 2nd of the 2 sides or 1st side of one sided printing.
                {
                    if ( dwNumberOfPagesPerSheet == dwEmptyPagesToPrintBeforeNextValidPage )
                    {
                        // All pages on this sheet are empty, so no need to print anything.
                    }
                    else
                    {

                        bPrintEmptyPages = TRUE;
                    }
                }
            }
            else //Reverse Printing.
            {
                //
                // If the document will fit on one physical page, then this variable will prevent
                // the printer from playing extra pages just to fill in one physical page
                // The exception is when the pages fit on a single physical 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 =  bCollate &&
                           (dwTotalNumberOfPages<=dwDrvNumberOfPagesPerSide);

                if ( !BeSmart)
                {
                    bPrintEmptyPages = TRUE;
                }
            }

            if ( TRUE == bPrintEmptyPages )
            {
                BPlayEmptyPages(hSpoolHandle, dwEmptyPagesToPrintBeforeNextValidPage, dwOptimization);
                dwEmptyPagesToPrintBeforeNextValidPage = 0;
            }
        }

    } // for pHead (Side)

    if ( pbEmptyPageEncountered)
    {
        *pbEmptyPageEncountered = bAtleastOneEmptyPageFound;
    }

CleanUp:
    return bRetVal;
}



/*++
Function Name:
    BAnyReasonNotToPrintBlankPage.

Function Description:
    Determines if we there is a reason to suppress blank page generation.
    Print Processor historically generated a blank page if required (e.g.
    odd page job on duplex). But now, in some cases, that blank page
    generation can be suppressed (e.g. if gpd says so AND some other conditions
    are satisfied). So this function looks at those things and determines
    whether blank page can be suppressed.


Parameters:



Return Values:  TRUE  if an generation of empty page can be suppressed.
                FALSE otherwise
--*/

BOOL
BAnyReasonNotToPrintBlankPage(
    _In_      PEMF_ATTRIBUTE_INFO     pEMFAttr,
    _In_      DWORD                   dwNumberOfPagesInJob
    )
{
    BOOL bReasonNotPrintBlank           = FALSE;   //no reason not to i.e. we should print blank

    if ( pEMFAttr->dwDuplexModeFlags   &  DONT_SEND_EXTRA_PAGES_FOR_DUPLEX &&
         pEMFAttr->dwJobNumberOfCopies <= pEMFAttr->dwDrvNumberOfCopies
       )
    {
        //
        // For multiple sheet print jobs (e.g. 3 page jobs requiring 2 sheets),
        // we need an extra blank page for reverse, but not for forward.
        //
        // For duplex jobs that fit on a single side, we don't need to generate extra empty page.
        //
        if (  FALSE                == pEMFAttr->bReverseOrderPrinting  ||
              dwNumberOfPagesInJob <= pEMFAttr->dwNumberOfPagesPerSide
           )
        {
            bReasonNotPrintBlank = TRUE; //found a reason not to print blank
        }
    }

    return bReasonNotPrintBlank;
}

/*++
Function Name:
    BIsEveryPageOnThisSideBlank

Function Description:
    This function determines if every page pointed out out by pLogicalPage is Blank.
    By Blank, we mean the page number (as indicated by the nodes in the
    linked list pLogicalPage) is more than the total number of pages in the valid job.
    By Blank, we DONT mean that the page as spooled by the application has no content.
    This function does not analyze the contents of the page.

Parameters:
    pHeadLogical         : The linked last where each node in the list has a certain page number.
    dwTotalNumberOfPages : The total number of pages in the job. This number is the number
                           of logical pages as spooled by the application for a single copy job.

Return Values:  TRUE  if each page is empty
                FALSE otherwise
--*/

BOOL
BIsEveryPageOnThisSideBlank(
    _In_  PPAGE_NUMBER  pHeadLogical,
    _In_  DWORD         dwTotalNumberOfPages)
{
    BOOL    bAllPagesBlank      = TRUE;
    DWORD   dwLogicalPageNumber = 0;

    for ( ; pHeadLogical; pHeadLogical = pHeadLogical->pNextLogPage )
    {
        dwLogicalPageNumber = pHeadLogical->dwPageNumber;

        if ( dwLogicalPageNumber > 0 && dwLogicalPageNumber <= dwTotalNumberOfPages )
        {
            bAllPagesBlank = FALSE;
            break;
        }
    }

    return bAllPagesBlank;
}

Our Services

  • What our customers say about us?

© 2011-2024 All Rights Reserved. Joya Systems. 4425 South Mopac Building II Suite 101 Austin, TX 78735 Tel: 800-DEV-KERNEL

Privacy Policy. Terms of use. Valid XHTML & CSS