You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1829 lines
58 KiB
1829 lines
58 KiB
/**************************************************************************\
|
|
*
|
|
* Copyright (c) 2000 Microsoft Corporation
|
|
*
|
|
* Module Name:
|
|
*
|
|
* MetaEmf.cpp
|
|
*
|
|
* Abstract:
|
|
*
|
|
* Methods for playing and recoloring an EMF.
|
|
*
|
|
* Created:
|
|
*
|
|
* 1/6/2000 DCurtis
|
|
*
|
|
\**************************************************************************/
|
|
|
|
#include "Precomp.hpp"
|
|
#include "MetaWmf.hpp"
|
|
|
|
//----------------------------------------------------------------------------
|
|
// File format signatures for Office Art data.
|
|
//------------------------------------------------------------------- JohnBo
|
|
#define msoszOfficeSignature "MSOFFICE"
|
|
#define msocbOfficeSignature 8
|
|
#define msoszOfficeAuthentication "9.0"
|
|
#define msocbOfficeAuthentication 3
|
|
#define msoszOfficeIdent msoszOfficeSignature msoszOfficeAuthentication
|
|
#define msocbOfficeIdent (msocbOfficeSignature+msocbOfficeAuthentication)
|
|
|
|
#define msoszOAPNGChunk "msOA"
|
|
#define msocbOAPNGChunk 4
|
|
#define msoszOADataHeader msoszOAPNGChunk msoszOfficeIdent
|
|
#define msocbOADataHeader (msocbOAPNGChunk+msocbOfficeIdent)
|
|
|
|
#define msoszOAKind "OA"
|
|
#define msocbOAKind 2
|
|
|
|
/* This defines an OZ chunk, like OA but Zlib compressed. */
|
|
#define msoszOZPNGChunk "msOZ"
|
|
#define msocbOZPNGChunk 4
|
|
#define msoszOZDataHeader msoszOZPNGChunk msoszOfficeIdent
|
|
#define msocbOZDataHeader (msocbOZPNGChunk+msocbOfficeIdent)
|
|
|
|
// These are needed for IA64 compatibility with X86
|
|
// Since we are reading this record from a stream, on IA64
|
|
// The EXTLOGPEN structure has a ULONG_PTR member which is not the same size
|
|
// on IA64 and X86. Since all output to files will keep the X86 format we need
|
|
// to make sure that we can read this format in IA64, so we make it compatible
|
|
// by changing the ULONG_PTR to a ULONG (32bit). The packing of the structure
|
|
// will them be the same and the members will be at the same offset
|
|
|
|
typedef struct tagEXTLOGPEN32 {
|
|
DWORD elpPenStyle;
|
|
DWORD elpWidth;
|
|
UINT elpBrushStyle;
|
|
COLORREF elpColor;
|
|
ULONG elpHatch;
|
|
DWORD elpNumEntries;
|
|
DWORD elpStyleEntry[1];
|
|
} EXTLOGPEN32, *PEXTLOGPEN32;
|
|
|
|
typedef struct tagEMREXTCREATEPEN32
|
|
{
|
|
EMR emr;
|
|
DWORD ihPen; // Pen handle index
|
|
DWORD offBmi; // Offset to the BITMAPINFO structure if any
|
|
DWORD cbBmi; // Size of the BITMAPINFO structure if any
|
|
// The bitmap info is followed by the bitmap
|
|
// bits to form a packed DIB.
|
|
DWORD offBits; // Offset to the brush bitmap bits if any
|
|
DWORD cbBits; // Size of the brush bitmap bits if any
|
|
EXTLOGPEN32 elp; // The extended pen with the style array.
|
|
} EMREXTCREATEPEN32, *PEMREXTCREATEPEN32;
|
|
|
|
typedef struct tagEMRRCLBOUNDS
|
|
{
|
|
EMR emr;
|
|
RECTL rclBounds;
|
|
} EMRRCLBOUNDS, *PEMRRCLBOUNDS;
|
|
|
|
typedef struct EMROFFICECOMMENT
|
|
{
|
|
EMR emr;
|
|
DWORD cbData; // Size of following fields and data
|
|
DWORD ident; // GDICOMMENT_IDENTIFIER
|
|
DWORD iComment; // Comment type e.g. GDICOMMENT_WINDOWS_METAFILE
|
|
} EMROFFICECOMMENT, *PEMROFFICECOMMENT;
|
|
|
|
RecolorStockObject RecolorStockObjectList[NUM_STOCK_RECOLOR_OBJS] =
|
|
{
|
|
{ WHITE_BRUSH, (COLORREF)RGB(0xFF, 0xFF, 0xFF), TRUE },
|
|
{ LTGRAY_BRUSH, (COLORREF)RGB(0xC0, 0xC0, 0xC0), TRUE },
|
|
{ GRAY_BRUSH, (COLORREF)RGB(0x80, 0x80, 0x80), TRUE },
|
|
{ DKGRAY_BRUSH, (COLORREF)RGB(0x40, 0x40 ,0x40), TRUE },
|
|
{ BLACK_BRUSH, (COLORREF)RGB(0, 0, 0), TRUE },
|
|
{ WHITE_PEN, (COLORREF)RGB(0xFF, 0xFF, 0xFF), FALSE },
|
|
{ BLACK_PEN, (COLORREF)RGB(0, 0 ,0), FALSE }
|
|
};
|
|
|
|
inline static RGBQUAD *
|
|
GetDibColorTable(
|
|
BITMAPINFOHEADER * dibInfo
|
|
)
|
|
{
|
|
return ( RGBQUAD *)(((BYTE *)dibInfo) + dibInfo->biSize);
|
|
}
|
|
|
|
BOOL
|
|
EmfEnumState::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))
|
|
{
|
|
ENHMETARECORD * modifiedRecord = (ENHMETARECORD *)ModifiedRecord;
|
|
|
|
modifiedRecord->iType = RecordType;
|
|
modifiedRecord->nSize = size;
|
|
|
|
if (RecordDataSize > 0)
|
|
{
|
|
GpMemcpy(modifiedRecord->dParm, RecordData, RecordDataSize);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
WARNING(("Failed to create copy of current record"));
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
EmfEnumState::CreateAndPlayOutputDIBRecord(
|
|
HDC hdc,
|
|
const RECTL * bounds,
|
|
INT dstX,
|
|
INT dstY,
|
|
INT dstWidth,
|
|
INT dstHeight,
|
|
INT srcX,
|
|
INT srcY,
|
|
INT srcWidth,
|
|
INT srcHeight,
|
|
UNALIGNED BITMAPINFOHEADER * dibInfo,
|
|
BYTE * bits, // if NULL, this is a packed DIB
|
|
UINT usage,
|
|
DWORD rop
|
|
)
|
|
{
|
|
ASSERT(!Globals::IsNt);
|
|
|
|
INT bitsSize = GetDibBitsSize(dibInfo);
|
|
UINT sizePalEntries;
|
|
|
|
if (GetDibNumPalEntries(FALSE,
|
|
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(EMRSTRETCHDIBITS) + bitmapHeaderSize + bitsSize ;
|
|
|
|
// We cannot use the CreateRecordToModify because the record has already
|
|
// been modified
|
|
size = (size + 3) & ~3;
|
|
EMRSTRETCHDIBITS* emrStretchDIBits = (EMRSTRETCHDIBITS*) GpMalloc(size);
|
|
if (emrStretchDIBits != NULL)
|
|
{
|
|
emrStretchDIBits->emr.iType = EmfRecordTypeStretchDIBits;
|
|
emrStretchDIBits->emr.nSize = size;
|
|
emrStretchDIBits->rclBounds = *bounds;
|
|
emrStretchDIBits->xDest = dstX;
|
|
emrStretchDIBits->yDest = dstY;
|
|
emrStretchDIBits->xSrc = srcX;
|
|
emrStretchDIBits->ySrc = srcY;
|
|
emrStretchDIBits->cxSrc = srcWidth;
|
|
emrStretchDIBits->cySrc = srcHeight;
|
|
emrStretchDIBits->offBmiSrc = sizeof(EMRSTRETCHDIBITS);
|
|
emrStretchDIBits->cbBmiSrc = bitmapHeaderSize;
|
|
emrStretchDIBits->offBitsSrc = emrStretchDIBits->offBmiSrc + emrStretchDIBits->cbBmiSrc;
|
|
emrStretchDIBits->cbBitsSrc = bitsSize;
|
|
emrStretchDIBits->iUsageSrc = usage;
|
|
emrStretchDIBits->dwRop = rop;
|
|
emrStretchDIBits->cxDest = dstWidth;
|
|
emrStretchDIBits->cyDest = dstHeight;
|
|
GpMemcpy((BYTE*)emrStretchDIBits + emrStretchDIBits->offBmiSrc, dibInfo, emrStretchDIBits->cbBmiSrc);
|
|
GpMemcpy((BYTE*)emrStretchDIBits + emrStretchDIBits->offBitsSrc, bits, emrStretchDIBits->cbBitsSrc);
|
|
|
|
::PlayEnhMetaFileRecord(hdc, HandleTable, (ENHMETARECORD *)emrStretchDIBits, NumObjects);
|
|
GpFree(emrStretchDIBits);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BITMAPINFOHEADER *
|
|
EmfEnumState::CreateModifiedDib(
|
|
BITMAPINFOHEADER * srcDibInfo,
|
|
BYTE * srcBits,
|
|
UINT & usage,
|
|
DWORD rop
|
|
)
|
|
{
|
|
BITMAPINFOHEADER * dstDibInfo = NULL;
|
|
UINT numPalEntries;
|
|
UINT dibBitsSize;
|
|
|
|
if ((srcDibInfo->biSize >= sizeof(BITMAPINFOHEADER)) &&
|
|
GetDibNumPalEntries(FALSE,
|
|
srcDibInfo->biSize,
|
|
srcDibInfo->biBitCount,
|
|
srcDibInfo->biCompression,
|
|
srcDibInfo->biClrUsed,
|
|
&numPalEntries) &&
|
|
((dibBitsSize = GetDibBitsSize(srcDibInfo)) > 0))
|
|
{
|
|
if (numPalEntries == 2 && rop != SRCCOPY)
|
|
{
|
|
DWORD *rgb = (DWORD*) GetDibColorTable(srcDibInfo);
|
|
if (rgb[0] == 0x00000000 && rgb[1] == 0x00FFFFFF)
|
|
{
|
|
return dstDibInfo;
|
|
}
|
|
}
|
|
|
|
// We need to pass in to ModifyDib the old Usage value because we haven't modified
|
|
// the bitmap yet. Once we do then we will return the new usage of the palette.
|
|
UINT oldUsage = usage;
|
|
INT dstDibSize = GetModifiedDibSize(srcDibInfo, numPalEntries, dibBitsSize, usage);
|
|
|
|
if ((dstDibSize > 0) && CreateRecordToModify(dstDibSize))
|
|
{
|
|
dstDibInfo = (BITMAPINFOHEADER *)ModifiedRecord;
|
|
ModifyDib(oldUsage, srcDibInfo, srcBits, dstDibInfo,
|
|
numPalEntries, dibBitsSize, ColorAdjustTypeBitmap);
|
|
}
|
|
}
|
|
return dstDibInfo;
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::BitBlt(
|
|
)
|
|
{
|
|
const EMRBITBLT * bitBltRecord = (const EMRBITBLT *)GetPartialRecord();
|
|
|
|
DWORD rop = bitBltRecord->dwRop;
|
|
|
|
// If No-Op ROP, do nothing; just return
|
|
if ((rop & 0xFFFF0000) == (GDIP_NOOP_ROP3 & 0xFFFF0000))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// On NT4, PATCOPYs fail to draw correctly if there is a skew/rotate
|
|
// in the matrix. So use a rectangle call instead.
|
|
if ((rop == PATCOPY) && Globals::IsNt && (Globals::OsVer.dwMajorVersion <= 4))
|
|
{
|
|
XFORM xform;
|
|
if (::GetWorldTransform(Hdc, &xform) &&
|
|
((xform.eM12 != 0.0f) || (xform.eM21 != 0.0f)))
|
|
{
|
|
HPEN hPenOld = (HPEN)::SelectObject(Hdc, ::GetStockObject(NULL_PEN));
|
|
DWORD dcRop = ::GetROP2(Hdc);
|
|
|
|
if (dcRop != R2_COPYPEN)
|
|
{
|
|
::SetROP2(Hdc, R2_COPYPEN);
|
|
}
|
|
::Rectangle(Hdc, bitBltRecord->xDest, bitBltRecord->yDest,
|
|
bitBltRecord->xDest + bitBltRecord->cxDest,
|
|
bitBltRecord->yDest + bitBltRecord->cyDest);
|
|
::SelectObject(Hdc, hPenOld);
|
|
if (dcRop != R2_COPYPEN)
|
|
{
|
|
::SetROP2(Hdc, dcRop);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (rop != SRCCOPY &&
|
|
rop != NOTSRCCOPY &&
|
|
rop != PATCOPY &&
|
|
rop != BLACKNESS &&
|
|
rop != WHITENESS)
|
|
{
|
|
RopUsed = TRUE;
|
|
}
|
|
|
|
if ((bitBltRecord->cbBitsSrc > 0) &&
|
|
(bitBltRecord->cbBmiSrc > 0) &&
|
|
IsSourceInRop3(rop))
|
|
{
|
|
// Should we modify the dib if it is monochrome?
|
|
// What if there is a non-identity transform for the src DC?
|
|
|
|
UINT usage = bitBltRecord->iUsageSrc;
|
|
BITMAPINFOHEADER * srcDibInfo = (BITMAPINFOHEADER *)(((BYTE *)bitBltRecord) + bitBltRecord->offBmiSrc);
|
|
BYTE * srcBits = ((BYTE *)bitBltRecord) + bitBltRecord->offBitsSrc;
|
|
BITMAPINFOHEADER * dstDibInfo = CreateModifiedDib(srcDibInfo, srcBits, usage, rop);
|
|
|
|
if (dstDibInfo != NULL)
|
|
{
|
|
srcDibInfo = dstDibInfo;
|
|
srcBits = NULL;
|
|
}
|
|
|
|
if (SrcCopyOnly && rop != SRCCOPY)
|
|
{
|
|
rop = SRCCOPY;
|
|
}
|
|
|
|
OutputDIB(Hdc,
|
|
&bitBltRecord->rclBounds,
|
|
bitBltRecord->xDest, bitBltRecord->yDest,
|
|
bitBltRecord->cxDest, bitBltRecord->cyDest,
|
|
bitBltRecord->xSrc, bitBltRecord->ySrc,
|
|
bitBltRecord->cxDest, bitBltRecord->cyDest,
|
|
srcDibInfo, srcBits, usage, rop, FALSE);
|
|
}
|
|
else
|
|
{
|
|
if (SrcCopyOnly && rop != PATCOPY && !IsSourceInRop3(rop) && CreateCopyOfCurrentRecord())
|
|
{
|
|
EMRBITBLT * newBitBltRecord = (EMRBITBLT *) ModifiedEmfRecord;
|
|
newBitBltRecord->dwRop = PATCOPY;
|
|
}
|
|
ResetRecordBounds();
|
|
this->PlayRecord();
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::StretchBlt(
|
|
)
|
|
{
|
|
const EMRSTRETCHBLT * stretchBltRecord = (const EMRSTRETCHBLT *)GetPartialRecord();
|
|
|
|
DWORD rop = stretchBltRecord->dwRop;
|
|
|
|
// 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 ((stretchBltRecord->cbBitsSrc > 0) &&
|
|
(stretchBltRecord->cbBmiSrc > 0) &&
|
|
IsSourceInRop3(rop))
|
|
{
|
|
// Should we modify the dib if it is monochrome?
|
|
// What if there is a non-identity transform for the src DC?
|
|
|
|
UINT usage = stretchBltRecord->iUsageSrc;
|
|
BITMAPINFOHEADER * srcDibInfo = (BITMAPINFOHEADER *)(((BYTE *)stretchBltRecord) + stretchBltRecord->offBmiSrc);
|
|
BYTE * srcBits = ((BYTE *)stretchBltRecord) + stretchBltRecord->offBitsSrc;
|
|
BITMAPINFOHEADER * dstDibInfo = CreateModifiedDib(srcDibInfo, srcBits, usage, rop);
|
|
|
|
if (SrcCopyOnly && rop != SRCCOPY)
|
|
{
|
|
rop = SRCCOPY;
|
|
}
|
|
|
|
ASSERT(sizeof(XFORM) == sizeof(REAL)*6);
|
|
|
|
GpMatrix xForm((REAL*) &(stretchBltRecord->xformSrc));
|
|
|
|
if (dstDibInfo != NULL)
|
|
{
|
|
srcDibInfo = dstDibInfo;
|
|
srcBits = NULL;
|
|
}
|
|
|
|
GpRectF srcRect((REAL)stretchBltRecord->xSrc,
|
|
(REAL)stretchBltRecord->ySrc,
|
|
(REAL)stretchBltRecord->cxSrc,
|
|
(REAL)stretchBltRecord->cySrc);
|
|
|
|
if (!xForm.IsIdentity())
|
|
{
|
|
// We cannot use TransformRect, because the output rect will always
|
|
// have a positive Width and Height which we don't want
|
|
GpPointF points[2];
|
|
points[0] = GpPointF(srcRect.X, srcRect.Y);
|
|
points[1] = GpPointF(srcRect.GetRight(), srcRect.GetBottom());
|
|
xForm.Transform(points, 2);
|
|
srcRect.X = points[0].X;
|
|
srcRect.Y = points[0].Y;
|
|
srcRect.Width = points[1].X - points[0].X;
|
|
srcRect.Height = points[1].Y - points[0].Y;
|
|
}
|
|
|
|
// StretchBlt takes as parameters the top left corner of the dest
|
|
// whereas StretchDIBits takes the offset in the source image.
|
|
// For bottom up dibs those are not the same and we need to offset
|
|
// the srcrect's Y coodinate by the difference
|
|
if (srcDibInfo->biHeight > 0 &&
|
|
srcRect.Height < srcDibInfo->biHeight)
|
|
{
|
|
srcRect.Y = srcDibInfo->biHeight - srcRect.Height - srcRect.Y;
|
|
}
|
|
|
|
OutputDIB(Hdc,
|
|
&stretchBltRecord->rclBounds,
|
|
stretchBltRecord->xDest, stretchBltRecord->yDest,
|
|
stretchBltRecord->cxDest, stretchBltRecord->cyDest,
|
|
GpRound(srcRect.X), GpRound(srcRect.Y),
|
|
GpRound(srcRect.Width), GpRound(srcRect.Height),
|
|
srcDibInfo, srcBits, usage, rop, FALSE);
|
|
}
|
|
else
|
|
{
|
|
if (SrcCopyOnly && rop != PATCOPY && !IsSourceInRop3(rop) && CreateCopyOfCurrentRecord())
|
|
{
|
|
EMRSTRETCHBLT * stretchBltRecord = (EMRSTRETCHBLT *)ModifiedEmfRecord;
|
|
stretchBltRecord->dwRop = PATCOPY;
|
|
}
|
|
ResetRecordBounds();
|
|
this->PlayRecord();
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::StretchDIBits(
|
|
)
|
|
{
|
|
const EMRSTRETCHDIBITS * stretchDIBitsRecord = (const EMRSTRETCHDIBITS *)GetPartialRecord();
|
|
|
|
DWORD rop = stretchDIBitsRecord->dwRop;
|
|
|
|
// 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 ((stretchDIBitsRecord->cbBitsSrc > 0) &&
|
|
(stretchDIBitsRecord->cbBmiSrc > 0) &&
|
|
IsSourceInRop3(rop))
|
|
{
|
|
UINT usage = stretchDIBitsRecord->iUsageSrc;
|
|
BITMAPINFOHEADER * srcDibInfo = (BITMAPINFOHEADER *)(((BYTE *)stretchDIBitsRecord) + stretchDIBitsRecord->offBmiSrc);
|
|
BYTE * srcBits = ((BYTE *)stretchDIBitsRecord) + stretchDIBitsRecord->offBitsSrc;
|
|
BITMAPINFOHEADER * dstDibInfo = CreateModifiedDib(srcDibInfo, srcBits, usage, rop);
|
|
|
|
if (dstDibInfo != NULL)
|
|
{
|
|
srcDibInfo = dstDibInfo;
|
|
srcBits = NULL;
|
|
}
|
|
|
|
if (SrcCopyOnly && rop != SRCCOPY)
|
|
{
|
|
rop = SRCCOPY;
|
|
}
|
|
|
|
OutputDIB(Hdc,
|
|
&stretchDIBitsRecord->rclBounds,
|
|
stretchDIBitsRecord->xDest, stretchDIBitsRecord->yDest,
|
|
stretchDIBitsRecord->cxDest, stretchDIBitsRecord->cyDest,
|
|
stretchDIBitsRecord->xSrc, stretchDIBitsRecord->ySrc,
|
|
stretchDIBitsRecord->cxSrc, stretchDIBitsRecord->cySrc,
|
|
srcDibInfo, srcBits, usage, rop, FALSE);
|
|
}
|
|
else
|
|
{
|
|
if (SrcCopyOnly && rop != PATCOPY && !IsSourceInRop3(rop) && CreateCopyOfCurrentRecord())
|
|
{
|
|
EMRSTRETCHDIBITS* stretchDIBitsRecord = (EMRSTRETCHDIBITS *)ModifiedEmfRecord;
|
|
stretchDIBitsRecord->dwRop = PATCOPY;
|
|
}
|
|
ResetRecordBounds();
|
|
this->PlayRecord();
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::SetDIBitsToDevice(
|
|
)
|
|
{
|
|
// !!! to do
|
|
|
|
// In SetDIBitsToDevice, the destination width and height
|
|
// are in device units but in StretchDIBits, they are in world units.
|
|
// Plus, the DIB header is for the entire DIB, but only part of the
|
|
// DIB may be here (based on the number of scans).
|
|
// So this record requires special handling if we are to process it.
|
|
|
|
ResetRecordBounds();
|
|
this->PlayRecord();
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::CreateDibPatternBrushPt(
|
|
)
|
|
{
|
|
const EMRCREATEDIBPATTERNBRUSHPT * brushRecord = (const EMRCREATEDIBPATTERNBRUSHPT *)GetPartialRecord();
|
|
INT objectIndex = brushRecord->ihBrush;
|
|
|
|
if (ValidObjectIndex(objectIndex) && (HandleTable != NULL))
|
|
{
|
|
UINT usage = brushRecord->iUsage;
|
|
BITMAPINFOHEADER * srcDibInfo = (BITMAPINFOHEADER *)(((UNALIGNED BYTE *)brushRecord) + brushRecord->offBmi);
|
|
BITMAPINFOHEADER * dstDibInfo = CreateModifiedDib(srcDibInfo, NULL, usage, SRCCOPY);
|
|
|
|
if (dstDibInfo != NULL)
|
|
{
|
|
HandleTable->objectHandle[objectIndex] =
|
|
CreateDIBPatternBrushPt((BITMAPINFO *)dstDibInfo, usage);
|
|
return;
|
|
}
|
|
}
|
|
|
|
this->PlayRecord();
|
|
}
|
|
|
|
inline static BOOL
|
|
IsOfficeArtData(
|
|
UINT recordSize,
|
|
const EMRGDICOMMENT * commentRecord
|
|
)
|
|
{
|
|
return ((recordSize >= (12 + 4 + msocbOADataHeader)) &&
|
|
(commentRecord->cbData >= (msocbOADataHeader + 4)) &&
|
|
((GpMemcmp(commentRecord->Data, msoszOADataHeader, msocbOADataHeader) == 0) ||
|
|
(GpMemcmp(commentRecord->Data, msoszOZDataHeader, msocbOZDataHeader) == 0)));
|
|
}
|
|
|
|
inline static const EMROFFICECOMMENT *
|
|
GetEmfComment(
|
|
const BYTE * emfRecord,
|
|
ULONG signature,
|
|
UINT kind
|
|
)
|
|
{
|
|
const EMROFFICECOMMENT * emfComment = (const EMROFFICECOMMENT*)(emfRecord);
|
|
if ((emfComment->ident == signature) && (emfComment->iComment == kind))
|
|
{
|
|
return emfComment;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
EmfEnumState::GdiComment(
|
|
)
|
|
{
|
|
// Skip Office Art data when playing into another metafile
|
|
if (IsMetafile() &&
|
|
IsOfficeArtData(
|
|
GetCurrentRecordSize(),
|
|
(const EMRGDICOMMENT *)GetPartialRecord()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (IsPostscript())
|
|
{
|
|
if (GetEmfComment((BYTE*)CurrentEmfRecord, msosignature, msocommentBeginSrcCopy))
|
|
{
|
|
SrcCopyOnly = TRUE;
|
|
return;
|
|
}
|
|
if (GetEmfComment((BYTE*)CurrentEmfRecord, msosignature, msocommentEndSrcCopy))
|
|
{
|
|
SrcCopyOnly = FALSE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
this->PlayRecord();
|
|
}
|
|
|
|
BOOL
|
|
IsPenCosmetic(
|
|
HDC hdc,
|
|
int penWidth
|
|
)
|
|
{
|
|
penWidth <<= 7;
|
|
|
|
INT newPenWidth = penWidth;
|
|
POINT points[2];
|
|
|
|
points[0].x = 0;
|
|
points[0].y = 0;
|
|
points[1].x = 1 << 7;
|
|
points[1].y = 0;
|
|
|
|
if (::DPtoLP(hdc, points, 2))
|
|
{
|
|
newPenWidth = points[1].x - points[0].x;
|
|
if (newPenWidth < 0)
|
|
{
|
|
newPenWidth = -newPenWidth;
|
|
}
|
|
}
|
|
return (penWidth <= newPenWidth);
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::CreatePen(
|
|
)
|
|
{
|
|
const EMRCREATEPEN * penRecord = (const EMRCREATEPEN *)GetPartialRecord();
|
|
DWORD oldStyle = penRecord->lopn.lopnStyle;
|
|
|
|
if (oldStyle == PS_NULL)
|
|
{
|
|
this->PlayRecord();
|
|
}
|
|
else if (IsMetafile())
|
|
{
|
|
ModifyRecordColor(4, ColorAdjustTypePen);
|
|
this->PlayRecord();
|
|
}
|
|
else
|
|
{
|
|
INT objectIndex = penRecord->ihPen;
|
|
|
|
if (ValidObjectIndex(objectIndex) && (HandleTable != NULL))
|
|
{
|
|
LOGBRUSH logBrush;
|
|
|
|
logBrush.lbStyle = PS_SOLID;
|
|
logBrush.lbColor = ModifyColor(penRecord->lopn.lopnColor, ColorAdjustTypePen);
|
|
logBrush.lbHatch = 0;
|
|
|
|
INT penWidth = penRecord->lopn.lopnWidth.x;
|
|
DWORD style;
|
|
|
|
if (!Globals::IsNt && !IsMetafile())
|
|
{
|
|
//IsPenCosmetic is gonna call DPtoLP... Make sure to invalidate
|
|
//the transform before
|
|
CreateAndPlayCommentRecord();
|
|
}
|
|
|
|
if (IsPenCosmetic(Hdc, penWidth))
|
|
{
|
|
switch (oldStyle)
|
|
{
|
|
case PS_SOLID:
|
|
case PS_DASH: // on Win9x, cosmetic only
|
|
case PS_DOT: // on Win9x, cosmetic only
|
|
case PS_DASHDOT: // on Win9x, cosmetic only
|
|
case PS_DASHDOTDOT: // on Win9x, cosmetic only
|
|
break;
|
|
|
|
case PS_ALTERNATE: // cosmetic only, NT only
|
|
if (Globals::IsNt)
|
|
{
|
|
break;
|
|
}
|
|
// FALLTHRU
|
|
|
|
case PS_USERSTYLE: // NT only
|
|
case PS_INSIDEFRAME: // geometric only
|
|
default:
|
|
oldStyle = PS_SOLID;
|
|
break;
|
|
}
|
|
penWidth = 1;
|
|
style = PS_COSMETIC | oldStyle;
|
|
}
|
|
else
|
|
{
|
|
switch (oldStyle)
|
|
{
|
|
case PS_SOLID:
|
|
case PS_INSIDEFRAME: // geometric only
|
|
break;
|
|
|
|
case PS_DASH: // on Win9x, cosmetic only
|
|
case PS_DOT: // on Win9x, cosmetic only
|
|
case PS_DASHDOT: // on Win9x, cosmetic only
|
|
case PS_DASHDOTDOT: // on Win9x, cosmetic only
|
|
if (Globals::IsNt)
|
|
{
|
|
break;
|
|
}
|
|
// FALLTHRU
|
|
|
|
case PS_ALTERNATE: // cosmetic only, NT only
|
|
case PS_USERSTYLE: // NT only
|
|
default:
|
|
oldStyle = PS_SOLID;
|
|
break;
|
|
}
|
|
style = PS_GEOMETRIC | oldStyle | PS_ENDCAP_ROUND | PS_JOIN_ROUND;
|
|
}
|
|
|
|
HandleTable->objectHandle[objectIndex] = ::ExtCreatePen(style, penWidth, &logBrush, 0, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
HFONT CreateTrueTypeFont(
|
|
HFONT hFont
|
|
)
|
|
{
|
|
if (hFont)
|
|
{
|
|
if (Globals::IsNt)
|
|
{
|
|
LOGFONT logFont;
|
|
|
|
if (GetObject(hFont, sizeof(logFont), &logFont) > 0)
|
|
{
|
|
logFont.lfOutPrecision = OUT_TT_ONLY_PRECIS;
|
|
return CreateFontIndirect(&logFont);
|
|
}
|
|
else
|
|
{
|
|
WARNING1("GetObject for hFont failed");
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
LOGFONTA logFont;
|
|
|
|
if (GetObjectA(hFont, sizeof(logFont), &logFont) > 0)
|
|
{
|
|
logFont.lfOutPrecision = OUT_TT_ONLY_PRECIS;
|
|
// 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
|
|
if (lstrcmpiA(logFont.lfFaceName, "MS SANS SERIF") == 0)
|
|
{
|
|
GpMemcpy(logFont.lfFaceName, "Times New Roman", sizeof("Times New Roman"));
|
|
}
|
|
return CreateFontIndirectA(&logFont);
|
|
}
|
|
else
|
|
{
|
|
WARNING1("GetObject for hFont failed");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WARNING1("NULL hFont");
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::ExtCreateFontIndirect(
|
|
)
|
|
{
|
|
const EMREXTCREATEFONTINDIRECTW * fontRecord = (const EMREXTCREATEFONTINDIRECTW *)GetPartialRecord();
|
|
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
|
|
if (UnicodeStringCompareCI(fontRecord->elfw.elfFullName, L"MS SANS SERIF") == 0)
|
|
{
|
|
if (CreateCopyOfCurrentRecord())
|
|
{
|
|
GpMemcpy(((EMREXTCREATEFONTINDIRECTW *)ModifiedEmfRecord)->elfw.elfFullName,
|
|
L"Times New Roman", sizeof(L"Times New Roman"));
|
|
recordCopied = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (fontRecord->elfw.elfLogFont.lfOutPrecision != OUT_TT_ONLY_PRECIS)
|
|
{
|
|
if (recordCopied || CreateCopyOfCurrentRecord())
|
|
// Instruct GDI to use only True Type fonts, since bitmap fonts
|
|
// are not scalable.
|
|
{
|
|
((EMREXTCREATEFONTINDIRECTW *)ModifiedEmfRecord)->elfw.elfLogFont.lfOutPrecision = OUT_TT_ONLY_PRECIS;
|
|
}
|
|
}
|
|
this->PlayRecord();
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::SelectObject(
|
|
)
|
|
{
|
|
const EMRSELECTOBJECT * selectRecord = (const EMRSELECTOBJECT *)GetPartialRecord();
|
|
DWORD handleIndex = selectRecord->ihObject;
|
|
|
|
// See if we're selecting in a stock font
|
|
if ((handleIndex & ENHMETA_STOCK_OBJECT) != 0)
|
|
{
|
|
handleIndex &= (~ENHMETA_STOCK_OBJECT);
|
|
|
|
// handleIndex >= (WHITE_BRUSH==0) && <= BLACK_PEN
|
|
if ((handleIndex <= BLACK_PEN) && (Recolor != NULL))
|
|
{
|
|
union {
|
|
LOGBRUSH lb;
|
|
LOGPEN lp;
|
|
};
|
|
|
|
RecolorStockObject* stockObj;
|
|
int i;
|
|
|
|
for (stockObj = &RecolorStockObjectList[0],
|
|
i = 0;
|
|
i < NUM_STOCK_RECOLOR_OBJS;
|
|
i++,
|
|
stockObj++)
|
|
{
|
|
if (stockObj->Handle == handleIndex)
|
|
{
|
|
// Already have a cached recolored handle lying around.
|
|
HGDIOBJ stockHandle = RecoloredStockHandle[i];
|
|
|
|
if (stockHandle == NULL)
|
|
{
|
|
// No cached recolored stock object handle, recreate
|
|
// one here.
|
|
COLORREF newColor;
|
|
|
|
if (stockObj->Brush)
|
|
{
|
|
newColor = ModifyColor(stockObj->Color, ColorAdjustTypeBrush);
|
|
|
|
lb.lbStyle = BS_SOLID;
|
|
lb.lbColor = newColor;
|
|
lb.lbHatch = 0;
|
|
|
|
stockHandle = ::CreateBrushIndirect(&lb);
|
|
|
|
RecoloredStockHandle[i] = stockHandle;
|
|
}
|
|
else
|
|
{
|
|
newColor = ModifyColor(stockObj->Color, ColorAdjustTypePen);
|
|
|
|
lp.lopnStyle = PS_SOLID;
|
|
lp.lopnWidth.x = 1;
|
|
lp.lopnWidth.y = 0;
|
|
lb.lbColor = newColor;
|
|
|
|
stockHandle = ::CreatePenIndirect(&lp);
|
|
|
|
RecoloredStockHandle[i] = stockHandle;
|
|
}
|
|
}
|
|
|
|
if (stockHandle != NULL)
|
|
{
|
|
::SelectObject(Hdc, stockHandle);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ((handleIndex >= OEM_FIXED_FONT) &&
|
|
(handleIndex <= DEFAULT_GUI_FONT))
|
|
{
|
|
// It is a stock font -- create a true type font, instead of
|
|
// using the stock font directly to guarantee that we don't
|
|
// use bitmap fonts which don't scale well.
|
|
|
|
HFONT hFont = StockFonts[handleIndex - OEM_FIXED_FONT];
|
|
|
|
if (hFont == NULL)
|
|
{
|
|
hFont = CreateTrueTypeFont((HFONT)GetStockObject(handleIndex));
|
|
StockFonts[handleIndex - OEM_FIXED_FONT] = hFont;
|
|
}
|
|
|
|
if (hFont != NULL)
|
|
{
|
|
::SelectObject(Hdc, hFont);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
this->PlayRecord();
|
|
|
|
// In case we select a region, intersect with the destrect
|
|
if (!Globals::IsNt)
|
|
{
|
|
if (((handleIndex & ENHMETA_STOCK_OBJECT) == 0) &&
|
|
(GetObjectTypeInternal((*HandleTable).objectHandle[handleIndex]) == OBJ_REGION))
|
|
{
|
|
this->IntersectDestRect();
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::ExtCreatePen(
|
|
)
|
|
{
|
|
const EMREXTCREATEPEN32 * penRecord = (const EMREXTCREATEPEN32 *)GetPartialRecord();
|
|
UINT brushStyle = penRecord->elp.elpBrushStyle;
|
|
|
|
if (brushStyle != BS_HOLLOW)
|
|
{
|
|
if (IsMetafile())
|
|
{
|
|
if ((brushStyle == BS_SOLID) || (brushStyle == BS_HATCHED))
|
|
{
|
|
ModifyRecordColor(8, ColorAdjustTypePen);
|
|
}
|
|
// else don't worry about recoloring pattern brushes for now
|
|
|
|
this->PlayRecord();
|
|
return;
|
|
}
|
|
|
|
INT objectIndex = penRecord->ihPen;
|
|
|
|
if (ValidObjectIndex(objectIndex) && (HandleTable != NULL))
|
|
{
|
|
if (!Globals::IsNt && !IsMetafile())
|
|
{
|
|
//IsPenCosmetic is gonna call DPtoLP... Make sure to invalidate
|
|
//the transform before
|
|
CreateAndPlayCommentRecord();
|
|
}
|
|
|
|
DWORD penWidth = penRecord->elp.elpWidth;
|
|
BOOL isCosmetic = IsPenCosmetic(Hdc, penWidth);
|
|
DWORD oldStyle = penRecord->elp.elpPenStyle;
|
|
|
|
if (!Globals::IsNt)
|
|
{
|
|
DWORD style;
|
|
|
|
if (isCosmetic)
|
|
{
|
|
oldStyle &= PS_STYLE_MASK;
|
|
|
|
switch (oldStyle)
|
|
{
|
|
case PS_SOLID:
|
|
case PS_DASH: // on Win9x, cosmetic only
|
|
case PS_DOT: // on Win9x, cosmetic only
|
|
case PS_DASHDOT: // on Win9x, cosmetic only
|
|
case PS_DASHDOTDOT: // on Win9x, cosmetic only
|
|
break;
|
|
|
|
case PS_ALTERNATE: // cosmetic only, NT only
|
|
case PS_USERSTYLE: // NT only
|
|
case PS_INSIDEFRAME: // geometric only
|
|
default:
|
|
oldStyle = PS_SOLID;
|
|
break;
|
|
}
|
|
penWidth = 1;
|
|
style = PS_COSMETIC | oldStyle;
|
|
}
|
|
else
|
|
{
|
|
oldStyle &= (~PS_TYPE_MASK);
|
|
|
|
switch (oldStyle & PS_STYLE_MASK)
|
|
{
|
|
case PS_SOLID:
|
|
case PS_INSIDEFRAME: // geometric only
|
|
break;
|
|
|
|
case PS_DASH: // on Win9x, cosmetic only
|
|
case PS_DOT: // on Win9x, cosmetic only
|
|
case PS_DASHDOT: // on Win9x, cosmetic only
|
|
case PS_DASHDOTDOT: // on Win9x, cosmetic only
|
|
case PS_ALTERNATE: // cosmetic only, NT only
|
|
case PS_USERSTYLE: // NT only
|
|
default:
|
|
oldStyle = (oldStyle & (~PS_STYLE_MASK)) | PS_SOLID;
|
|
break;
|
|
}
|
|
style = PS_GEOMETRIC | oldStyle;
|
|
}
|
|
|
|
COLORREF color = RGB(0,0,0);
|
|
|
|
if ((brushStyle == BS_SOLID) || (brushStyle == BS_HATCHED))
|
|
{
|
|
color = penRecord->elp.elpColor;
|
|
}
|
|
|
|
color = ModifyColor(color, ColorAdjustTypePen);
|
|
|
|
// Only solid brushes are supported on Win9x
|
|
LOGBRUSH logBrush;
|
|
logBrush.lbStyle = PS_SOLID;
|
|
logBrush.lbColor = color;
|
|
logBrush.lbHatch = 0;
|
|
|
|
HandleTable->objectHandle[objectIndex] = ::ExtCreatePen(style, penWidth, &logBrush, 0, NULL);
|
|
return;
|
|
}
|
|
|
|
// else it is NT
|
|
if ((brushStyle == BS_SOLID) || (brushStyle == BS_HATCHED))
|
|
{
|
|
ModifyRecordColor(8, ColorAdjustTypePen);
|
|
}
|
|
// else don't worry about recoloring pattern brushes for now
|
|
|
|
if (isCosmetic && CreateCopyOfCurrentRecord())
|
|
{
|
|
oldStyle &= PS_STYLE_MASK;
|
|
if (oldStyle == PS_INSIDEFRAME) // geometric only
|
|
{
|
|
oldStyle = PS_SOLID;
|
|
}
|
|
((EMREXTCREATEPEN32 *)ModifiedEmfRecord)->elp.elpPenStyle = PS_COSMETIC | oldStyle;
|
|
((EMREXTCREATEPEN32 *)ModifiedEmfRecord)->elp.elpWidth = 1;
|
|
}
|
|
}
|
|
}
|
|
this->PlayRecord();
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::CreateBrushIndirect(
|
|
)
|
|
{
|
|
const EMRCREATEBRUSHINDIRECT * brushRecord = (const EMRCREATEBRUSHINDIRECT *)GetPartialRecord();
|
|
|
|
if (brushRecord->lb.lbStyle != BS_HOLLOW)
|
|
{
|
|
ModifyRecordColor(2, ColorAdjustTypeBrush);
|
|
|
|
if (ModifiedEmfRecord != NULL)
|
|
{
|
|
brushRecord = (const EMRCREATEBRUSHINDIRECT *)ModifiedEmfRecord;
|
|
}
|
|
|
|
// 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 && (brushRecord->lb.lbStyle == BS_SOLID) &&
|
|
(((color = brushRecord->lb.lbColor) & 0x02000000) == 0))
|
|
{
|
|
// create a halftone brush, instead of a solid brush
|
|
|
|
INT objectIndex = brushRecord->ihBrush;
|
|
|
|
if (ValidObjectIndex(objectIndex) && (HandleTable != NULL))
|
|
{
|
|
BYTE dib[sizeof(BITMAPINFOHEADER) + // DIB 8 bpp header
|
|
(8 * sizeof(RGBQUAD)) + // DIB 8 colors
|
|
(8* 8)]; // DIB 8x8 pixels
|
|
|
|
HalftoneColorRef_216(color, dib);
|
|
|
|
HandleTable->objectHandle[objectIndex] =
|
|
CreateDIBPatternBrushPt(dib, DIB_RGB_COLORS);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
this->PlayRecord();
|
|
}
|
|
|
|
BOOL
|
|
EmfEnumState::PlayRecord(
|
|
)
|
|
{
|
|
const ENHMETARECORD * recordToPlay = ModifiedEmfRecord;
|
|
|
|
// See if we've modified the record
|
|
if (recordToPlay == NULL)
|
|
{
|
|
// We haven't. See if we have a valid current record
|
|
if (CurrentEmfRecord != NULL)
|
|
{
|
|
recordToPlay = CurrentEmfRecord;
|
|
}
|
|
else
|
|
{
|
|
// we don't so we have to create one
|
|
if (!CreateCopyOfCurrentRecord())
|
|
{
|
|
return FALSE;
|
|
}
|
|
recordToPlay = ModifiedEmfRecord;
|
|
}
|
|
}
|
|
return PlayEnhMetaFileRecord(Hdc, HandleTable, recordToPlay, NumObjects);
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::RestoreHdc(
|
|
)
|
|
{
|
|
LONG relativeCount = ((const EMRRESTOREDC *)GetPartialRecord())->iRelative;
|
|
|
|
if (SaveDcCount < 0)
|
|
{
|
|
if (relativeCount >= SaveDcCount)
|
|
{
|
|
if (relativeCount >= 0)
|
|
{
|
|
// Modify the record
|
|
CreateCopyOfCurrentRecord(); // guaranteed to succeed
|
|
relativeCount = -1;
|
|
((EMRRESTOREDC *)ModifiedEmfRecord)->iRelative = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Modify the record
|
|
CreateCopyOfCurrentRecord(); // guaranteed to succeed
|
|
relativeCount = SaveDcCount;
|
|
((EMRRESTOREDC *)ModifiedEmfRecord)->iRelative = relativeCount;
|
|
}
|
|
SaveDcCount -= relativeCount;
|
|
this->PlayRecord();
|
|
}
|
|
else
|
|
{
|
|
WARNING(("RestoreDC not matched to a SaveDC"));
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::ModifyRecordColor(
|
|
INT paramIndex,
|
|
ColorAdjustType adjustType
|
|
)
|
|
{
|
|
COLORREF origColor = ((COLORREF *)RecordData)[paramIndex];
|
|
COLORREF modifiedColor = ModifyColor(origColor, adjustType);
|
|
|
|
if (modifiedColor != origColor)
|
|
{
|
|
if (CreateCopyOfCurrentRecord())
|
|
{
|
|
*((COLORREF*)&(ModifiedEmfRecord->dParm[paramIndex])) = modifiedColor;
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::ExtEscape(
|
|
)
|
|
{
|
|
if (IsPostscriptPrinter())
|
|
{
|
|
// 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.
|
|
|
|
PEMREXTESCAPE escRecord = (PEMREXTESCAPE) RecordData;
|
|
|
|
// EMR emr;
|
|
// INT iEscape; // Escape code
|
|
// INT cbEscData; // Size of escape data
|
|
// BYTE EscData[1]; // Escape data
|
|
|
|
if (escRecord->iEscape == POSTSCRIPT_DATA)
|
|
{
|
|
if (Globals::IsNt)
|
|
{
|
|
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())
|
|
{
|
|
((EMREXTESCAPE *)ModifiedEmfRecord)->iEscape = PASSTHROUGH;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// PS-centric mode
|
|
if (CreateCopyOfCurrentRecord())
|
|
{
|
|
((EMREXTESCAPE *)ModifiedEmfRecord)->iEscape = POSTSCRIPT_PASSTHROUGH;
|
|
}
|
|
}
|
|
|
|
this->PlayRecord();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// compatibility mode uses POSTSCRIPT_DATA
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Win98 doesn't distinguish between GDI & compatibility mode
|
|
if (CreateCopyOfCurrentRecord())
|
|
{
|
|
((EMREXTESCAPE *)ModifiedEmfRecord)->iEscape = PASSTHROUGH;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this->PlayRecord();
|
|
}
|
|
|
|
EmfEnumState::EmfEnumState(
|
|
HDC hdc,
|
|
HENHMETAFILE hEmf,
|
|
const RECT * dest,
|
|
const RECT * deviceRect,
|
|
BOOL externalEnumeration,
|
|
InterpolationMode interpolation,
|
|
DpContext * context,
|
|
GpRecolor * recolor,
|
|
ColorAdjustType adjustType
|
|
)
|
|
: MfEnumState(hdc, externalEnumeration, interpolation,
|
|
recolor, adjustType, deviceRect, context)
|
|
{
|
|
if (IsValid())
|
|
{
|
|
ClipRgn = NULL;
|
|
Palette = NULL;
|
|
BrushOrg.x = 0;
|
|
BrushOrg.y = 0;
|
|
|
|
// Bug 166280 from Office:
|
|
// PROBLEM: If the DC has any clipping region in it, EnumEnhMetaFile
|
|
// will create a region before calling the EnumProc for the
|
|
// first time. And, this region is not deleted and cannot be
|
|
// recovered through RestoreDC. (Only on Win9x)
|
|
// FIX: Before calling EnumEnhMetafile, save the clipping region
|
|
// and set the Clipping region to NULL. Select the saved
|
|
// clipping region in the CallBack at EMR_HEADER.
|
|
// We will not do this on Metafile DC. Put clipping region
|
|
// records in Metafile may cause other rendering problems.
|
|
|
|
if (!Globals::IsNt && !IsMetafile())
|
|
{
|
|
HRGN clipRgn = ::CreateRectRgn(0,0,0,0);
|
|
|
|
if (clipRgn != NULL)
|
|
{
|
|
switch (::GetClipRgn(hdc, clipRgn))
|
|
{
|
|
case -1: // error
|
|
case 0: // no initial clip region
|
|
::DeleteObject(clipRgn);
|
|
break;
|
|
case 1: // has initial clip region
|
|
::SelectClipRgn(hdc, NULL);
|
|
ClipRgn = clipRgn;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Bug 160932 from Office: Redraw problems with EMFs
|
|
// The fix is to make the drawing independent of where
|
|
// the EMF is drawn - this can be done easily by setting the
|
|
// brush origin before the first record is played (in the
|
|
// record callback proc) to a value which is the top left of the
|
|
// output rectangle of the EMF in device coordinates (i.e. LPtoDP of
|
|
// the logical coordinate top left).
|
|
|
|
BrushOrg.x = dest->left;
|
|
BrushOrg.y = dest->top;
|
|
LPtoDP(hdc, &BrushOrg, 1);
|
|
|
|
// EnumEnhMetafile selects in the DEFAULT_PALETTE, but we may need
|
|
// another palette to remain selected in (for halftoning, etc.)
|
|
// so save the current palette and select it back in when the
|
|
// header record is received.
|
|
|
|
HPALETTE hpal = (HPALETTE)GetCurrentObject(hdc, OBJ_PAL);
|
|
if (hpal != (HPALETTE)GetStockObject(DEFAULT_PALETTE))
|
|
{
|
|
Palette = hpal;
|
|
}
|
|
BkColor = ::GetBkColor(hdc);
|
|
TextColor = ::GetTextColor(hdc);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::Header(
|
|
)
|
|
{
|
|
::SetBrushOrgEx(Hdc, BrushOrg.x, BrushOrg.y, NULL);
|
|
if (ClipRgn != (HRGN)0)
|
|
{
|
|
::SelectClipRgn(Hdc, ClipRgn);
|
|
::DeleteObject(ClipRgn);
|
|
ClipRgn = NULL;
|
|
}
|
|
if (Palette != NULL)
|
|
{
|
|
::SelectPalette(Hdc, Palette, TRUE);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
this->PlayRecord();
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::SelectPalette(INT objectIndex)
|
|
{
|
|
if (objectIndex == (ENHMETA_STOCK_OBJECT | DEFAULT_PALETTE))
|
|
{
|
|
CurrentPalette = (HPALETTE)::GetStockObject(DEFAULT_PALETTE);
|
|
}
|
|
else
|
|
{
|
|
MfEnumState::SelectPalette(objectIndex);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EmfEnumState::IntersectDestRect()
|
|
{
|
|
if (!IsMetafile())
|
|
{
|
|
// Make the transform the identity
|
|
POINT windowOrg;
|
|
SIZE windowExt;
|
|
POINT viewportOrg;
|
|
SIZE viewportExt;
|
|
::SetViewportOrgEx(Hdc, 0, 0, &viewportOrg);
|
|
::SetViewportExtEx(Hdc, 1, 1, &viewportExt);
|
|
::SetWindowOrgEx(Hdc, 0, 0, &windowOrg);
|
|
::SetWindowExtEx(Hdc, 1, 1, &windowExt);
|
|
|
|
// We are always in device units
|
|
::IntersectClipRect(Hdc, DestRectDevice.left, DestRectDevice.top,
|
|
DestRectDevice.right, DestRectDevice.bottom);
|
|
|
|
// Restore the transform
|
|
::SetViewportOrgEx(Hdc, viewportOrg.x, viewportOrg.y, NULL);
|
|
::SetViewportExtEx(Hdc, viewportExt.cx, viewportExt.cy, NULL);
|
|
::SetWindowOrgEx(Hdc, windowOrg.x, windowOrg.y, NULL);
|
|
::SetWindowExtEx(Hdc, windowExt.cx, windowExt.cy, NULL);
|
|
}
|
|
}
|
|
|
|
VOID EmfEnumState::SetROP2()
|
|
{
|
|
DWORD dwROP = ((const EMRSETROP2 *)GetPartialRecord())->iMode;
|
|
|
|
if (dwROP != R2_BLACK &&
|
|
dwROP != R2_COPYPEN &&
|
|
dwROP != R2_NOTCOPYPEN &&
|
|
dwROP != R2_WHITE )
|
|
{
|
|
RopUsed = TRUE;
|
|
}
|
|
this->PlayRecord();
|
|
}
|
|
|
|
VOID EmfEnumState::ExtTextOutW()
|
|
{
|
|
if (!this->PlayRecord())
|
|
{
|
|
BYTE* emrTextOut = (BYTE*) GetPartialRecord();
|
|
if(CreateCopyOfCurrentRecord())
|
|
{
|
|
// !!! Shouldn't this use the offset in the record?
|
|
|
|
BYTE * ptr = emrTextOut + sizeof(EMREXTTEXTOUTW);
|
|
AnsiStrFromUnicode ansistr((WCHAR*)ptr);
|
|
INT len = strlen(ansistr);
|
|
// Don't forget to copy the NULL byte
|
|
GpMemcpy((BYTE*)ModifiedEmfRecord + sizeof(EMREXTTEXTOUTW), (char*)ansistr, len+1);
|
|
EMREXTTEXTOUTA *record = (EMREXTTEXTOUTA*) ModifiedEmfRecord;
|
|
record->emr.iType = EmfRecordTypeExtTextOutA;
|
|
|
|
// Keep the size of the record intact because of the spacing vector
|
|
this->PlayRecord();
|
|
}
|
|
}
|
|
}
|
|
|
|
VOID EmfEnumState::Rectangle()
|
|
{
|
|
// On NT convert the rectangle call to a polygon call because rectangle
|
|
// seem to have a special case that can draw outside of the metafile
|
|
// bounds. GDI seems to Ceil the coordinates instead of rounding them for
|
|
// rectangles.
|
|
|
|
if (!Globals::IsNt || IsMetafile())
|
|
{
|
|
this->PlayRecord();
|
|
return;
|
|
}
|
|
const EMRRECTANGLE *emrRect = (const EMRRECTANGLE*) GetPartialRecord();
|
|
|
|
POINT points[4] = {emrRect->rclBox.left, emrRect->rclBox.top,
|
|
emrRect->rclBox.right, emrRect->rclBox.top,
|
|
emrRect->rclBox.right, emrRect->rclBox.bottom,
|
|
emrRect->rclBox.left, emrRect->rclBox.bottom};
|
|
|
|
::Polygon(Hdc, points, 4);
|
|
return;
|
|
}
|
|
|
|
BOOL
|
|
EmfEnumState::ProcessRecord(
|
|
EmfPlusRecordType recordType,
|
|
UINT recordDataSize,
|
|
const BYTE * recordData
|
|
)
|
|
{
|
|
BOOL forceCallback = FALSE;
|
|
|
|
// See if we're doing enumeration for an external app
|
|
if (ExternalEnumeration)
|
|
{
|
|
if (recordData == NULL)
|
|
{
|
|
recordDataSize = 0;
|
|
}
|
|
else if (recordDataSize == 0)
|
|
{
|
|
recordData = NULL;
|
|
}
|
|
|
|
// See if the app changed the record at all.
|
|
if ((recordType != RecordType) ||
|
|
(recordDataSize != RecordDataSize) ||
|
|
((recordDataSize > 0) &&
|
|
((CurrentEmfRecord == NULL) ||
|
|
(recordData != (const BYTE *)(const BYTE *)CurrentEmfRecord->dParm))))
|
|
{
|
|
// Yes, we need to override what happened in StartRecord
|
|
CurrentEmfRecord = NULL;
|
|
RecordType = recordType;
|
|
RecordData = recordData;
|
|
RecordDataSize = recordDataSize;
|
|
}
|
|
}
|
|
|
|
GDIP_TRY
|
|
|
|
switch (recordType)
|
|
{
|
|
case EmfRecordTypeHeader:
|
|
this->Header();
|
|
break;
|
|
|
|
#if 0
|
|
// Do we really need to do anything for PolyPolygon records?
|
|
// If so, why not for PolyPolyline too?
|
|
case EmfRecordTypePolyPolygon:
|
|
this->PolyPolygon();
|
|
break;
|
|
|
|
case EmfRecordTypePolyPolygon16:
|
|
this->PolyPolygon16();
|
|
break;
|
|
#endif
|
|
|
|
case EmfRecordTypeExtEscape:
|
|
this->ExtEscape();
|
|
break;
|
|
|
|
case EmfRecordTypeSetPixelV:
|
|
this->SetPixelV();
|
|
break;
|
|
|
|
case EmfRecordTypeSetTextColor:
|
|
this->SetTextColor();
|
|
break;
|
|
|
|
case EmfRecordTypeSetBkColor:
|
|
this->SetBkColor();
|
|
break;
|
|
|
|
case EmfRecordTypeSetMetaRgn:
|
|
// Office Bug 154881. Win9x doesn't handle MetaRgn correctly.
|
|
if (Globals::IsNt)
|
|
{
|
|
this->PlayRecord();
|
|
}
|
|
break;
|
|
|
|
case EmfRecordTypeSaveDC:
|
|
this->SaveHdc();
|
|
break;
|
|
|
|
case EmfRecordTypeRestoreDC:
|
|
this->RestoreHdc();
|
|
break;
|
|
|
|
case EmfRecordTypeCreatePen:
|
|
this->CreatePen();
|
|
break;
|
|
|
|
case EmfRecordTypeCreateBrushIndirect:
|
|
this->CreateBrushIndirect();
|
|
break;
|
|
|
|
case EmfRecordTypeSelectPalette:
|
|
// 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(((UINT32 *)recordData)[0]);
|
|
break;
|
|
|
|
case EmfRecordTypeRealizePalette:
|
|
// We don't want to invalidate our halftoning palette by realizing one
|
|
// from a metafile.
|
|
break;
|
|
|
|
case EmfRecordTypeExtFloodFill:
|
|
this->ExtFloodFill();
|
|
break;
|
|
|
|
case EmfRecordTypeGdiComment:
|
|
this->GdiComment();
|
|
break;
|
|
|
|
case EmfRecordTypeBitBlt:
|
|
this->BitBlt();
|
|
forceCallback = TRUE;
|
|
break;
|
|
|
|
case EmfRecordTypeStretchBlt:
|
|
this->StretchBlt();
|
|
forceCallback = TRUE;
|
|
break;
|
|
|
|
case EmfRecordTypeMaskBlt:
|
|
this->MaskBlt();
|
|
forceCallback = TRUE;
|
|
break;
|
|
|
|
case EmfRecordTypePlgBlt:
|
|
this->PlgBlt();
|
|
forceCallback = TRUE;
|
|
break;
|
|
|
|
case EmfRecordTypeSetDIBitsToDevice:
|
|
this->SetDIBitsToDevice();
|
|
forceCallback = TRUE;
|
|
break;
|
|
|
|
case EmfRecordTypeStretchDIBits:
|
|
this->StretchDIBits();
|
|
forceCallback = TRUE;
|
|
break;
|
|
|
|
// case EMR_CREATEMONOBRUSH:
|
|
// A monochrome brush uses the text color and the background color,
|
|
// so we shouldn't need to make any changes to the brush itself.
|
|
|
|
case EmfRecordTypeCreateDIBPatternBrushPt:
|
|
this->CreateDibPatternBrushPt();
|
|
break;
|
|
|
|
case EmfRecordTypeExtCreatePen:
|
|
this->ExtCreatePen();
|
|
break;
|
|
|
|
case EmfRecordTypeSetICMMode:
|
|
case EmfRecordTypeCreateColorSpace:
|
|
case EmfRecordTypeSetColorSpace:
|
|
case EmfRecordTypeDeleteColorSpace:
|
|
case EmfRecordTypeSetICMProfileA:
|
|
case EmfRecordTypeSetICMProfileW:
|
|
case EmfRecordTypeCreateColorSpaceW:
|
|
if (Globals::IsNt ||
|
|
(!this->IsScreen() && !this->IsBitmap()))
|
|
{
|
|
this->PlayRecord();
|
|
}
|
|
// else skip the record
|
|
break;
|
|
|
|
case EmfRecordTypeAlphaBlend:
|
|
this->AlphaBlend();
|
|
forceCallback = TRUE;
|
|
break;
|
|
|
|
case EmfRecordTypeTransparentBlt:
|
|
this->TransparentBlt();
|
|
forceCallback = TRUE;
|
|
break;
|
|
|
|
case EmfRecordTypeGradientFill:
|
|
this->GradientFill();
|
|
forceCallback = TRUE;
|
|
break;
|
|
|
|
case EmfRecordTypeExtCreateFontIndirect:
|
|
this->ExtCreateFontIndirect();
|
|
break;
|
|
|
|
case EmfRecordTypeSelectObject:
|
|
this->SelectObject();
|
|
break;
|
|
|
|
case EmfRecordTypeSelectClipPath:
|
|
case EmfRecordTypeExtSelectClipRgn:
|
|
case EmfRecordTypeOffsetClipRgn:
|
|
this->PlayRecord();
|
|
if (!Globals::IsNt)
|
|
{
|
|
this->IntersectDestRect();
|
|
}
|
|
break;
|
|
|
|
case EmfRecordTypeSetROP2:
|
|
this->SetROP2();
|
|
break;
|
|
|
|
case EmfRecordTypeFillRgn:
|
|
case EmfRecordTypeFrameRgn:
|
|
case EmfRecordTypeInvertRgn:
|
|
case EmfRecordTypePaintRgn:
|
|
this->ResetRecordBounds();
|
|
this->PlayRecord();
|
|
break;
|
|
|
|
case EmfRecordTypeExtTextOutW:
|
|
this->ExtTextOutW();
|
|
break;
|
|
|
|
case EmfRecordTypeRectangle:
|
|
this->Rectangle();
|
|
break;
|
|
|
|
case EmfRecordTypeSetMapMode:
|
|
case EmfRecordTypeSetViewportExtEx:
|
|
case EmfRecordTypeSetViewportOrgEx:
|
|
case EmfRecordTypeSetWindowExtEx:
|
|
case EmfRecordTypeSetWindowOrgEx:
|
|
case EmfRecordTypePolyBezier:
|
|
case EmfRecordTypePolygon:
|
|
case EmfRecordTypePolyline:
|
|
case EmfRecordTypePolyBezierTo:
|
|
case EmfRecordTypePolyLineTo:
|
|
case EmfRecordTypePolyPolyline:
|
|
case EmfRecordTypePolyPolygon:
|
|
case EmfRecordTypeSetBrushOrgEx:
|
|
case EmfRecordTypeEOF:
|
|
case EmfRecordTypeSetMapperFlags:
|
|
case EmfRecordTypeSetBkMode:
|
|
case EmfRecordTypeSetPolyFillMode:
|
|
case EmfRecordTypeSetStretchBltMode:
|
|
case EmfRecordTypeSetTextAlign:
|
|
case EmfRecordTypeSetColorAdjustment:
|
|
case EmfRecordTypeMoveToEx:
|
|
case EmfRecordTypeExcludeClipRect:
|
|
case EmfRecordTypeIntersectClipRect:
|
|
case EmfRecordTypeScaleViewportExtEx:
|
|
case EmfRecordTypeScaleWindowExtEx:
|
|
case EmfRecordTypeSetWorldTransform:
|
|
case EmfRecordTypeModifyWorldTransform:
|
|
case EmfRecordTypeDeleteObject:
|
|
case EmfRecordTypeAngleArc:
|
|
case EmfRecordTypeEllipse:
|
|
case EmfRecordTypeRoundRect:
|
|
case EmfRecordTypeArc:
|
|
case EmfRecordTypeChord:
|
|
case EmfRecordTypePie:
|
|
case EmfRecordTypeCreatePalette:
|
|
case EmfRecordTypeSetPaletteEntries:
|
|
case EmfRecordTypeResizePalette:
|
|
case EmfRecordTypeLineTo:
|
|
case EmfRecordTypeArcTo:
|
|
case EmfRecordTypePolyDraw:
|
|
case EmfRecordTypeSetArcDirection:
|
|
case EmfRecordTypeSetMiterLimit:
|
|
case EmfRecordTypeBeginPath:
|
|
case EmfRecordTypeEndPath:
|
|
case EmfRecordTypeCloseFigure:
|
|
case EmfRecordTypeFillPath:
|
|
case EmfRecordTypeStrokeAndFillPath:
|
|
case EmfRecordTypeStrokePath:
|
|
case EmfRecordTypeFlattenPath:
|
|
case EmfRecordTypeWidenPath:
|
|
case EmfRecordTypeAbortPath:
|
|
case EmfRecordTypeReserved_069:
|
|
case EmfRecordTypeExtTextOutA:
|
|
case EmfRecordTypePolyBezier16:
|
|
case EmfRecordTypePolygon16:
|
|
case EmfRecordTypePolyline16:
|
|
case EmfRecordTypePolyBezierTo16:
|
|
case EmfRecordTypePolylineTo16:
|
|
case EmfRecordTypePolyPolyline16:
|
|
case EmfRecordTypePolyPolygon16:
|
|
case EmfRecordTypePolyDraw16:
|
|
case EmfRecordTypeCreateMonoBrush:
|
|
case EmfRecordTypePolyTextOutA:
|
|
case EmfRecordTypePolyTextOutW:
|
|
case EmfRecordTypeGLSRecord:
|
|
case EmfRecordTypeGLSBoundedRecord:
|
|
case EmfRecordTypePixelFormat:
|
|
case EmfRecordTypeDrawEscape:
|
|
case EmfRecordTypeStartDoc:
|
|
case EmfRecordTypeSmallTextOut:
|
|
case EmfRecordTypeForceUFIMapping:
|
|
case EmfRecordTypeNamedEscape:
|
|
case EmfRecordTypeColorCorrectPalette:
|
|
case EmfRecordTypeSetLayout:
|
|
case EmfRecordTypeReserved_117:
|
|
case EmfRecordTypeSetLinkedUFIs:
|
|
case EmfRecordTypeSetTextJustification:
|
|
case EmfRecordTypeColorMatchToTargetW:
|
|
// Play the current record.
|
|
// Even if it fails, we keep playing the rest of the metafile.
|
|
this->PlayRecord();
|
|
break;
|
|
|
|
default:
|
|
// unknown record -- ignore it
|
|
WARNING1("Unknown EMF Record");
|
|
break;
|
|
}
|
|
|
|
GDIP_CATCH
|
|
forceCallback = TRUE;
|
|
GDIP_ENDCATCH
|
|
|
|
return forceCallback;
|
|
}
|
|
|
|
VOID EmfEnumState::ResetRecordBounds()
|
|
{
|
|
if (Globals::IsNt && IsMetafile())
|
|
{
|
|
if (ModifiedEmfRecord == NULL)
|
|
{
|
|
CreateCopyOfCurrentRecord();
|
|
}
|
|
// In case the previous call failed
|
|
if (ModifiedEmfRecord != NULL)
|
|
{
|
|
RECTL rect = {INT_MIN, INT_MIN, INT_MAX, INT_MAX};
|
|
EMRRCLBOUNDS *record = (EMRRCLBOUNDS*) ModifiedEmfRecord;
|
|
record->rclBounds = rect;
|
|
}
|
|
}
|
|
}
|