/**************************************************************************\
*
* Copyright (c) 1999  Microsoft Corporation
*
* Module Name:
*
*   MetaWmf.cpp
*
* Abstract:
*
*   Methods for playing and recoloring a WMF.
*
* Created:
*
*   12/13/1999 DCurtis
*
\**************************************************************************/

#include "Precomp.hpp"
#include "MetaWmf.hpp"

#ifndef BI_CMYK     // from wingdip.h
#define BI_CMYK         10L
#define BI_CMYKRLE8     11L
#define BI_CMYKRLE4     12L
#endif

inline static BOOL
IsDwordAligned(
    VOID *      pointer
    )
{
    return (((ULONG_PTR)pointer & (sizeof(DWORD) - 1)) == 0);
}

inline static BOOL
IsPostscriptPrinter(
    HDC     hdc
    )
{
    // It is a PostScript printer if POSTSCRIPT_PASSTHROUGH or
    // POSTSCRIPT_IGNORE is available

    int iWant1 = POSTSCRIPT_PASSTHROUGH;
    int iWant2 = POSTSCRIPT_IGNORE;

    return ((Escape(hdc, QUERYESCSUPPORT, sizeof(iWant1), (LPCSTR)&iWant1, NULL) != 0) ||
            (Escape(hdc, QUERYESCSUPPORT, sizeof(iWant2), (LPCSTR)&iWant2, NULL) != 0));
}

// Some escapes apparently cause NT 3.51 to crash, so skip them
inline static BOOL
SkipEscape(
    INT     escapeCode
    )
{
    switch (escapeCode)
    {
    case GETPHYSPAGESIZE:       // 12
    case GETPRINTINGOFFSET:     // 13
    case GETSCALINGFACTOR:      // 14
    case BEGIN_PATH:            // 4096
    case CLIP_TO_PATH:          // 4097
    case END_PATH:              // 4098
        return TRUE;
    default:
        return FALSE;
    }
}

inline static BOOL
IsOfficeArtData(
    UINT                recordSize,
    const WORD *        recordData
    )
{
    return (recordData[0] == MFCOMMENT) &&
           (recordSize > 16) &&
           ((INT)recordSize >= (recordData[1] + 10)) &&
           (GpMemcmp(recordData + 2, "TNPPOA", 6) == 0);
}

// The structure which defines the contents of a comment for a WMF or PICT,
// for an EMF use GdiComment() and the "approved" format (see the Win32
// documentation) - this basically is the same except that it has a 4 byte
// kind field.  For a PICT this is the format of an ApplicationComment (kind
// 100).
#pragma pack(push, GDIP_pack, 2)
typedef struct
{
    ULONG       Signature;   // Identifes the comment writer.
    USHORT      Kind;        // Type of comment (writer specific)
    // Comment data follows here.

} WmfComment;

typedef struct
{
    WORD     lbStyle;
    COLORREF lbColor;
    SHORT    lbHatch;
} LOGBRUSH16;

typedef struct
{
    WORD     lopnStyle;
    POINTS   lopnWidth;
    COLORREF lopnColor;
} LOGPEN16;

typedef struct
{
    SHORT   bmType;
    SHORT   bmWidth;
    SHORT   bmHeight;
    SHORT   bmWidthBytes;
    BYTE    bmPlanes;
    BYTE    bmBitsPixel;
    LPBYTE  bmBits;
} BITMAP16;

typedef struct tagLOGFONT16
{
    SHORT     lfHeight;
    SHORT     lfWidth;
    SHORT     lfEscapement;
    SHORT     lfOrientation;
    SHORT     lfWeight;
    BYTE      lfItalic;
    BYTE      lfUnderline;
    BYTE      lfStrikeOut;
    BYTE      lfCharSet;
    BYTE      lfOutPrecision;
    BYTE      lfClipPrecision;
    BYTE      lfQuality;
    BYTE      lfPitchAndFamily;
    BYTE      lfFaceName[LF_FACESIZE];
} LOGFONT16;

#pragma pack(pop, GDIP_pack)

inline static const WmfComment UNALIGNED *
GetWmfComment(
    const WORD *        recordData,
    ULONG               signature,
    UINT                kind
    )
{
    // Assumes you've already checked that
    //     (wmfCommentRecord->rdFunction == META_ESCAPE &&
    //      wmfCommentRecord->rdParm[0] == MFCOMMENT)

    const WmfComment UNALIGNED * wmfComment = (const WmfComment UNALIGNED *)&(recordData[2]);
    if ((wmfComment->Signature == signature) && (wmfComment->Kind == kind))
    {
        return wmfComment;
    }
    return NULL;
}

inline static INT
GetDibByteWidth(
    INT     biWidth,
    INT     biPlanes,
    INT     biBitCount
    )
{
    return (((biWidth * biPlanes * biBitCount) + 31) & ~31) / 8;
}

inline static RGBQUAD UNALIGNED *
GetDibColorTable(
    BITMAPINFOHEADER UNALIGNED * dibInfo
    )
{
    return (RGBQUAD UNALIGNED *)(((BYTE *)dibInfo) + dibInfo->biSize);
}

static BYTE *
GetDibBits(
    BITMAPINFOHEADER UNALIGNED * dibInfo,
    UINT                         numPalEntries,
    UINT                         usage
    )
{
    ASSERT(dibInfo->biSize >= sizeof(BITMAPINFOHEADER));

    INT         colorSize = 0;

    if (numPalEntries > 0)
    {
        if ((usage == DIB_PAL_COLORS) &&
            (dibInfo->biCompression != BI_BITFIELDS) &&
            (dibInfo->biCompression != BI_CMYK))
        {
            // Make sure it is aligned
            colorSize = ((numPalEntries * sizeof(INT16)) + 3) & ~3;
        }
        else
        {
            colorSize = numPalEntries * sizeof(RGBQUAD);
        }
    }

    return ((BYTE *)GetDibColorTable(dibInfo)) + colorSize;
}

UINT
GetDibBitsSize(
    BITMAPINFOHEADER UNALIGNED *  dibInfo
    )
{
    // Check for PM-style DIB
    if (dibInfo->biSize >= sizeof(BITMAPINFOHEADER))
    {
        // not a core header

        if (dibInfo->biWidth > 0)   // can't handle negative width
        {
            if ((dibInfo->biCompression == BI_RGB) ||
                (dibInfo->biCompression == BI_BITFIELDS) ||
                (dibInfo->biCompression == BI_CMYK))
            {
                INT     posHeight = dibInfo->biHeight;

                if (posHeight < 0)
                {
                    posHeight = -posHeight;
                }
                return posHeight *
                       GetDibByteWidth(dibInfo->biWidth, dibInfo->biPlanes,
                                       dibInfo->biBitCount);
            }
            return dibInfo->biSizeImage;
        }
        WARNING(("0 or negative DIB width"));
        return 0;
    }
    else    // it is a PM-style DIB
    {
        BITMAPCOREHEADER UNALIGNED * coreDibInfo = (BITMAPCOREHEADER UNALIGNED *)dibInfo;

        // width and height must be > 0 for COREINFO dibs
        if ((coreDibInfo->bcWidth  > 0) &&
            (coreDibInfo->bcHeight > 0))
        {
            return coreDibInfo->bcHeight *
                   GetDibByteWidth(coreDibInfo->bcWidth,coreDibInfo->bcPlanes,
                                   coreDibInfo->bcBitCount);
        }
        WARNING(("0 or negative DIB width or height"));
        return 0;
    }
}

BOOL
GetDibNumPalEntries(
    BOOL        isWmfDib,
    UINT        biSize,
    UINT        biBitCount,
    UINT        biCompression,
    UINT        biClrUsed,
    UINT *      numPalEntries
    )
{
    UINT        maxPalEntries = 0;

    // Dibs in a WMF always have the bitfields for 16 and 32-bpp dibs.
    if (((biBitCount == 16) || (biBitCount == 32)) && isWmfDib)
    {
        biCompression = BI_BITFIELDS;
    }

    switch (biCompression)
    {
    case BI_BITFIELDS:
        //
        // Handle 16 and 32 bit per pel bitmaps.
        //

        switch (biBitCount)
        {
        case 16:
        case 32:
            break;
        default:
            WARNING(("BI_BITFIELDS not Valid for this biBitCount"));
            return FALSE;
        }

        if (biSize <= sizeof(BITMAPINFOHEADER))
        {
            biClrUsed = maxPalEntries = 3;
        }
        else
        {
            //
            // masks are part of BITMAPV4 and greater
            //

            biClrUsed = maxPalEntries = 0;
        }
        break;

    case BI_RGB:
        switch (biBitCount)
        {
        case 1:
        case 4:
        case 8:
            maxPalEntries = 1 << biBitCount;
            break;
        default:
            maxPalEntries = 0;

            switch (biBitCount)
            {
            case 16:
            case 24:
            case 32:
                break;
            default:
                WARNING(("Invalid biBitCount in BI_RGB"));
                return FALSE;
            }
        }
        break;

    case BI_CMYK:
        switch (biBitCount)
        {
        case 1:
        case 4:
        case 8:
            maxPalEntries = 1 << biBitCount;
            break;
        case 32:
            maxPalEntries = 0;
            break;
        default:
            WARNING(("Invalid biBitCount in BI_CMYK"));
            return FALSE;
        }
        break;

    case BI_RLE4:
    case BI_CMYKRLE4:
        if (biBitCount != 4)
        {
            WARNING(("Invalid biBitCount in BI_RLE4"));
            return FALSE;
        }

        maxPalEntries = 16;
        break;

    case BI_RLE8:
    case BI_CMYKRLE8:
        if (biBitCount != 8)
        {
            WARNING(("Invalid biBitCount in BI_RLE8"));
            return FALSE;
        }

        maxPalEntries = 256;
        break;

    case BI_JPEG:
    case BI_PNG:
        maxPalEntries = 0;
        break;

    default:
        WARNING(("Invalid biCompression"));
        return FALSE;
    }

    if (biClrUsed != 0)
    {
        if (biClrUsed <= maxPalEntries)
        {
            maxPalEntries = biClrUsed;
        }
    }

    *numPalEntries = maxPalEntries;
    return TRUE;
}

GdipHdcType
GetHdcType(
    HDC     hdc
    )
{
    GdipHdcType     hdcType = UnknownHdc;
    UINT            dcType  = GetDCType(hdc);

    switch (dcType)
    {
    case OBJ_DC:
        {
            INT technology = GetDeviceCaps(hdc, TECHNOLOGY);

            if (technology == DT_RASDISPLAY)
            {
                hdcType = ScreenHdc;
            }
            else if (technology == DT_RASPRINTER)
            {
                if (IsPostscriptPrinter(hdc))
                {
                    hdcType = PostscriptPrinterHdc;
                }
                else
                {
                    hdcType = PrinterHdc;
                }
            }
            else
            {
                WARNING(("Unknown HDC technology!"));
            }
        }
        break;

    case OBJ_MEMDC:
        hdcType = BitmapHdc;
        break;

    case OBJ_ENHMETADC:
        // When metafile spooling, the printer DC will be of type
        // OBJ_ENHMETADC on Win9x and NT4 (but not NT5 due to a fix
        // to NT bug 98810).  We need to do some more work to figure
        // out whether it's really a printer DC or a true metafile
        // DC:

        if (Globals::GdiIsMetaPrintDCFunction(hdc))
        {
            if (IsPostscriptPrinter(hdc))
            {
                hdcType = PostscriptPrinterHdc;
            }
            else
            {
                hdcType = PrinterHdc;
            }
        }
        else
        {
            hdcType = EmfHdc;
        }
        break;

    case OBJ_METADC:
        hdcType = WmfHdc;
        break;

    default:
        WARNING(("Unknown HDC type!"));
        break;
    }

    return hdcType;
}

DWORD
GetHdcBitmapBitsPixel(
    HDC hdc
    )
{
    // This function returns the number of bits per pixel for a bitmap DC.
    // On error, 0 is returned.

    ASSERT(GetDCType(hdc) == OBJ_MEMDC);

    HBITMAP hbm = (HBITMAP) GetCurrentObject(hdc, OBJ_BITMAP);

    if (hbm)
    {
        BITMAP bm;

        if (GetObjectA(hbm, sizeof(bm), &bm) >= sizeof(BITMAP))
        {
            return bm.bmBitsPixel;
        }
    }
    
    return 0;
}

MfEnumState::MfEnumState(
    HDC                 hdc,
    BOOL                externalEnumeration,
    InterpolationMode   interpolation,
    GpRecolor *         recolor,
    ColorAdjustType     adjustType,
    const RECT *        deviceRect,
    DpContext *         context
    )
{
    HdcType             = GetHdcType(hdc);
    Hdc                 = hdc;
    HandleTable         = NULL;
    NumObjects          = 0;
    CurrentPalette      = (HPALETTE)::GetStockObject(DEFAULT_PALETTE);
    SizeAllocedRecord   = 0;
    ModifiedRecordSize  = 0;
    SaveDcCount         = 0;
    BytesEnumerated     = 0;
    ExternalEnumeration = externalEnumeration;
    Recolor             = recolor;
    AdjustType          = adjustType;
    CurrentRecord       = NULL;
    ModifiedRecord      = NULL;
    AllocedRecord       = NULL;
    Interpolation       = interpolation;
    SaveDcVal           = SaveDC(hdc);
    FsmState            = MfFsmStart;
    GdiCentricMode      = FALSE;
    SoftekFilter        = FALSE;
    DefaultFont         = NULL;
    RopUsed             = FALSE;
    Context             = context;
    SrcCopyOnly         = FALSE;
    GpMemset(StockFonts, 0, sizeof(StockFonts[0]) * NUM_STOCK_FONTS);
    GpMemset(RecoloredStockHandle, 0, sizeof(HGDIOBJ) * NUM_STOCK_RECOLOR_OBJS);

    // See if we should halftone solid colors
    if ((IsScreen() && (::GetDeviceCaps(hdc, BITSPIXEL) == 8)) ||
        (IsBitmap() && (GetHdcBitmapBitsPixel(hdc) == 8)))
    {
        Is8Bpp = TRUE;
        EpPaletteMap    paletteMap(hdc);
        IsHalftonePalette = (paletteMap.IsValid() && (!paletteMap.IsVGAOnly()));
    }
    else
    {
        Is8Bpp = FALSE;
        IsHalftonePalette = FALSE;
    }

    // Since the transform can change as we are playing the metafile
    // convert the destrect into DeviceUnits. We will make sure to have an
    // identity matrix when we apply it.
    DestRectDevice = *deviceRect;
}

MfEnumState::~MfEnumState()
{
    // Delete all the true type fonts we created (first make sure they
    // are not selected into the Hdc.

    ::SelectObject(Hdc, GetStockObject(SYSTEM_FONT));

    if (DefaultFont)
    {
        DeleteObject(DefaultFont);
    }
    for (int i = 0; i < NUM_STOCK_FONTS; i++)
    {
        if (StockFonts[i] != NULL)
        {
            DeleteObject(StockFonts[i]);
        }
    }

    // Not necessary to delete NULL_BRUSH, NULL_PEN into HDC.  The subclasses
    // restore DC state which should dissolve any selections of these pen/brushes.
    for (int i = 0; i < NUM_STOCK_RECOLOR_OBJS; i++)
    {
        if (RecoloredStockHandle[i] != NULL)
        {
            DeleteObject(RecoloredStockHandle[i]);
        }
    }
}

WmfEnumState::WmfEnumState(
    HDC                 hdc,
    HMETAFILE           hWmf,
    BOOL                externalEnumeration,
    InterpolationMode   interpolation,
    const RECT *        dstRect,
    const RECT *        deviceRect,
    DpContext *         context,
    GpRecolor *         recolor,
    ColorAdjustType     adjustType
    )
    : MfEnumState(hdc, externalEnumeration, interpolation,
                  recolor, adjustType, deviceRect, context)
{
    if (IsValid())
    {
        IgnorePostscript = FALSE;
        BytesEnumerated  = sizeof(METAHEADER); // in WMF enumeration, we don't get header
        MetafileSize     = GetMetaFileBitsEx(hWmf, 0, NULL);
        FirstViewportExt = TRUE;
        FirstViewportOrg = TRUE;
        IsFirstRecord    = TRUE;

        // The bad thing about this is that if the metafile is being recolored,
        // the default pen, brush, text color, and background colors have NOT
        // been recolored.  So let's hope they're not really used.
        HpenSave   = (HPEN)  ::SelectObject(hdc, GetStockObject(BLACK_PEN));
        HbrushSave = (HBRUSH)::SelectObject(hdc, GetStockObject(WHITE_BRUSH));

        if (!Globals::IsNt)
        {
            HpaletteSave    = (HPALETTE)GetCurrentObject(hdc, OBJ_PAL);
            HfontSave       = (HFONT)   GetCurrentObject(hdc, OBJ_FONT);
            HbitmapSave     = (HBITMAP) GetCurrentObject(hdc, OBJ_BITMAP);
            HregionSave     = (HRGN)    GetCurrentObject(hdc, OBJ_REGION);
        }
        else
        {
            HpaletteSave    = NULL;
            HfontSave       = NULL;
            HbitmapSave     = NULL;
            HregionSave     = NULL;
        }

        // Make sure a few default values are set in the hdc
        ::SetTextAlign(hdc, 0);
        ::SetTextJustification(hdc, 0, 0);
        ::SetTextColor(hdc, RGB(0,0,0));
        TextColor = RGB(0,0,0);
        ::SetBkColor(hdc, RGB(255,255,255));
        BkColor = RGB(255,255,255);
        ::SetROP2(hdc, R2_COPYPEN);

        DstViewportOrg.x  = dstRect->left;
        DstViewportOrg.y  = dstRect->top;
        DstViewportExt.cx = dstRect->right - DstViewportOrg.x;
        DstViewportExt.cy = dstRect->bottom - DstViewportOrg.y;
    }
}

WmfEnumState::~WmfEnumState()
{
    // Turn POSTSCRIPT_IGNORE back off if we need to.
    if (IsPostscriptPrinter() && IgnorePostscript)
    {
        WORD    wOn = FALSE;
        ::Escape(Hdc, POSTSCRIPT_IGNORE, sizeof(wOn), (LPCSTR)&wOn, NULL);
    }

    // According to Office GEL, SaveDC/RestoreDC doesn't always restore
    // the brush and the pen correctly, so we have to do that ourselves.
    ::SelectObject(Hdc, HbrushSave);
    ::SelectObject(Hdc, HpenSave);

    GpFree(AllocedRecord);

    if (IsMetafile())
    {
        // Account for unbalanced SaveDC/RestoreDC pairs and
        // restore to the saveDC state
        ::RestoreDC(Hdc, SaveDcCount - 1);
    }
    else
    {
        ::RestoreDC(Hdc, SaveDcVal);
    }
}

VOID
WmfEnumState::EndRecord(
    )
{
    // We rely on the number of bytes enumerated to determine if
    // this is the last record of the WMF.
    if ((MetafileSize - BytesEnumerated) < SIZEOF_METARECORDHEADER)
    {
        if (!Globals::IsNt)
        {
            // GDI won't delete objects that are still selected, so
            // select out all WMF objects before proceeding.
            ::SelectObject(Hdc, HpenSave);
            ::SelectObject(Hdc, HbrushSave);
            ::SelectObject(Hdc, HpaletteSave);
            ::SelectObject(Hdc, HfontSave);
            ::SelectObject(Hdc, HbitmapSave);
            ::SelectObject(Hdc, HregionSave);

            INT     i;
            HANDLE  handle;

            if (HandleTable != NULL)
            {
                for (i = 0; i < NumObjects; i++)
                {
                    if ((handle = HandleTable->objectHandle[i]) != NULL)
                    {
                        ::DeleteObject(handle);
                        HandleTable->objectHandle[i] = NULL;
                    }
                }
            }
        }
    }
}

BOOL
WmfEnumState::PlayRecord(
    )
{
    const METARECORD *  recordToPlay = ModifiedWmfRecord;

    // See if we've modified the record
    if (recordToPlay == NULL)
    {
        // We haven't.  See if we have a valid current record
        if (CurrentWmfRecord != NULL)
        {
            recordToPlay = CurrentWmfRecord;
        }
        else
        {
            // we don't so we have to create one
            if (!CreateCopyOfCurrentRecord())
            {
                return FALSE;
            }
            recordToPlay = ModifiedWmfRecord;
        }
    }
    return PlayMetaFileRecord(Hdc, HandleTable, (METARECORD *)recordToPlay, NumObjects);
}

INT
MfEnumState::GetModifiedDibSize(
    BITMAPINFOHEADER UNALIGNED * dibInfo,
    UINT                         numPalEntries,
    UINT                         dibBitsSize,
    UINT &                       usage
    )
{
    ASSERT(dibInfo->biSize >= sizeof(BITMAPINFOHEADER));

    INT     byteWidth;
    INT     bitCount = dibInfo->biBitCount;

    if ((usage == DIB_PAL_COLORS) &&
        ((bitCount > 8) || (dibInfo->biCompression == BI_BITFIELDS)))
    {
        usage = DIB_RGB_COLORS;
    }

    if ((Recolor != NULL) || (usage == DIB_PAL_COLORS))
    {
        INT     biSize = dibInfo->biSize;

        if (bitCount > 8)
        {
            if ((dibInfo->biCompression != BI_RGB) &&
                (dibInfo->biCompression != BI_BITFIELDS))
            {
                return 0;    // don't handle compressed images
            }

            ASSERT((bitCount == 16) || (bitCount == 24) || (bitCount == 32));

            INT posHeight = dibInfo->biHeight;
            if (posHeight < 0)
            {
                posHeight = -posHeight;
            }

            // We have to recolor the object, so we will convert it to a
            // 24 bit image and send it down.
            // Even if we have less then 256 pixels, it's not worth palettizing
            // anymore because the palette will be the size of the image anyway
            // make sure that the bitmap is width is aligned
            // PERF: We could create a GpBitmap from the bitmap and recolor the
            // GpBitmap
            dibBitsSize = posHeight * (((dibInfo->biWidth * 3) + 3) & ~3);
            numPalEntries = 0;
            biSize = sizeof(BITMAPINFOHEADER);
        }
        else if ((numPalEntries == 0) ||
                 (dibInfo->biCompression == BI_CMYK) ||
                 (dibInfo->biCompression == BI_CMYKRLE4) ||
                 (dibInfo->biCompression == BI_CMYKRLE8))
        {
            return 0;    // don't handle CMYK images
        }
        usage = DIB_RGB_COLORS;
        return biSize + (numPalEntries * sizeof(RGBQUAD)) + dibBitsSize;
    }

    return 0;       // no modifications needed
}

inline static INT
GetMaskShift(
    INT     maskValue
    )
{
    ASSERT (maskValue != 0);

    INT     shift = 0;

    while (((maskValue & 1) == 0) && (shift < 24))
    {
        shift++;
        maskValue >>= 1;
    }
    return shift;
}

inline static INT
GetNumMaskBits(
    INT     maskValue
    )
{
    ASSERT ((maskValue & 1) != 0);

    INT     numBits = 0;

    while ((maskValue & 1) != 0)
    {
        numBits++;
        maskValue >>= 1;
    }
    return numBits;
}

VOID
MfEnumState::Modify16BppDib(
    INT               width,
    INT               posHeight,
    BYTE *            srcPixels,
    DWORD UNALIGNED * bitFields,
    BYTE *            dstPixels,
    ColorAdjustType   adjustType
    )
{
    INT     rMask       = 0x00007C00;   // same as GDI default
    INT     gMask       = 0x000003E0;
    INT     bMask       = 0x0000001F;
    INT     rMaskShift  = 10;
    INT     gMaskShift  = 5;
    INT     bMaskShift  = 0;
    INT     rNumBits    = 5;
    INT     gNumBits    = 5;
    INT     bNumBits    = 5;
    INT     rRightShift = 2;
    INT     gRightShift = 2;
    INT     bRightShift = 2;

    if (bitFields != NULL)
    {
        rMask       = (INT)((WORD)(*bitFields++));
        gMask       = (INT)((WORD)(*bitFields++));
        bMask       = (INT)((WORD)(*bitFields));
        rMaskShift  = GetMaskShift(rMask);
        gMaskShift  = GetMaskShift(gMask);
        bMaskShift  = GetMaskShift(bMask);
        rNumBits    = GetNumMaskBits(rMask >> rMaskShift);
        gNumBits    = GetNumMaskBits(gMask >> gMaskShift);
        bNumBits    = GetNumMaskBits(bMask >> bMaskShift);
        rRightShift = (rNumBits << 1) - 8;
        gRightShift = (gNumBits << 1) - 8;
        bRightShift = (bNumBits << 1) - 8;
    }

    INT         palIndex = 0;
    INT         pixel;
    INT         r, g, b;
    COLORREF    color;
    INT         w, h;
    INT         srcByteWidth = ((width * 2) + 3) & (~3);
    INT         dstByteWidth = ((width * 3) + 3) & (~3);

    for (h = 0; h < posHeight; h++)
    {
        for (w = 0; w < width; w++)
        {
            pixel = (INT)(((INT16 *)srcPixels)[w]);

            r = (pixel & rMask) >> rMaskShift;
            r = (r | (r << rNumBits)) >> rRightShift;

            g = (pixel & gMask) >> gMaskShift;
            g = (g | (g << gNumBits)) >> gRightShift;

            b = (pixel & bMask) >> bMaskShift;
            b = (b | (b << bNumBits)) >> bRightShift;

            color = ModifyColor(RGB(r, g, b), adjustType);

            dstPixels[3*w + 2] = GetRValue(color);
            dstPixels[3*w + 1] = GetGValue(color);
            dstPixels[3*w]     = GetBValue(color);
        }
        srcPixels += srcByteWidth;
        dstPixels += dstByteWidth;
    }
}

inline static INT
Get24BppColorIndex(
    INT     maskValue
    )
{
    switch(GetMaskShift(maskValue))
    {
    default:
        WARNING(("Invalid BitFields Mask"));
        // FALLTHRU

    case 0:
        return 0;
    case 8:
        return 1;
    case 16:
        return 2;
    }
}

VOID
MfEnumState::Modify24BppDib(
    INT               width,
    INT               posHeight,
    BYTE *            srcPixels,
    DWORD UNALIGNED * bitFields,
    BYTE *            dstPixels,
    ColorAdjustType   adjustType
    )
{
    INT     rIndex = 2;
    INT     gIndex = 1;
    INT     bIndex = 0;

    if (bitFields != NULL)
    {
        INT     rMask = (INT)((*bitFields++));
        INT     gMask = (INT)((*bitFields++));
        INT     bMask = (INT)((*bitFields));

        rIndex = Get24BppColorIndex(rMask);
        gIndex = Get24BppColorIndex(gMask);
        bIndex = Get24BppColorIndex(bMask);
    }

    INT         palIndex = 0;
    INT         r, g, b;
    COLORREF    color;
    INT         w, h;
    INT         srcByteWidth = ((width * 3) + 3) & (~3);
    INT         dstByteWidth = ((width * 3) + 3) & (~3);
    BYTE *      srcRaster = srcPixels;

    for (h = 0; h < posHeight; h++)
    {
        srcPixels = srcRaster;
        for (w = 0; w < width; w++)
        {
            r = srcPixels[rIndex];
            g = srcPixels[gIndex];
            b = srcPixels[bIndex];
            srcPixels += 3;

            color = ModifyColor(RGB(r, g, b), adjustType);

            dstPixels[3*w + 2] = GetRValue(color);
            dstPixels[3*w + 1] = GetGValue(color);
            dstPixels[3*w]     = GetBValue(color);
        }
        srcRaster += srcByteWidth;
        dstPixels += dstByteWidth;
    }
}

inline static INT
Get32BppColorIndex(
    INT     maskValue
    )
{
    switch(GetMaskShift(maskValue))
    {
    default:
        WARNING(("Invalid BitFields Mask"));
        // FALLTHRU

    case 0:
        return 0;
    case 8:
        return 1;
    case 16:
        return 2;
    case 24:
        return 3;
    }
}

VOID
MfEnumState::Modify32BppDib(
    INT               width,
    INT               posHeight,
    BYTE *            srcPixels,
    DWORD UNALIGNED * bitFields,
    BYTE *            dstPixels,
    ColorAdjustType   adjustType
    )
{
    INT     rIndex = 2;
    INT     gIndex = 1;
    INT     bIndex = 0;

    if (bitFields != NULL)
    {
        INT     rMask = (INT)((*bitFields++));
        INT     gMask = (INT)((*bitFields++));
        INT     bMask = (INT)((*bitFields));

        rIndex = Get32BppColorIndex(rMask);
        gIndex = Get32BppColorIndex(gMask);
        bIndex = Get32BppColorIndex(bMask);
    }

    INT         palIndex = 0;
    INT         r, g, b;
    COLORREF    color;
    INT         w, h;
    INT         dstByteWidth = ((width * 3) + 3) & (~3);

    for (h = 0; h < posHeight; h++)
    {
        for (w = 0; w < width; w++)
        {
            r = srcPixels[rIndex];
            g = srcPixels[gIndex];
            b = srcPixels[bIndex];
            srcPixels += 4;

            color = ModifyColor(RGB(r, g, b), adjustType);

            dstPixels[3*w + 2] = GetRValue(color);
            dstPixels[3*w + 1] = GetGValue(color);
            dstPixels[3*w]     = GetBValue(color);
        }
        dstPixels += dstByteWidth;
    }
}

VOID
MfEnumState::ModifyDib(
    UINT                          usage,
    BITMAPINFOHEADER UNALIGNED *  srcDibInfo,
    BYTE *                        srcBits,    // if NULL, it's a packed DIB
    BITMAPINFOHEADER UNALIGNED *  dstDibInfo,
    UINT                          numPalEntries,
    UINT                          srcDibBitsSize,
    ColorAdjustType               adjustType
    )
{
    INT      srcBitCount = srcDibInfo->biBitCount;
    BYTE *   srcPixels   = srcBits;
    COLORREF color;

    if (srcBitCount <= 8)
    {
        GpMemcpy(dstDibInfo, srcDibInfo, srcDibInfo->biSize);

        RGBQUAD UNALIGNED * srcRgb = GetDibColorTable(srcDibInfo);
        RGBQUAD UNALIGNED * dstRgb = GetDibColorTable(dstDibInfo);

        dstDibInfo->biClrUsed = numPalEntries;

        if ((usage == DIB_PAL_COLORS) &&
            (dstDibInfo->biCompression != BI_BITFIELDS))
        {
            WORD *      srcPal = (WORD *)srcRgb;

            if (srcPixels == NULL)
            {
                srcPixels = (BYTE *)(srcPal + ((numPalEntries + 1) & ~1)); // align
            }

            // Copy the Dib pixel data
            GpMemcpy(dstRgb + numPalEntries, srcPixels, srcDibBitsSize);

            // Modify the palette colors
            while (numPalEntries--)
            {
                color = ModifyColor(*srcPal++ | 0x01000000, adjustType);
                dstRgb->rgbRed      = GetRValue(color);
                dstRgb->rgbGreen    = GetGValue(color);
                dstRgb->rgbBlue     = GetBValue(color);
                dstRgb->rgbReserved = 0;
                dstRgb++;
            }
        }
        else
        {
            if (srcPixels == NULL)
            {
                srcPixels = (BYTE *)(srcRgb + numPalEntries);
            }

            // Copy the Dib pixel data
            GpMemcpy(dstRgb + numPalEntries, srcPixels, srcDibBitsSize);

            // Modify the palette colors
            while (numPalEntries--)
            {
                color = ModifyColor(RGB(srcRgb->rgbRed, srcRgb->rgbGreen, srcRgb->rgbBlue), adjustType);
                dstRgb->rgbRed      = GetRValue(color);
                dstRgb->rgbGreen    = GetGValue(color);
                dstRgb->rgbBlue     = GetBValue(color);
                dstRgb->rgbReserved = 0;
                dstRgb++;
                srcRgb++;
            }
        }
    }
    else    // Recolor the bitmap. There is no need to palettize the image since
            // the palette will be as big as the image
    {
        INT posHeight = srcDibInfo->biHeight;

        if (posHeight < 0)
        {
            posHeight = -posHeight;
        }

        ASSERT((srcDibInfo->biCompression == BI_RGB) ||
               (srcDibInfo->biCompression == BI_BITFIELDS));

        GpMemset(dstDibInfo, 0, sizeof(BITMAPINFOHEADER));

        dstDibInfo->biSize     = sizeof(BITMAPINFOHEADER);
        dstDibInfo->biWidth    = srcDibInfo->biWidth;
        dstDibInfo->biHeight   = srcDibInfo->biHeight;
        dstDibInfo->biPlanes   = 1;
        dstDibInfo->biBitCount = 24;

        BYTE *                dstPixels = GetDibBits(dstDibInfo,0,0);
        DWORD UNALIGNED *     bitFields = NULL;

        if (srcPixels == NULL)
        {
            srcPixels = (BYTE *)GetDibBits(srcDibInfo, numPalEntries, usage);
        }

        dstDibInfo->biClrUsed = 0;
        dstDibInfo->biClrImportant = 0;

        if (numPalEntries == 3)
        {
            ASSERT((srcBitCount == 16) || (srcBitCount == 32));
            bitFields = (DWORD*) GetDibColorTable(srcDibInfo);
            if ((bitFields[0] == 0) ||
                (bitFields[1] == 0) ||
                (bitFields[2] == 0))
            {
                bitFields = NULL;
            }
        }
        else if (srcDibInfo->biSize >= sizeof(BITMAPV4HEADER))
        {
            BITMAPV4HEADER *    srcHeaderV4 = (BITMAPV4HEADER *)srcDibInfo;

            if ((srcHeaderV4->bV4RedMask != 0) &&
                (srcHeaderV4->bV4GreenMask != 0) &&
                (srcHeaderV4->bV4BlueMask != 0))
            {
                bitFields = &(srcHeaderV4->bV4RedMask);
            }
        }

        switch (srcBitCount)
        {
        case 16:
            Modify16BppDib(srcDibInfo->biWidth, posHeight, srcPixels,
                           bitFields, dstPixels, adjustType);
            break;
        case 24:
            Modify24BppDib(srcDibInfo->biWidth, posHeight, srcPixels,
                           bitFields, dstPixels, adjustType);
            break;
        case 32:
            Modify32BppDib(srcDibInfo->biWidth, posHeight, srcPixels,
                           bitFields, dstPixels, adjustType);
            break;
        }
    }
}

VOID
WmfEnumState::DibCreatePatternBrush(
    )
{
    INT                           style      = (INT)((INT16)(((WORD *)RecordData)[0]));
    UINT                          usage      = (UINT)((UINT16)(((WORD *)RecordData)[1]));
    BITMAPINFOHEADER UNALIGNED *  srcDibInfo = (BITMAPINFOHEADER UNALIGNED *)(&(((WORD *)RecordData)[2]));
    UINT                          numPalEntries;

    // Pattern brush should mean that it is a monochrome DIB
    if (style == BS_PATTERN)
    {
        if (Recolor != NULL)
        {
            DWORD UNALIGNED *   rgb = (DWORD UNALIGNED *)GetDibColorTable(srcDibInfo);

            // See if it is a monochrome pattern brush.  If it is, then
            // the text color will be used for 0 bits and the background
            // color will be used for 1 bits.  These colors are already
            // modified by their respective records, so there is no need
            // to do anything here.

            // If it is not a monochrome pattern brush, just create a
            // solid black brush.
            if ((usage != DIB_RGB_COLORS) ||
                (srcDibInfo->biSize < sizeof(BITMAPINFOHEADER)) ||
                !GetDibNumPalEntries(TRUE,
                                     srcDibInfo->biSize,
                                     srcDibInfo->biBitCount,
                                     srcDibInfo->biCompression,
                                     srcDibInfo->biClrUsed,
                                     &numPalEntries) ||
                (numPalEntries != 2) ||
                (srcDibInfo->biBitCount != 1) || (srcDibInfo->biPlanes != 1) ||
                (rgb[0] != 0x00000000) || (rgb[1] != 0x00FFFFFF))
            {
                // This shouldn't happen, at least not if recorded on NT
                WARNING(("Non-monochrome pattern brush"));
                MakeSolidBlackBrush();
            }
        }
    }
    else
    {
        UINT    dibBitsSize;

        if ((srcDibInfo->biSize >= sizeof(BITMAPINFOHEADER)) &&
            GetDibNumPalEntries(TRUE,
                                srcDibInfo->biSize,
                                srcDibInfo->biBitCount,
                                srcDibInfo->biCompression,
                                srcDibInfo->biClrUsed,
                                &numPalEntries) &&
            ((dibBitsSize = GetDibBitsSize(srcDibInfo)) > 0))
        {
            UINT    oldUsage = usage;
            INT     dstDibSize = GetModifiedDibSize(srcDibInfo, numPalEntries, dibBitsSize, usage);

            if (dstDibSize > 0)
            {
                INT     size = SIZEOF_METARECORDHEADER + (2 * sizeof(WORD)) + dstDibSize;

                CreateRecordToModify(size);
                ModifiedWmfRecord->rdSize      = size / 2;
                ModifiedWmfRecord->rdFunction  = META_DIBCREATEPATTERNBRUSH;
                ModifiedWmfRecord->rdParm[0]   = BS_DIBPATTERN;
                ModifiedWmfRecord->rdParm[1]   = DIB_RGB_COLORS;
                ModifyDib(oldUsage, srcDibInfo, NULL,
                          (BITMAPINFOHEADER UNALIGNED *)(&(ModifiedWmfRecord->rdParm[2])),
                          numPalEntries, dibBitsSize, ColorAdjustTypeBrush);
            }
        }
    }

    this->PlayRecord();
}

// This record is obsolete, because it uses a compatible bitmap
// instead of a DIB.  It has a BITMAP16 structure that is
// used to call CreateBitmapIndirect.  That HBITMAP is, in turn,
// used to call CreatePatternBrush.  If this record is present,
// it is likely that the bitmap is monochrome, in which case
// the TextColor and the BkColor will be used, and these colors
// already get modified by their respective records.
VOID
WmfEnumState::CreatePatternBrush(
    )
{
    WARNING(("Obsolete META_CREATEPATTERNBRUSH record"));

    BITMAP16 UNALIGNED *  bitmap = (BITMAP16 UNALIGNED *)RecordData;

    if (bitmap->bmBitsPixel != 1)
    {
        WARNING(("Non-monochrome pattern brush"));
        MakeSolidBlackBrush();
    }

    this->PlayRecord();
}

VOID
WmfEnumState::CreatePenIndirect(
    )
{
    LOGPEN16 UNALIGNED * logPen = (LOGPEN16 UNALIGNED *)RecordData;

    switch (logPen->lopnStyle)
    {
    default:
        WARNING(("Unrecognized Pen Style"));
    case PS_NULL:
        break;      // leave the pen alone

    case PS_SOLID:
    case PS_INSIDEFRAME:
    case PS_DASH:
    case PS_DOT:
    case PS_DASHDOT:
    case PS_DASHDOTDOT:
        ModifyRecordColor(3, ColorAdjustTypePen);
        break;
    }

    this->PlayRecord();
}

VOID
WmfEnumState::CreateBrushIndirect(
    )
{
    LOGBRUSH16 UNALIGNED * logBrush = (LOGBRUSH16 UNALIGNED *)RecordData;

    switch (logBrush->lbStyle)
    {
    case BS_SOLID:
    case BS_HATCHED:
        {
            ModifyRecordColor(1, ColorAdjustTypeBrush);
            if (ModifiedWmfRecord != NULL)
            {
                logBrush = (LOGBRUSH16 UNALIGNED *)(ModifiedWmfRecord->rdParm);
            }
            // See if we need to halftone the color.  We do if it is a solid
            // color, and we have a halftone palette, and the color is not
            // an exact match in the palette.

            COLORREF    color;

            if (IsHalftonePalette && (logBrush->lbStyle == BS_SOLID) &&
                (((color = logBrush->lbColor) & 0x02000000) == 0))
            {
                // create a halftone brush, instead of a solid brush

                INT     size = SIZEOF_METARECORDHEADER + (2 * sizeof(WORD)) +
                               sizeof(BITMAPINFOHEADER) + // DIB 8 bpp header
                               (8 * sizeof(RGBQUAD)) +    // DIB 8 colors
                               (8 * 8);                   // DIB 8x8 pixels

                ModifiedRecordSize = 0; // in case we already modified the record
                CreateRecordToModify(size);
                ModifiedWmfRecord->rdSize      = size / 2;
                ModifiedWmfRecord->rdFunction  = META_DIBCREATEPATTERNBRUSH;
                ModifiedWmfRecord->rdParm[0]   = BS_DIBPATTERN;
                ModifiedWmfRecord->rdParm[1]   = DIB_RGB_COLORS;

                HalftoneColorRef_216(color, &(ModifiedWmfRecord->rdParm[2]));
            }
        }
        break;

    case BS_HOLLOW:
        break;  // leave the record alone

    default:
        // Looking at the NT source code, there shouldn't be any
        // other brush styles for an indirect brush.
        WARNING(("Brush Style Not Valid"));
        MakeSolidBlackBrush();
        break;
    }

    this->PlayRecord();
}

// Also handles StretchBlt.
// These records are obsolete (when there is a source bitmap) because they
// have a compatible bitmap instead of a DIB.  For that reason, we don't
// recolor them.
VOID
WmfEnumState::BitBlt(
    )
{
    DWORD   rop = *((UNALIGNED DWORD *)RecordData);

    // If No-Op ROP, do nothing; just return
    if ((rop & 0xFFFF0000) == (GDIP_NOOP_ROP3 & 0xFFFF0000))
    {
        return;
    }

    if (!IsMetafile())
    {
        if (IsSourceInRop3(rop))
        {
            WARNING(("Obsolete META_BITBLT/META_STRETCHBLT record"));

            if ((rop != SRCCOPY) && SrcCopyOnly &&
                CreateCopyOfCurrentRecord())
            {
                *((DWORD UNALIGNED *)ModifiedWmfRecord->rdParm) = SRCCOPY;
            }
        }
        else
        {
            if ((rop != PATCOPY) && SrcCopyOnly &&
                CreateCopyOfCurrentRecord())
            {
                *((DWORD UNALIGNED *)ModifiedWmfRecord->rdParm) = PATCOPY;
            }
        }
    }

    this->PlayRecord();
}

VOID
WmfEnumState::Escape(
    )
{
    INT     escapeCode = (INT)((INT16)(((WORD *)RecordData)[0]));

    if (!IsPostscript())
    {
        if (SkipEscape(escapeCode))
        {
            return;
        }

        // Skip Office Art data when playing into another metafile
        if (IsMetafile() &&
            IsOfficeArtData(GetCurrentRecordSize(), (WORD *)RecordData))
        {
            return;
        }
    }
    else // it is postscript
    {
        if (escapeCode == MFCOMMENT)
        {
            if (GetWmfComment((WORD *)RecordData, msosignature, msocommentBeginSrcCopy))
            {
                SrcCopyOnly = TRUE;
                return;
            }
            if (GetWmfComment((WORD *)RecordData, msosignature, msocommentEndSrcCopy))
            {
                SrcCopyOnly = FALSE;
                return;
            }
        }

        if (escapeCode == POSTSCRIPT_DATA)
        {
            // Bug #98743 (Windows Bugs) Gdiplus must overcome GDI limitation
            // with POSTSCRIPT_INJECTION.  Comments from Rammanohar Arumugam:
            //
            // Being in xx-centric mode means POSTSCRIPT_DATA won't work. I
            // take that to mean that PlayMetaFileRecord only works in
            // compatibility mode.
            //
            // GdiPlus will check for the printer mode. In GDI-centric and
            // Postscript-centric mode, it will not do PlayMetaFileRecord for
            // any record that has POSTSCRIPT_DATA. Instead, it will output
            // the postscript data through a PASSTHRU (for GDI-centric mode)
            // or a POSTSCRIPT_PASSTHRU (for Postscript-Centric mode).
            //
            // You can find out the mode by querying the escape support.
            // 1. Query for POSTSCRIPT_INJECTION support. If not supported,
            // it's compat mode. If supported, find out the mode by doing step 2/3
            // 2. Query for PASSTHROUGH support. If supported, it's GDI-centric.
            // 3. Query for POSTSCRIPT_PASSTHROUGH support. If supported, it's
            // PS-centric.

            if (Globals::IsNt)
            {
                if (!SoftekFilter)
                {
                    // Determine presence of Softek Filter EPS, if so, then
                    // we apply workaround patches.

                    WORD size = *((WORD*)RecordData);
                    LPSTR escape = (LPSTR)(&RecordData[6]);
                    const LPSTR softekString = "%MSEPS Preamble [Softek";

                    INT softekLen = strlen(softekString);

                    if (size >= softekLen)
                    {
                        SoftekFilter = !GpMemcmp(softekString, escape, softekLen);
                    }
                }

                DWORD EscapeValue = POSTSCRIPT_IDENTIFY;

                if (::ExtEscape(Hdc,
                              QUERYESCSUPPORT,
                              sizeof(DWORD),
                              (LPSTR)&EscapeValue,
                              0,
                              NULL) <= 0)
                {
                    // POSTSCRIPT_IDENTITY is not supported if the mode has
                    // been set because it can only be set once.

                    EscapeValue = POSTSCRIPT_PASSTHROUGH;
                    if (::ExtEscape(Hdc,
                                  QUERYESCSUPPORT,
                                  sizeof(DWORD),
                                  (LPSTR)&EscapeValue,
                                  0,
                                  NULL) <= 0)
                    {
                        // GDI-centric mode
                        if (CreateCopyOfCurrentRecord())
                        {
                            *((WORD *)ModifiedWmfRecord->rdParm) = PASSTHROUGH;
                        }
                        GdiCentricMode = TRUE;
                    }
                    else
                    {
                        // PS-centric mode
                        if (CreateCopyOfCurrentRecord())
                        {
                            *((WORD *)ModifiedWmfRecord->rdParm) = POSTSCRIPT_PASSTHROUGH;
                        }
                    }

                    this->PlayRecord();
                    return;
                }
                else
                {
                    // compatibility mode, uses POSTSCRIPT_DATA
                }
            }
            else
            {
                // Win98 doesn't distinguish between GDI & compatibility mode
                if (CreateCopyOfCurrentRecord())
                {
                    *((WORD *)ModifiedWmfRecord->rdParm) = PASSTHROUGH;
                }
            }
        }
    }

    // Keep track of the POSTSCRIPT_IGNORE state.  If it is still on at
    // the of the metafile, then turn it OFF
    if (escapeCode == POSTSCRIPT_IGNORE && IsPostscript())
    {
        IgnorePostscript = ((WORD *)RecordData)[2] ? TRUE : FALSE;
    }
    this->PlayRecord();
}

VOID
WmfEnumState::Rectangle(
    )
{
    if (FsmState == MfFsmSetROP)
    {
        // There is a bug using PlayMetaFileRecord on Win2K for this
        // type of escape, we must explicitly call ExtEscape.  See bug
        // #98743.

        WORD* rdParm = (WORD*)&(RecordData[0]);
        CHAR postscriptEscape[512];

        RECT rect;
        rect.left = (SHORT)rdParm[3];
        rect.top = (SHORT)rdParm[2];
        rect.right = (SHORT)rdParm[1];
        rect.bottom = (SHORT)rdParm[0];

        if (LPtoDP(Hdc, (POINT*)&rect, 2))
        {
            // Some injected postscript, strangely enough contains the equivalent
            // of a stroke which is erroroneously executed on the current path.  In
            // one case, bug #281856 it results in a border about the object.  To
            // get around this, we output a 'N' which is newpath operator if it's
            // defined (which should be always.)  This, incidently, is done when
            // calling GDI Rectangle() succeeds, it outputs "N x y w h B", so we
            // are doing the equivalent here.

            GpRect  clipRect;
            Context->VisibleClip.GetBounds(&clipRect);

            wsprintfA(&postscriptEscape[2],
                      "\r\n%d %d %d %d CB\r\n"
                      "%s"
                      "%d %d %d %d B\r\n",
                      clipRect.Width,
                      clipRect.Height,
                      clipRect.X,
                      clipRect.Y,
                      Globals::IsNt ? "newpath\r\n" : "",
                      rect.right - rect.left,
                      rect.bottom - rect.top,
                      rect.left,
                      rect.top);
            ASSERT(strlen(&postscriptEscape[2]) < 512);
            *(WORD*)(&postscriptEscape[0]) = (WORD)(strlen(&postscriptEscape[2]));

            ::ExtEscape(Hdc,
                        PASSTHROUGH,
                        *(WORD*)(&postscriptEscape[0]) + sizeof(WORD) + 1,
                        (CHAR*)&postscriptEscape[0],
                        0,
                        NULL);
            return;
        }
    }

    this->PlayRecord();
}

VOID
WmfEnumState::RestoreHdc(
    )
{
    INT     relativeCount = (INT)((INT16)(((WORD *)RecordData)[0]));

    if (SaveDcCount < 0)
    {
        if (relativeCount >= SaveDcCount)
        {
            if (relativeCount >= 0)
            {
                // Modify the record
                CreateCopyOfCurrentRecord();    // guaranteed to succeed
                relativeCount = -1;
                ModifiedWmfRecord->rdParm[0] = (INT16)(-1);
            }
        }
        else
        {
            // Modify the record
            CreateCopyOfCurrentRecord();    // guaranteed to succeed
            relativeCount = SaveDcCount;
            ModifiedWmfRecord->rdParm[0] = (INT16)(relativeCount);
        }

        SaveDcCount -= relativeCount;
        this->PlayRecord();
    }
    else
    {
        WARNING(("RestoreDC not matched to a SaveDC"));
    }
}

// The rop for this command is always SRCCOPY
VOID
WmfEnumState::SetDIBitsToDevice(
    )
{
    // !!!

    // Office doesn't do anything with this record.  For now, I don't think
    // I will either.  It's a tough one to deal with for a couple reasons:
    // 1st -    The xDest and yDest values are in world units, but the
    //          width and height values are in device units
    //          (unlike StretchDIBits).
    // 2nd -    The amount of bits data present may be different than
    //          what is in the DIB header (based on the cScanLines param).
    //          This makes it harder to deal with as a packed DIB.

    this->PlayRecord();
}

VOID
MfEnumState::SelectPalette(
    INT     objectIndex
    )
{
    // For EMF the check really should be > 0
    if ((objectIndex >= 0) && (objectIndex < NumObjects) && (HandleTable != NULL))
    {
        HGDIOBJ    hPal = HandleTable->objectHandle[objectIndex];
        if ((hPal != NULL) && (GetObjectTypeInternal(hPal) == OBJ_PAL))
        {
            CurrentPalette = (HPALETTE)hPal;
            return;
        }
    }
    WARNING(("SelectPalette Failure"));
}

inline static VOID
Point32FromPoint16(
    POINTL *    dstPoints,
    POINTS UNALIGNED * srcPoints,
    UINT        numPoints
    )
{
    for (UINT i = 0; i < numPoints; i++, dstPoints++, srcPoints++)
    {
        dstPoints->x = (INT)((INT16)(srcPoints->x));
        dstPoints->y = (INT)((INT16)(srcPoints->y));
    }
}

// Apparently there is a bug on Win9x with PolyPolygons, so we
// parse the record ourselves.
VOID
WmfEnumState::PolyPolygon(
    )
{
    UINT        numPolygons = ((WORD *)RecordData)[0];
    UINT        numPoints   = 0;
    UINT        i;

    for (i = 0; i < numPolygons; i++)
    {
        numPoints += ((LPWORD)&((WORD *)RecordData)[1])[i];
    }

    INT *       polyCounts;
    POINTL *    points;
    INT         size = (numPolygons * sizeof(INT)) +
                       (numPoints * sizeof(POINTL));

    if (CreateRecordToModify(size))
    {
        polyCounts = (INT *)ModifiedRecord;
        points = (POINTL *)(polyCounts + numPolygons);

        for (i = 0; i < numPolygons; i++)
        {
            polyCounts[i] = (INT)(UINT)((LPWORD)&((WORD *)RecordData)[1])[i];
        }
        Point32FromPoint16(points,
                           (POINTS UNALIGNED *)(((WORD *)RecordData) + numPolygons + 1),
                           numPoints);
        ::PolyPolygon(Hdc, (POINT *)points, polyCounts, numPolygons);
        return;
    }

    this->PlayRecord();
}

#ifdef NEED_TO_KNOW_IF_BITMAP
static INT
GetBppFromMemDC(
    HDC     hMemDC
    )
{
    HBITMAP     hBitmap = (HBITMAP)::GetCurrentObject(hMemDC, OBJ_BITMAP);
    BITMAP      bitmap;

    if ((hBitmap == NULL) ||
        (::GetObjectA(hBitmap, sizeof(bitmap), &bitmap) == 0))
    {
        WARNING(("Couldn't get Bitmap object"));
        return 0;   // error
    }

    if (bitmap.bmPlanes <= 0)
    {
        WARNING(("Bitmap with no planes"));
        bitmap.bmPlanes = 1;
    }

    INT     bpp = bitmap.bmPlanes * bitmap.bmBitsPixel;

    if (bpp > 32)
    {
        WARNING(("Bitmap with too many bits"));
        bpp = 32;
    }

    return bpp;
}
#endif

#define GDI_INTERPOLATION_MAX   (1 << 23)

VOID
MfEnumState::OutputDIB(
    HDC                          hdc,
    const RECTL *                bounds,
    INT                          dstX,
    INT                          dstY,
    INT                          dstWidth,
    INT                          dstHeight,
    INT                          srcX,
    INT                          srcY,
    INT                          srcWidth,
    INT                          srcHeight,
    BITMAPINFOHEADER UNALIGNED * dibInfo,
    BYTE *                       bits,   // if NULL, this is a packed DIB
    UINT                         usage,
    DWORD                        rop,
    BOOL                         isWmfDib
    )
{
    BITMAPINFO       dibHeaderBuffer[1]; // To be sure it's aligned for 64Bits

    BOOL             restoreColors = FALSE;
    COLORREF         oldBkColor;
    COLORREF         oldTextColor;

    ASSERT(dibInfo->biSize >= sizeof(BITMAPINFOHEADER));

    if (bits == NULL)
    {
        UINT        numPalEntries;

        if (GetDibNumPalEntries(isWmfDib,
                                dibInfo->biSize,
                                dibInfo->biBitCount,
                                dibInfo->biCompression,
                                dibInfo->biClrUsed,
                                &numPalEntries))
        {
            bits = GetDibBits(dibInfo, numPalEntries, usage);
        }
        else
        {
            WARNING(("GetDibNumPalEntries failure"));
            return;
        }
    }


    INT     posDstWidth = dstWidth;

    if (posDstWidth < 0)
    {
        posDstWidth = -posDstWidth;
    }

    INT     posDstHeight = dstHeight;

    if (posDstHeight < 0)
    {
        posDstHeight = -posDstHeight;
    }

    INT             stretchBltMode = HALFTONE;
    GpBitmap *      destBitmap     = NULL;
    POINT           destPoints[3];
    BitmapData      bmpData;
    BitmapData *    bmpDataPtr = NULL;
    HBITMAP         hBitmap = NULL;
    BYTE *          bmpBits = NULL;
    BITMAPINFO *    dibBmpInfo = NULL;
    BOOL            deleteDIBSection = FALSE;

    // Don't use GDI+ stretching for a mask
    // Make this the first thing so that we are sure that they are set
    if ((dibInfo->biBitCount == 1) && (rop != SRCCOPY))
    {
        oldBkColor = ::SetBkColor(hdc, BkColor);
        oldTextColor = ::SetTextColor(hdc, TextColor);
        restoreColors = TRUE;
        goto DoGdiStretch;
    }

    // On Win9x we need to create an play a comment record so that the transform
    // gets invalidated and recalculated
    if (!Globals::IsNt && !IsMetafile())
    {
        CreateAndPlayCommentRecord();
    }

    destPoints[0].x = dstX;
    destPoints[0].y = dstY;
    destPoints[1].x = dstX + posDstWidth;
    destPoints[1].y = dstY;
    destPoints[2].x = dstX;
    destPoints[2].y = dstY + posDstHeight;

    if (!::LPtoDP(hdc, destPoints, 3))
    {
        goto DoGdiStretch;
    }

    posDstWidth  = ::GetIntDistance(destPoints[0], destPoints[1]);
    posDstHeight = ::GetIntDistance(destPoints[0], destPoints[2]);

    if ((posDstWidth == 0) || (posDstHeight == 0))
    {
        return;
    }

    INT     posSrcWidth;
    INT     srcWidthSign;

    posSrcWidth  = srcWidth;
    srcWidthSign = 1;

    if (posSrcWidth < 0)
    {
        posSrcWidth  = -posSrcWidth;
        srcWidthSign = -1;
    }

    INT     posSrcHeight;
    INT     srcHeightSign;

    posSrcHeight  = srcHeight;
    srcHeightSign = 1;

    if (posSrcHeight < 0)
    {
        posSrcHeight  = -posSrcHeight;
        srcHeightSign = -1;
    }

    INT     posSrcDibWidth;

    posSrcDibWidth = dibInfo->biWidth;

    if (posSrcDibWidth <= 0)
    {
        WARNING(("Bad biWidth value"));
        return; // negative source dib width not allowed
    }

    INT     posSrcDibHeight;

    posSrcDibHeight = dibInfo->biHeight;

    if (posSrcDibHeight < 0)
    {
        posSrcDibHeight = -posSrcDibHeight;
    }

    // We can have a negative source Width or height
    // we need to verify that the two corners of the srcRect lie in the
    // bitmap bounds
    if (srcX < 0)
    {
        srcX = 0;
        WARNING(("srcX < 0"));
    }

    if (srcX > posSrcDibWidth)
    {
        WARNING(("Bad srcWidth or srcX value"));
        srcX = posSrcDibWidth;
    }

    if (srcY < 0)
    {
        srcY = 0;
        WARNING(("srcY < 0"));
    }

    if (srcY > posSrcDibHeight)
    {
        WARNING(("Bad srcWidth or srcX value"));
        srcY = posSrcDibHeight;
    }

    INT srcRight;
    srcRight     = srcX + srcWidth;
    if (srcRight < 0)
    {
        WARNING(("Bad srcWidth or srcX value"));
        srcWidth = -srcX;
    }

    if(srcRight > posSrcDibWidth)
    {
        WARNING(("Bad srcWidth or srcX value"));
        srcWidth = posSrcDibWidth - srcX;
    }

    INT srcBottom;
    srcBottom = srcY + srcHeight;
    if (srcBottom < 0)
    {
        WARNING(("Bad srcWidth or srcX value"));
        srcHeight = -srcY;
    }

    if (srcBottom > posSrcDibHeight)
    {
        WARNING(("Bad srcWidth or srcX value"));
        srcHeight = posSrcDibHeight - srcY;
    }
    // This also catches the case where
    // (posSrcDibWidth == 0) || (posSrcDibHeight == 0)
    if ((posSrcWidth <= 0) || (posSrcHeight <= 0))
    {
        return;
    }

    // If we are drawing into an 8Bpp surface and we have a different ROP then
    // srcCopy.
    if (Is8Bpp && rop != SRCCOPY)
    {
        BOOL         freeDibInfo    = FALSE;
        UINT         size;
        BITMAPINFO * alignedDibInfo = NULL;
        if (GetDibNumPalEntries(TRUE,
                                dibInfo->biSize,
                                dibInfo->biBitCount,
                                dibInfo->biCompression,
                                dibInfo->biClrUsed,
                                &size))
        {
            if (IsDwordAligned(dibInfo))
            {
                alignedDibInfo = (BITMAPINFO*) dibInfo;
            }
            else
            {
                // Mutliply the number of entries by the size of each entry
                size *= ((usage==DIB_RGB_COLORS)?sizeof(RGBQUAD):sizeof(WORD));
                // WMF's can't use System Palette
                alignedDibInfo = (BITMAPINFO*) GpMalloc(dibInfo->biSize + size);
                if (alignedDibInfo != NULL)
                {
                    memcpy((void*)&alignedDibInfo, dibInfo, dibInfo->biSize + size);
                    freeDibInfo = TRUE;
                }
            }
            if (alignedDibInfo != NULL)
            {
                if (GpBitmap::DrawAndHalftoneForStretchBlt(hdc, alignedDibInfo, bits, srcX, srcY,
                                                           posSrcWidth, posSrcHeight,
                                                           posDstWidth, posDstHeight,
                                                           &dibBmpInfo, &bmpBits, &hBitmap,
                                                           Interpolation) == Ok)
                {
                    deleteDIBSection = TRUE;
                    srcX = 0;
                    srcY = 0;
                    srcWidth = posDstWidth;
                    srcHeight = posDstHeight;
                    dibInfo = (BITMAPINFOHEADER*) dibBmpInfo;
                    bits = bmpBits;
                    if (freeDibInfo)
                    {
                        GpFree(alignedDibInfo);
                    }
                    goto DoGdiStretch;
                }
                if (freeDibInfo)
                {
                    GpFree(alignedDibInfo);
                }
            }
        }
    }

    // if not stretching, let GDI do the blt
    if ((posSrcWidth == posDstWidth) && (posSrcHeight == posDstHeight))
    {
        goto DoGdiStretch;
    }

    InterpolationMode interpolationMode;

    interpolationMode = Interpolation;

    // if not going to the screen or to a bitmap, use GDI to do the stretch
    // otherwise, use GDI+ to do the stretch (but let GDI do the blt)

    // if going to printer on Win98 and it is RLE8 compressed bitmap, then
    // always decode and blit from GDI+.  The reason is that print drivers
    // commonly don't support RLEx encoding and punt to GDI.  In this
    // context it creates a compatible printer dc and bitmap and then does a
    // StretchBlt, but it only does this at 1bpp, the result is black and white.

    if ((IsPrinter() && !Globals::IsNt &&
         dibInfo->biCompression == BI_RLE8) ||
        ((IsScreen() || IsBitmap()) &&
        (interpolationMode != InterpolationModeNearestNeighbor) &&
        (posSrcWidth > 1) && (posSrcHeight > 1) &&
        ((posDstWidth * posDstHeight) < GDI_INTERPOLATION_MAX)))
    {
        GpStatus status = GenericError;

        destBitmap = new GpBitmap(posDstWidth, posDstHeight, PIXFMT_24BPP_RGB);

        if (destBitmap != NULL)
        {
            if (destBitmap->IsValid())
            {
                BITMAPINFO * alignedDibInfo = NULL;
                UINT         size;
                BOOL         freeDibInfo = FALSE;
                if (GetDibNumPalEntries(TRUE,
                                        dibInfo->biSize,
                                        dibInfo->biBitCount,
                                        dibInfo->biCompression,
                                        dibInfo->biClrUsed,
                                        &size))
                {
                    if (IsDwordAligned(dibInfo))
                    {
                        alignedDibInfo = (BITMAPINFO*) dibInfo;
                    }
                    else
                    {
                        // Mutliply the number of entries by the size of each entry
                        size *= ((usage==DIB_RGB_COLORS)?sizeof(RGBQUAD):sizeof(WORD));
                        // WMF's can't use System Palette
                        alignedDibInfo = (BITMAPINFO*) GpMalloc(dibInfo->biSize + size);
                        if (alignedDibInfo != NULL)
                        {
                            memcpy((void*)&alignedDibInfo, dibInfo, dibInfo->biSize + size);
                            freeDibInfo = TRUE;
                        }
                    }
                    if (alignedDibInfo != NULL)
                    {
                        GpBitmap *  srcBitmap = new GpBitmap(alignedDibInfo,
                                                             bits, FALSE);
                        if (srcBitmap != NULL)
                        {
                            if (srcBitmap->IsValid())
                            {
                                GpGraphics *    destGraphics = destBitmap->GetGraphicsContext();

                                if (destGraphics != NULL)
                                {
                                    if (destGraphics->IsValid())
                                    {
                                        // we have to lock the graphics so the driver doesn't assert
                                        GpLock  lockGraphics(destGraphics->GetObjectLock());

                                        ASSERT(lockGraphics.IsValid());

                                        GpRectF     dstRect(0.0f, 0.0f, (REAL)posDstWidth, (REAL)posDstHeight);
                                        GpRectF     srcRect;

                                        // StretchDIBits takes a srcY parameter relative to the lower-left
                                        // (bottom) corner of the bitmap, not the top-left corner,
                                        // like DrawImage does
                                        srcRect.Y      = (REAL)(posSrcDibHeight - srcY - srcHeight);
                                        srcRect.X      = (REAL)srcX;

                                        // !!! We have to subtract one to keep the
                                        // filter from blending black into the image
                                        // on the right and bottom.
                                        srcRect.Width  = (REAL)(srcWidth - (srcWidthSign));
                                        srcRect.Height = (REAL)(srcHeight - (srcHeightSign));

                                        // don't do any blending as part of the stretch
                                        destGraphics->SetCompositingMode(CompositingModeSourceCopy);
                                        destGraphics->SetInterpolationMode(interpolationMode);

                                        // Set the image attributes to Wrap since we don't want to
                                        // use black pixels for the edges
                                        GpImageAttributes imgAttr;
                                        imgAttr.SetWrapMode(WrapModeTileFlipXY);

                                        // now draw the source into the dest bitmap
                                        status = destGraphics->DrawImage(srcBitmap,
                                                                         dstRect,
                                                                         srcRect,
                                                                         UnitPixel,
                                                                         &imgAttr);
                                    }
                                    else
                                    {
                                        WARNING(("destGraphics not valid"));
                                    }
                                    delete destGraphics;
                                }
                                else
                                {
                                    WARNING(("Could not construct destGraphics"));
                                }
                            }
                            else
                            {
                                WARNING(("srcGraphics not valid"));
                            }
                            srcBitmap->Dispose();   // doesn't delete the source data
                        }
                        else
                        {
                            WARNING(("Could not allocate a new BitmapInfoHeader"));
                        }
                        if (freeDibInfo)
                        {
                            GpFree(alignedDibInfo);
                        }
                    }
                    else
                    {
                        WARNING(("Could not construct srcGraphics"));
                    }
                }
                else
                {
                    WARNING(("Could not clone the bitmap header"));
                }

                if ((status == Ok) &&
                    (destBitmap->LockBits(NULL, IMGLOCK_READ, PIXFMT_24BPP_RGB,
                                          &bmpData) == Ok))
                {
                    ASSERT((bmpData.Stride & 3) == 0);

                    GpMemset(dibHeaderBuffer, 0, sizeof(BITMAPINFO));
                    bmpDataPtr = &bmpData;
                    bits       = (BYTE *)bmpData.Scan0;
                    srcX       = 0;
                    srcY       = 0;
                    srcWidth   = posDstWidth;
                    srcHeight  = posDstHeight;
                    usage      = DIB_RGB_COLORS;
                    dibInfo    = (BITMAPINFOHEADER *)dibHeaderBuffer;
                    dibInfo->biSize     = sizeof(BITMAPINFOHEADER);
                    dibInfo->biWidth    = posDstWidth;
                    dibInfo->biHeight   = -posDstHeight;
                    dibInfo->biPlanes   = 1;
                    dibInfo->biBitCount = 24;

                    // We don't want to set the StretchBltMode to COLORONCOLOR here
                    // because we might draw this into non 8Bpp surface and we still
                    // have to halftone in this case
                }
            }
            else
            {
                WARNING(("destBitmap not valid"));
            }
        }
        else
        {
            WARNING(("Could not construct destBitmap"));
        }
    }

DoGdiStretch:

    // Halftoning on NT4 with 8bpp does not work very well -- it gets
    // the wrong color hue.
    // We cannot halftone to metafile for that same reason
    if ((rop != SRCCOPY) ||
        (Is8Bpp && Globals::IsNt && (Globals::OsVer.dwMajorVersion <= 4)) ||
        IsMetafile())
    {
        // don't let halftoning mess up some kind of masking or other effect
        stretchBltMode = COLORONCOLOR;
    }
    ::SetStretchBltMode(hdc, stretchBltMode);

    // There is a bug in Win9x that some StretchDIBits calls don't work
    // so we need to create an actual StretchDIBits record to play.
    // Also, NT4 postscript printing can't handle anything but SRCCOPY,
    // so change any ROPs that are not source copy.
    BOOL processed = FALSE;
    if (!Globals::IsNt)
    {
        processed = CreateAndPlayOutputDIBRecord(hdc, bounds, dstX, dstY, dstWidth,
            dstHeight, srcX, srcY, srcWidth, srcHeight, dibInfo, bits, usage,
            rop);
    }
    else if (rop != SRCCOPY &&
             Globals::OsVer.dwMajorVersion <= 4 &&
             IsPostscript())
    {
        rop = SRCCOPY;
    }

    // In MSO, at this point they would check if this is NT running
    // on a non-true-color surface.  If so, they would set the
    // color adjustment gamma to be 20000.  The comment said that
    // this was needed for NT 3.5.  I'm assuming we don't need to
    // worry about that anymore.

    if (!processed)
    {
        ::StretchDIBits(hdc,
                        dstX, dstY, dstWidth, dstHeight,
                        srcX, srcY, srcWidth, srcHeight,
                        bits, (BITMAPINFO *)dibInfo, usage, rop);
    }

    if (destBitmap)
    {
        if (bmpDataPtr != NULL)
        {
            destBitmap->UnlockBits(bmpDataPtr);
        }
        destBitmap->Dispose();
    }
    if (restoreColors)
    {
        ::SetBkColor(hdc, oldBkColor);
        ::SetTextColor(hdc, oldTextColor);
    }
    if (deleteDIBSection)
    {
        // This will get rid of the Bitmap and it's bits
        ::DeleteObject(hBitmap);
        GpFree(dibBmpInfo);
    }
}

// Also handles META_DIBSTRETCHBLT
// There is not a usage parameter with these records -- the
// usage is always DIB_RGB_COLORS.
VOID
WmfEnumState::DIBBitBlt(
    )
{
    DWORD   rop = *((DWORD UNALIGNED *)RecordData);

    // If No-Op ROP, do nothing; just return
    if ((rop & 0xFFFF0000) == (GDIP_NOOP_ROP3 & 0xFFFF0000))
    {
        return;
    }

    if (rop != SRCCOPY &&
        rop != NOTSRCCOPY &&
        rop != PATCOPY &&
        rop != BLACKNESS &&
        rop != WHITENESS)
    {
        RopUsed = TRUE;
    }

    INT     paramIndex = 7;

    if (RecordType != WmfRecordTypeDIBBitBlt)
    {
        paramIndex = 9;      // META_DIBSTRETCHBLT
    }

    INT     dibIndex = paramIndex + 1;

    if (!IsSourceInRop3(rop))
    {
        if (((GetCurrentRecordSize() / 2) - 3) ==
            (GDIP_EMFPLUS_RECORD_TO_WMF(RecordType) >> 8))
        {
            paramIndex++;
        }

        INT dstX      = (INT)((INT16)((WORD *)RecordData)[paramIndex--]);
        INT dstY      = (INT)((INT16)((WORD *)RecordData)[paramIndex--]);
        INT dstWidth  = (INT)((INT16)((WORD *)RecordData)[paramIndex--]);
        INT dstHeight = (INT)((INT16)((WORD *)RecordData)[paramIndex]);


        // We know that this call will succeed because we have a 2K buffer
        // by default
        CreateRecordToModify(20);

        // For some strange reason we need to play the record on both
        // Win2K and Win9x. So create a WMF record for PatBlt and play it
        ModifiedWmfRecord->rdFunction = GDIP_EMFPLUS_RECORD_TO_WMF(WmfRecordTypePatBlt);
        ModifiedWmfRecord->rdSize     = 10;
        ModifiedWmfRecord->rdParm[5]  = (WORD) dstX;
        ModifiedWmfRecord->rdParm[4]  = (WORD) dstY;
        ModifiedWmfRecord->rdParm[3]  = (WORD) dstWidth;
        ModifiedWmfRecord->rdParm[2]  = (WORD) dstHeight;

        if (rop != PATCOPY && SrcCopyOnly)
        {
            *((DWORD*) ModifiedWmfRecord->rdParm) = PATCOPY;
        }
        else
        {
            *((DWORD*) ModifiedWmfRecord->rdParm) = rop;
        }

        // Now play the record (below)
    }
    else
    {
        BITMAPINFOHEADER UNALIGNED *  srcDibInfo = (BITMAPINFOHEADER UNALIGNED *)(((WORD *)RecordData) + dibIndex);
        UINT                          numPalEntries;
        UINT                          dibBitsSize;

        if ((srcDibInfo->biSize >= sizeof(BITMAPINFOHEADER)) &&
            GetDibNumPalEntries(TRUE,
                                srcDibInfo->biSize,
                                srcDibInfo->biBitCount,
                                srcDibInfo->biCompression,
                                srcDibInfo->biClrUsed,
                                &numPalEntries) &&
            ((dibBitsSize = GetDibBitsSize(srcDibInfo)) > 0))
        {
            if ((srcDibInfo->biBitCount == 1) && (srcDibInfo->biPlanes == 1))
            {
                DWORD UNALIGNED * rgb = (DWORD UNALIGNED *)GetDibColorTable(srcDibInfo);

                if ((rgb[0] == 0x00000000) &&
                    (rgb[1] == 0x00FFFFFF))
                {
                    if (SrcCopyOnly && (rop != SRCCOPY) && CreateCopyOfCurrentRecord())
                    {
                        *((DWORD UNALIGNED *)ModifiedWmfRecord->rdParm) = SRCCOPY;
                        goto PlayTheRecord;
                    }
                    else
                    {
                        // It is a compatible monochrome bitmap, which means it
                        // will use the TextColor and BkColor, so no recoloring
                        // is needed. Since we are not using SrcCopy that means
                        // that it's a mask
                        COLORREF oldBkColor = ::SetBkColor(Hdc, BkColor);
                        COLORREF oldTextColor = ::SetTextColor(Hdc, TextColor);
                        this->PlayRecord();
                        ::SetBkColor(Hdc, oldBkColor);
                        ::SetTextColor(Hdc, oldTextColor);
                        return;
                    }
                }
            }

            UINT    usage      = DIB_RGB_COLORS;
            INT     dstDibSize = GetModifiedDibSize(srcDibInfo, numPalEntries, dibBitsSize, usage);

            if (IsMetafile())
            {
                if (dstDibSize > 0)
                {
                    INT     size = SIZEOF_METARECORDHEADER + (dibIndex * sizeof(WORD)) + dstDibSize;

                    if (CreateRecordToModify(size))
                    {
                        ModifiedWmfRecord->rdFunction = GDIP_EMFPLUS_RECORD_TO_WMF(RecordType);
                        ModifiedWmfRecord->rdSize     = size / 2;
                        GpMemcpy(ModifiedWmfRecord->rdParm, RecordData, (dibIndex * sizeof(WORD)));
                        ModifyDib(DIB_RGB_COLORS, srcDibInfo, NULL,
                                  (BITMAPINFOHEADER UNALIGNED *)(ModifiedWmfRecord->rdParm + dibIndex),
                                  numPalEntries, dibBitsSize, ColorAdjustTypeBitmap);
                    }
                }
                goto PlayTheRecord;
            }
            // At this point, we are not going to play the record.  We're
            // going to call StretchDIBits.  One reason is because we want
            // to set the StretchBltMode, which is only used if a stretching
            // method is called.

            // Also, it avoids us doing an allocation/copy and then GDI doing
            // another allocation/copy (GDI has to do this to align the
            // DIB on a DWORD boundary).

            BITMAPINFOHEADER UNALIGNED * dstDibInfo = srcDibInfo;

            if (dstDibSize > 0)
            {
                if (CreateRecordToModify(dstDibSize))
                {
                    // ModifiedRecord is Aligned
                    dstDibInfo = (BITMAPINFOHEADER UNALIGNED *)ModifiedRecord;

                    ModifyDib(DIB_RGB_COLORS, srcDibInfo, NULL, dstDibInfo,
                              numPalEntries, dibBitsSize, ColorAdjustTypeBitmap);
                }
            }
            else if (!IsDwordAligned(dstDibInfo))
            {
                // The srcDibInfo may not aligned properly, so we make
                // a copy of it, so that it will be aligned.

                dstDibSize = GetCurrentRecordSize() - (SIZEOF_METARECORDHEADER + (dibIndex * sizeof(WORD)));
                if (CreateRecordToModify(dstDibSize))
                {
                    dstDibInfo = (BITMAPINFOHEADER *)ModifiedRecord;

                    GpMemcpy(dstDibInfo, srcDibInfo, dstDibSize);
                }
            }

            if (SrcCopyOnly)
            {
                rop = SRCCOPY;
            }

            INT srcX, srcY;
            INT dstX, dstY;
            INT srcWidth, srcHeight;
            INT dstWidth, dstHeight;

            dstX      = (INT)((INT16)((WORD *)RecordData)[paramIndex--]);
            dstY      = (INT)((INT16)((WORD *)RecordData)[paramIndex--]);
            dstWidth  = (INT)((INT16)((WORD *)RecordData)[paramIndex--]);
            dstHeight = (INT)((INT16)((WORD *)RecordData)[paramIndex--]);
            srcX      = (INT)((INT16)((WORD *)RecordData)[paramIndex--]);
            srcY      = (INT)((INT16)((WORD *)RecordData)[paramIndex--]);

            if (RecordType != WmfRecordTypeDIBBitBlt)
            {
                // META_DIBSTRETCHBLT
                srcWidth  = (INT)((INT16)((WORD *)RecordData)[paramIndex--]);
                srcHeight = (INT)((INT16)((WORD *)RecordData)[paramIndex--]);
            }
            else
            {
                srcWidth  = dstWidth;
                srcHeight = dstHeight;
            }

            // Need to flip the source y coordinate to call StretchDIBits.
            srcY = dstDibInfo->biHeight - srcY - srcHeight;

            OutputDIB(Hdc,
                      NULL,
                      dstX, dstY, dstWidth, dstHeight,
                      srcX, srcY, srcWidth, srcHeight,
                      dstDibInfo, NULL, DIB_RGB_COLORS, rop, TRUE);
            return;
        }
    }

PlayTheRecord:
    this->PlayRecord();
}

VOID
WmfEnumState::StretchDIBits(
    )
{
    DWORD   rop = *((DWORD UNALIGNED *)RecordData);

    // If No-Op ROP, do nothing; just return
    if ((rop & 0xFFFF0000) == (GDIP_NOOP_ROP3 & 0xFFFF0000))
    {
        return;
    }

    if (rop != SRCCOPY &&
        rop != NOTSRCCOPY &&
        rop != PATCOPY &&
        rop != BLACKNESS &&
        rop != WHITENESS)
    {
        RopUsed = TRUE;
    }

    if (IsSourceInRop3(rop))
    {
        if(((GetCurrentRecordSize() / 2) > (SIZEOF_METARECORDHEADER / sizeof(WORD)) + 11))
        {
            BITMAPINFOHEADER UNALIGNED *  srcDibInfo = (BITMAPINFOHEADER UNALIGNED *)(((WORD *)RecordData) + 11);
            UINT                          numPalEntries;
            UINT                          dibBitsSize;
    
            if ((srcDibInfo->biSize >= sizeof(BITMAPINFOHEADER)) &&
                GetDibNumPalEntries(TRUE,
                                    srcDibInfo->biSize,
                                    srcDibInfo->biBitCount,
                                    srcDibInfo->biCompression,
                                    srcDibInfo->biClrUsed,
                                    &numPalEntries) &&
                ((dibBitsSize = GetDibBitsSize(srcDibInfo)) > 0))
            {
                UINT                          usage      = ((WORD *)RecordData)[2];
                UINT                          oldUsage = usage;
                INT                           dstDibSize = GetModifiedDibSize(srcDibInfo, numPalEntries, dibBitsSize, usage);
                BITMAPINFOHEADER UNALIGNED *  dstDibInfo = srcDibInfo;
    
                if (dstDibSize > 0)
                {
                    if ((srcDibInfo->biBitCount == 1) && (srcDibInfo->biPlanes == 1))
                    {
                        DWORD UNALIGNED *     rgb = (DWORD UNALIGNED *)GetDibColorTable(srcDibInfo);
    
                        if ((rgb[0] == 0x00000000) &&
                            (rgb[1] == 0x00FFFFFF))
                        {
                            if (SrcCopyOnly && (rop != SRCCOPY) && CreateCopyOfCurrentRecord())
                            {
                                *((DWORD UNALIGNED *)ModifiedWmfRecord->rdParm) = SRCCOPY;
                                goto PlayTheRecord;
                            }
                            else
                            {
                                // It is a compatible monochrome bitmap, which means it
                                // will use the TextColor and BkColor, so no recoloring
                                // is needed. Since we are not using SrcCopy that means
                                // that it's a mask
                                COLORREF oldBkColor = ::SetBkColor(Hdc, BkColor);
                                COLORREF oldTextColor = ::SetTextColor(Hdc, TextColor);
                                this->PlayRecord();
                                ::SetBkColor(Hdc, oldBkColor);
                                ::SetTextColor(Hdc, oldTextColor);
                                return;
                            }
                        }
                    }
    
    
                    INT     size = SIZEOF_METARECORDHEADER + (11 * sizeof(WORD)) + dstDibSize;
    
                    if (CreateRecordToModify(size))
                    {
                        ModifiedWmfRecord->rdFunction = GDIP_EMFPLUS_RECORD_TO_WMF(RecordType);
                        ModifiedWmfRecord->rdSize     = size / 2;
                        GpMemcpy(ModifiedWmfRecord->rdParm, RecordData, (11 * sizeof(WORD)));
                        // This will be aligned.... Do we want to take a chance?
                        dstDibInfo = (BITMAPINFOHEADER UNALIGNED *)(ModifiedWmfRecord->rdParm + 11);
                        ModifyDib(oldUsage, srcDibInfo, NULL, dstDibInfo,
                                  numPalEntries, dibBitsSize, ColorAdjustTypeBitmap);
                    }
                }
    
                if (!IsMetafile())
                {
                    if ((dstDibInfo == srcDibInfo) && (!IsDwordAligned(dstDibInfo)))
                    {
                        // The srcDibInfo may not aligned properly, so we make
                        // a copy of it, so that it will be aligned.
    
                        dstDibSize = GetCurrentRecordSize() - (SIZEOF_METARECORDHEADER + (11 * sizeof(WORD)));
                        if (CreateRecordToModify(dstDibSize))
                        {
                            dstDibInfo = (BITMAPINFOHEADER UNALIGNED *)ModifiedRecord;
    
                            GpMemcpy(dstDibInfo, srcDibInfo, dstDibSize);
                        }
                    }
    
                    if (SrcCopyOnly)
                    {
                        rop = SRCCOPY;
                    }
    
                    INT dstX      = (INT)((INT16)((WORD *)RecordData)[10]);
                    INT dstY      = (INT)((INT16)((WORD *)RecordData)[9]);
                    INT dstWidth  = (INT)((INT16)((WORD *)RecordData)[8]);
                    INT dstHeight = (INT)((INT16)((WORD *)RecordData)[7]);
                    INT srcX      = (INT)((INT16)((WORD *)RecordData)[6]);
                    INT srcY      = (INT)((INT16)((WORD *)RecordData)[5]);
                    INT srcWidth  = (INT)((INT16)((WORD *)RecordData)[4]);
                    INT srcHeight = (INT)((INT16)((WORD *)RecordData)[3]);
    
                    OutputDIB(Hdc,
                              NULL,
                              dstX, dstY, dstWidth, dstHeight,
                              srcX, srcY, srcWidth, srcHeight,
                              dstDibInfo, NULL, usage, rop, TRUE);
                    return;
                }
            }
        }
    }
    else // !IsSourceRop3
    {
        if (rop != PATCOPY && SrcCopyOnly && CreateCopyOfCurrentRecord())
        {
            *((DWORD UNALIGNED *)ModifiedWmfRecord->rdParm) = PATCOPY;
        }
    }
PlayTheRecord:
    this->PlayRecord();
}

BOOL
WmfEnumState::CreateAndPlayOutputDIBRecord(
    HDC                           hdc,
    const RECTL *                 bounds,
    INT                           dstX,
    INT                           dstY,
    INT                           dstWidth,
    INT                           dstHeight,
    INT                           srcX,
    INT                           srcY,
    INT                           srcWidth,
    INT                           srcHeight,
    BITMAPINFOHEADER UNALIGNED *  dibInfo,
    BYTE *                        bits,   // if NULL, this is a packed DIB
    UINT                          usage,
    DWORD                         rop
    )
{
    INT  bitsSize = GetDibBitsSize(dibInfo);
    UINT sizePalEntries;

    if (GetDibNumPalEntries(TRUE,
                            dibInfo->biSize,
                            dibInfo->biBitCount,
                            dibInfo->biCompression,
                            dibInfo->biClrUsed,
                            &sizePalEntries))
    {
        // We need to get the palette size that corresponds to the type
        // If we have a DIB_PAL_COLORS then each entry is 16bits
        sizePalEntries *= ((usage == DIB_PAL_COLORS)?2:sizeof(RGBQUAD));
    }
    else
    {
        sizePalEntries = 0 ;
    }

    // We need at least a BITMAPINFO structure in there, but if there is a
    // palette, calculate the full size of the structure including the
    // palette

    INT bitmapHeaderSize = sizeof(BITMAPINFOHEADER) + sizePalEntries;
    INT size = SIZEOF_METARECORDHEADER + (11 * sizeof(WORD)) + bitmapHeaderSize + bitsSize ;

    // We cannot use the CreateRecordToModify because the record has already
    // been modified
    size = (size + 1) & ~1;
    METARECORD* metaRecord = (METARECORD*) GpMalloc(size);
    if (metaRecord != NULL)
    {
        metaRecord->rdFunction = GDIP_EMFPLUS_RECORD_TO_WMF(WmfRecordTypeStretchDIB);
        metaRecord->rdSize     = size / 2;
        metaRecord->rdParm[10] = (WORD) dstX;
        metaRecord->rdParm[9]  = (WORD) dstY;
        metaRecord->rdParm[8]  = (WORD) dstWidth;
        metaRecord->rdParm[7]  = (WORD) dstHeight;
        metaRecord->rdParm[6]  = (WORD) srcX;
        metaRecord->rdParm[5]  = (WORD) srcY;
        metaRecord->rdParm[4]  = (WORD) srcWidth;
        metaRecord->rdParm[3]  = (WORD) srcHeight;
        metaRecord->rdParm[2]  = (WORD) usage;
        *(DWORD UNALIGNED *)(&(metaRecord->rdParm[0])) = rop;

        GpMemcpy((BYTE*)(&(metaRecord->rdParm[11])), dibInfo, bitmapHeaderSize);
        GpMemcpy((BYTE*)(&(metaRecord->rdParm[11])) + bitmapHeaderSize, bits, bitsSize);

        ::PlayMetaFileRecord(hdc, HandleTable, metaRecord, NumObjects);
        GpFree(metaRecord);
        return TRUE;
    }
    return FALSE;
}

VOID
WmfEnumState::ModifyRecordColor(
    INT             paramIndex,
    ColorAdjustType adjustType
    )
{
    COLORREF    origColor = *((COLORREF UNALIGNED *)&(((WORD *)RecordData)[paramIndex]));
    COLORREF    modifiedColor = ModifyColor(origColor, adjustType);

    if (modifiedColor != origColor)
    {
        if (CreateCopyOfCurrentRecord())
        {
            *((COLORREF UNALIGNED *)&(ModifiedWmfRecord->rdParm[paramIndex])) = modifiedColor;
        }
    }
}

COLORREF
MfEnumState::ModifyColor(
    COLORREF        color,
    ColorAdjustType adjustType
    )
{
    if (AdjustType != ColorAdjustTypeDefault)
    {
        adjustType = AdjustType;
    }

    switch (color & 0xFF000000)
    {
    case 0x00000000:
        break;

    case 0x01000000:    // Palette Index
        {
            PALETTEENTRY    palEntry;

            if (::GetPaletteEntries(CurrentPalette, color & 0x000000FF, 1, &palEntry) == 1)
            {
                color = RGB(palEntry.peRed, palEntry.peGreen, palEntry.peBlue);
            }
            else
            {
                color = RGB(0, 0, 0);
                WARNING(("Failed to get palette entry"));
            }
        }
        break;

    case 0x02000000:    // Palette RGB
    default:
        color &= 0x00FFFFFF;
        break;
    }
    // Possible perfomance improvement: recolor the SelectedPalette so only
    // RGB values need to be recolored here.
    if (Recolor != NULL)
    {
        Recolor->ColorAdjustCOLORREF(&color, adjustType);
    }

    // Palette RGB values don't get dithered (at least not on NT), so we
    // only want to make it a PaletteRGB value if it is a solid color in
    // the palette.

    if (Is8Bpp)
    {
        COLORREF    matchingColor;

        matchingColor = (::GetNearestColor(Hdc, color | 0x02000000) & 0x00FFFFFF);

        // Pens and Text don't get Dithered so match them to the logical palette
        // the other adjustTypes do so they will get halftoned
        if ((matchingColor == color) ||
            (adjustType == ColorAdjustTypePen) ||
            (adjustType == ColorAdjustTypeText))
        {
            return color | 0x02000000;
        }
    }

    return color;
}

BOOL
MfEnumState::CreateRecordToModify(
    INT         size
    )
{
    if (size <= 0)
    {
        size = this->GetCurrentRecordSize();
    }

    // add a little padding to help insure we don't read past the end of the buffer
    size += 16;

    if (ModifiedRecordSize < size)
    {
        ASSERT(ModifiedRecordSize == 0);

        if (size <= GDIP_RECORDBUFFER_SIZE)
        {
            ModifiedRecord = RecordBuffer;
        }
        else if (size <= SizeAllocedRecord)
        {
            ModifiedRecord = AllocedRecord;
        }
        else
        {
            VOID *          newRecord;
            INT             allocSize;

            // alloc in increments of 1K
            allocSize = (size + 1023) & (~1023);

            ModifiedRecord = NULL;
            newRecord = GpRealloc(AllocedRecord, allocSize);
            if (newRecord != NULL)
            {
                ModifiedRecord    = newRecord;
                AllocedRecord     = newRecord;
                SizeAllocedRecord = allocSize;
            }
        }
    }
    else
    {
        ASSERT(ModifiedRecord != NULL);
        ASSERT(ModifiedRecordSize == size);
    }

    if (ModifiedRecord != NULL)
    {
        ModifiedRecordSize = size;
        return TRUE;
    }

    WARNING(("Failed to create ModifiedRecord"));
    return FALSE;
}

BOOL
WmfEnumState::CreateCopyOfCurrentRecord()
{
    if (ModifiedRecordSize > 0)
    {
        // We already made a modified record.  Don't do it again.
        ASSERT(ModifiedRecord != NULL);
        return TRUE;
    }

    INT     size = this->GetCurrentRecordSize();

    if (CreateRecordToModify(size))
    {
        METARECORD *    modifiedRecord = (METARECORD *)ModifiedRecord;

        modifiedRecord->rdFunction = GDIP_EMFPLUS_RECORD_TO_WMF(RecordType);
        modifiedRecord->rdSize     = size / 2;

        if (RecordDataSize > 0)
        {
            GpMemcpy(modifiedRecord->rdParm, RecordData, RecordDataSize);
        }
        return TRUE;
    }

    WARNING(("Failed to create copy of current record"));
    return FALSE;
}

VOID
WmfEnumState::MakeSolidBlackBrush()
{
    INT     size = SIZEOF_METARECORDHEADER + sizeof(LOGBRUSH16);

    CreateRecordToModify(size);
    ModifiedWmfRecord->rdSize = size / 2;
    ModifiedWmfRecord->rdFunction = META_CREATEBRUSHINDIRECT;

    LOGBRUSH16 *    logBrush = (LOGBRUSH16 *)(ModifiedWmfRecord->rdParm);

    logBrush->lbStyle = BS_SOLID;
    logBrush->lbColor = PALETTERGB(0,0,0);
    logBrush->lbHatch = 0;
}

VOID
WmfEnumState::CalculateViewportMatrix()
{
    GpRectF destViewport((REAL)DstViewportOrg.x, (REAL)DstViewportOrg.y,
                         (REAL)DstViewportExt.cx, (REAL)DstViewportExt.cy);
    GpRectF srcViewport((REAL)ImgViewportOrg.x, (REAL)ImgViewportOrg.y,
                        (REAL)ImgViewportExt.cx, (REAL)ImgViewportExt.cy);

    Status status = ViewportXForm.InferAffineMatrix(destViewport, srcViewport);
    if (status != Ok)
    {
        ViewportXForm.Reset();
    }
}

VOID
WmfEnumState::SetViewportOrg()
{
    // If this is the first SetViewportOrg then we need to save that value and
    // calculate a transform from out viewport to this viewport
    ImgViewportOrg.x = (INT)((INT16)((WORD *)RecordData)[1]);
    ImgViewportOrg.y = (INT)((INT16)((WORD *)RecordData)[0]);
    if (FirstViewportOrg || FirstViewportExt)
    {
        FirstViewportOrg = FALSE;
        // If we have processed the first ViewportExt call then we can calculate
        // the transform from our current viewport to the new viewport
        if (!FirstViewportExt)
        {
            CalculateViewportMatrix();
        }
    }
    else
    {
        // We need to keep the new Viewport origin to be able to calculate
        // the viewport bottom right corner before passing it through a
        // transform
        GpPointF newOrg((REAL) ImgViewportOrg.x,
                        (REAL) ImgViewportOrg.y);
        // Transform the new viewport with our viewport transformation
        ViewportXForm.Transform(&newOrg);
        DstViewportOrg.x = GpRound(newOrg.X);
        DstViewportOrg.y = GpRound(newOrg.Y);
        if(CreateRecordToModify())
        {
            ModifiedWmfRecord->rdFunction = CurrentWmfRecord->rdFunction;
            ModifiedWmfRecord->rdSize     = CurrentWmfRecord->rdSize;
            ModifiedWmfRecord->rdParm[0]  = (WORD)GpRound(newOrg.Y);
            ModifiedWmfRecord->rdParm[1]  = (WORD)GpRound(newOrg.X);
        }

        this->PlayRecord();
    }

}

VOID
WmfEnumState::SetViewportExt()
{
    // If this is the first SetViewportOrg then we need to save that value and
    // calculate a transform from out viewport to this viewport
    ImgViewportExt.cx = (INT)((INT16)((WORD *)RecordData)[1]);
    ImgViewportExt.cy = (INT)((INT16)((WORD *)RecordData)[0]);
    if (FirstViewportOrg || FirstViewportExt)
    {
        FirstViewportExt = FALSE;
        // If we have processed the first ViewportExt call then we can calculate
        // the transform from our current viewport to the new viewport
        if (!FirstViewportOrg)
        {
            CalculateViewportMatrix();
        }
    }
    else
    {
        // We need to transform the point, so add the current origin
        // of the Viewport
        GpPointF newExt((REAL) ImgViewportExt.cx,
                        (REAL) ImgViewportExt.cy);

        // Transform the new viewport with our viewport transformation
        ViewportXForm.VectorTransform(&newExt);
        if(CreateRecordToModify())
        {
            ModifiedWmfRecord->rdFunction = CurrentWmfRecord->rdFunction;
            ModifiedWmfRecord->rdSize     = CurrentWmfRecord->rdSize;
            ModifiedWmfRecord->rdParm[0]  = (WORD)GpRound(newExt.Y);
            ModifiedWmfRecord->rdParm[1]  = (WORD)GpRound(newExt.X);
        }

        this->PlayRecord();
    }
}

VOID
WmfEnumState::CreateRegion()
{
    // Check if the region it too big when mapped to device space.

    if (!Globals::IsNt)
    {

        // There is a bug in Win9x GDI where the code which plays METACREATEREGION doesn't copy the
        // entire region data, it is off by 8 bytes.  This seems to have been introduced to allow
        // for compatibility with an older header format, WIN2OBJECT.  We get around this by increasing
        // the size of the record by 8 bytes.  No other harm done.

        if (CreateCopyOfCurrentRecord())
        {
            // When we create a copy of the record, we add 16 bytes of padding so we know this
            // won't overflow into other memory.
            ModifiedWmfRecord->rdSize += 4;
        }
    }

    this->PlayRecord();
}

HFONT CreateTrueTypeFont(
    HFONT   hFont
    );

VOID
WmfEnumState::CreateFontIndirect(
    )
{
    LOGFONT16 *     logFont = (LOGFONT16 *)RecordData;
    BOOL            recordCopied = FALSE;

    if (!Globals::IsNt)
    {
        // We have a bug in Win9x that the OUT_TT_ONLY_PRECIS flag is
        // not always respected so if the font name is MS SANS SERIF
        // change it to Times New Roman
        // Since we don't have a string compare in ASCII do it in UNICODE
        WCHAR faceName[32];
        if (AnsiToUnicodeStr((char*)(logFont->lfFaceName), faceName, sizeof(faceName)/sizeof(WCHAR)) &&
            (UnicodeStringCompareCI(faceName, L"MS SANS SERIF") == 0))
        {
            if (CreateCopyOfCurrentRecord())
            {
                GpMemcpy(((LOGFONT16 *)(ModifiedWmfRecord->rdParm))->lfFaceName,
                         "Times New Roman", sizeof("Times New Roman"));
                recordCopied = TRUE;
            }
        }
    }
    if (logFont->lfOutPrecision != OUT_TT_ONLY_PRECIS)
    {
        // Instruct GDI to use only True Type fonts, since bitmap fonts
        // are not scalable.
        if (recordCopied || CreateCopyOfCurrentRecord())
        {
            ((LOGFONT16 *)(ModifiedWmfRecord->rdParm))->lfOutPrecision = OUT_TT_ONLY_PRECIS;
        }
    }
    this->PlayRecord();
}

VOID WmfEnumState::SelectObject()
{
    this->PlayRecord();

    // In case we selected a region on Win9x, we need to intersect
    if (!Globals::IsNt)
    {
        DWORD index = CurrentWmfRecord->rdParm[0];
        if (GetObjectTypeInternal((*HandleTable).objectHandle[index]) == OBJ_REGION)
        {
            this->IntersectDestRect();
        }
    }
}

VOID WmfEnumState::IntersectDestRect()
{
    if (!IsMetafile())
    {
        POINT windowOrg;
        SIZE  windowExt;
        ::SetWindowOrgEx(Hdc, DstViewportOrg.x, DstViewportOrg.y, &windowOrg);
        ::SetWindowExtEx(Hdc, DstViewportExt.cx, DstViewportExt.cy, &windowExt);

        // We are always in device units
        ::IntersectClipRect(Hdc, DestRectDevice.left, DestRectDevice.top,
                            DestRectDevice.right, DestRectDevice.bottom);

        ::SetWindowOrgEx(Hdc, windowOrg.x, windowOrg.y, NULL);
        ::SetWindowExtEx(Hdc, windowExt.cx, windowExt.cy, NULL);
    }
}

VOID WmfEnumState::SetROP2()
{
    INT rop = (INT)((INT16)(((WORD *)RecordData)[0]));

    if (rop != R2_BLACK &&
        rop != R2_COPYPEN &&
        rop != R2_NOTCOPYPEN &&
        rop != R2_WHITE )
    {
        RopUsed = TRUE;
    }
    this->PlayRecord();
}

BOOL
WmfEnumState::ProcessRecord(
    EmfPlusRecordType       recordType,
    UINT                    recordDataSize,
    const BYTE *            recordData
    )
{
    BOOL        forceCallback = FALSE;

    MfFsmState  nextState = MfFsmStart;

    if (IsFirstRecord)
    {
        // Bitmap fonts are not good for playing metafiles because they
        // don't scale well, so use a true type font instead as the default font.

        HFONT hFont = CreateTrueTypeFont((HFONT)GetCurrentObject(Hdc, OBJ_FONT));

        if (hFont != NULL)
        {
            DefaultFont = hFont;
            ::SelectObject(Hdc, hFont);
        }

        IsFirstRecord = FALSE;
    }

    // See if we're doing enumeration for an external app
    if (ExternalEnumeration)
    {
        if (recordData == NULL)
        {
            recordDataSize = 0;
        }
        else if (recordDataSize == 0)
        {
            recordData = NULL;
        }

        // make sure it's an EMF enum type
        recordType = GDIP_WMF_RECORD_TO_EMFPLUS(recordType);

        // See if the app changed the record at all.
        if ((recordType != RecordType) ||
            (recordDataSize != RecordDataSize) ||
            ((recordDataSize > 0) &&
             ((CurrentWmfRecord == NULL) ||
              (recordData != (const BYTE *)CurrentWmfRecord->rdParm))))
        {
            // Yes, we need to override what happened in StartRecord
            CurrentWmfRecord  = NULL;
            RecordType        = recordType;
            RecordData        = recordData;
            RecordDataSize    = recordDataSize;
        }
    }

    // Ignore all non-escape records if IgnorePostcript is TRUE
    if (recordType == WmfRecordTypeEscape || !IgnorePostscript)
    {
        GDIP_TRY

        switch (recordType)
        {
        // According to NT playback code, this is a EOF record, but it
        // is just skipped by the NT player.
        case GDIP_WMF_RECORD_TO_EMFPLUS(0x0000):  // End of metafile record
            break;

        // These records are not played back (at least in Win2000).
        // Apparently they haven't been supported since before Win3.1!
        case WmfRecordTypeSetRelAbs:
        case WmfRecordTypeDrawText:
        case WmfRecordTypeResetDC:
        case WmfRecordTypeStartDoc:
        case WmfRecordTypeStartPage:
        case WmfRecordTypeEndPage:
        case WmfRecordTypeAbortDoc:
        case WmfRecordTypeEndDoc:
        case WmfRecordTypeCreateBrush:
        case WmfRecordTypeCreateBitmapIndirect:
        case WmfRecordTypeCreateBitmap:
            ONCE(WARNING1("Unsupported WMF record"));
            break;

        default:
            // unknown record -- ignore it
            WARNING1("Unknown WMF Record");
            break;

        case WmfRecordTypeSetBkMode:
        case WmfRecordTypeSetMapMode:
        case WmfRecordTypeSetPolyFillMode:
        case WmfRecordTypeSetStretchBltMode:
        case WmfRecordTypeSetTextCharExtra:
        case WmfRecordTypeSetTextJustification:
        case WmfRecordTypeSetWindowOrg:
        case WmfRecordTypeSetWindowExt:
        case WmfRecordTypeOffsetWindowOrg:
        case WmfRecordTypeScaleWindowExt:
        case WmfRecordTypeOffsetViewportOrg:
        case WmfRecordTypeScaleViewportExt:
        case WmfRecordTypeLineTo:
        case WmfRecordTypeMoveTo:
        case WmfRecordTypeExcludeClipRect:
        case WmfRecordTypeIntersectClipRect:
        case WmfRecordTypeArc:
        case WmfRecordTypeEllipse:
        case WmfRecordTypePie:
        case WmfRecordTypeRoundRect:
        case WmfRecordTypePatBlt:
        case WmfRecordTypeTextOut:
        case WmfRecordTypePolygon:
        case WmfRecordTypePolyline:
        case WmfRecordTypeFillRegion:
        case WmfRecordTypeFrameRegion:
        case WmfRecordTypeInvertRegion:
        case WmfRecordTypePaintRegion:
        case WmfRecordTypeSetTextAlign:
        case WmfRecordTypeChord:
        case WmfRecordTypeSetMapperFlags:
        case WmfRecordTypeExtTextOut:
        case WmfRecordTypeAnimatePalette:
        case WmfRecordTypeSetPalEntries:
        case WmfRecordTypeResizePalette:
        case WmfRecordTypeSetLayout:
        case WmfRecordTypeDeleteObject:
        case WmfRecordTypeCreatePalette:
            // Play the current record.
            // Even if it fails, we keep playing the rest of the metafile.
            // There is a case that GdiComment with EPS may fail.
            this->PlayRecord();
            break;

        case WmfRecordTypeCreateRegion:
            this->CreateRegion();
            break;

        case WmfRecordTypeCreateFontIndirect:
            this->CreateFontIndirect();
            break;

        case WmfRecordTypeSetBkColor:
            this->SetBkColor();
            break;

        case WmfRecordTypeSetTextColor:
            this->SetTextColor();
            break;

        case WmfRecordTypeFloodFill:
            this->FloodFill();
            break;

        case WmfRecordTypeExtFloodFill:
            this->ExtFloodFill();
            break;

        case WmfRecordTypeSaveDC:
            this->SaveHdc();        // plays the record
            break;

        case WmfRecordTypeSetPixel:
            this->SetPixel();
            break;

        case WmfRecordTypeDIBCreatePatternBrush:
            this->DibCreatePatternBrush();
            break;

        case WmfRecordTypeCreatePatternBrush:   // Obsolete but still played back
            this->CreatePatternBrush();
            break;

        case WmfRecordTypeCreatePenIndirect:
            this->CreatePenIndirect();
            if (FsmState == MfFsmSelectBrush)
            {
                nextState = MfFsmCreatePenIndirect;
            }
            break;

        case WmfRecordTypeCreateBrushIndirect:
            this->CreateBrushIndirect();
            if (FsmState == MfFsmPSData)
            {
                nextState = MfFsmCreateBrushIndirect;
            }
            break;

        case WmfRecordTypeSelectObject:
            // What if we break out of the FSM, we do want to Create the appropriate
            // brush and pens right?!
            if (FsmState == MfFsmCreateBrushIndirect)
            {
                nextState = MfFsmSelectBrush;
                break;
            }
            else if (FsmState == MfFsmSelectBrush ||
                     FsmState == MfFsmCreatePenIndirect)
            {
                nextState = MfFsmSelectPen;
                break;
            }
            this->SelectObject();
            break;

        case WmfRecordTypeRectangle:
            this->Rectangle();
            break;

        case WmfRecordTypeSetROP2:
            {
                INT rdParm = (INT)((INT16)(((WORD *)RecordData)[0]));

                if (FsmState == MfFsmSelectPen &&
                    (INT)(rdParm == R2_NOP))
                {
                    nextState = MfFsmSetROP;
                }
                this->SetROP2();
            }
            break;

        case WmfRecordTypeBitBlt:       // Obsolete but still played back
            this->BitBlt();
            forceCallback = TRUE;
            break;

        case WmfRecordTypeStretchBlt:   // Obsolete but still played back
            this->StretchBlt();
            forceCallback = TRUE;
            break;

        case WmfRecordTypeEscape:
            {
                INT     escapeCode = (INT)((INT16)(((WORD *)RecordData)[0]));

                this->Escape();         // optionally plays the record

                if (FsmState == MfFsmStart && escapeCode == POSTSCRIPT_DATA &&
                    Globals::IsNt && IsPostscriptPrinter() &&
                    GdiCentricMode && SoftekFilter)
                {
                    nextState = MfFsmPSData;
                }

                // Comments do not change the current state
                if (escapeCode == MFCOMMENT)
                {
                    nextState = FsmState;
                }
            }
            break;

        case WmfRecordTypeRestoreDC:
            this->RestoreHdc();     // optionally plays the record
            break;

        case WmfRecordTypeSetDIBToDev:
            this->SetDIBitsToDevice();
            forceCallback = TRUE;
            break;

        case WmfRecordTypeSelectPalette:
            // We don't select in any palettes when playing the metafile,
            // because we don't want to invalidate our halftoning palette.
            // Keep track of the palette so we can map from PALETTEINDEXes
            // to RGB values.
            this->SelectPalette((INT)((INT16)(((WORD *)recordData)[0])));
            break;

        case WmfRecordTypeRealizePalette:
            // We don't want to invalidate our halftoning palette by realizing one
            // from a metafile.
            break;

        case WmfRecordTypePolyPolygon:
            this->PolyPolygon();
            break;

        case WmfRecordTypeDIBBitBlt:
            this->DIBBitBlt();
            forceCallback = TRUE;
            break;

        case WmfRecordTypeDIBStretchBlt:
            this->DIBStretchBlt();
            forceCallback = TRUE;
            break;

        case WmfRecordTypeStretchDIB:
            this->StretchDIBits();
            forceCallback = TRUE;
            break;

        case WmfRecordTypeSetViewportOrg:
            this->SetViewportOrg();
            break;

        case WmfRecordTypeSetViewportExt:
            this->SetViewportExt();
            break;

        case WmfRecordTypeSelectClipRegion:
        case WmfRecordTypeOffsetClipRgn:
            this->PlayRecord();
            if (!Globals::IsNt)
            {
                this->IntersectDestRect();
            }
            break;
        }

        GDIP_CATCH
            forceCallback = TRUE;
        GDIP_ENDCATCH

    }
    FsmState = nextState;
    this->EndRecord();

    return forceCallback;
}