/**************************************************************************\ * * Copyright (c) 1999 Microsoft Corporation * * Module Name: * * MetaFile.cpp * * Abstract: * * Metafile object handling * * Created: * * 4/14/1999 DCurtis * \**************************************************************************/ #include "precomp.hpp" #include "MetaWmf.hpp" #define GDIP_TRANSPARENT_COLOR_KEY 0xAA0D0B0C #define GDIP_WMF_PLACEABLEKEY 0x9AC6CDD7 // for Placeable WMFs #define GDIP_DO_CALLBACK_MASK 0x00000003 // when to do callback // Metafile constants not in Windows.h #define METAVERSION300 0x0300 #define METAVERSION100 0x0100 #define MEMORYMETAFILE 1 #define DISKMETAFILE 2 typedef VOID (EmfPlusRecordPlay::*PLAYRECORDFUNC)(MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize) const; PLAYRECORDFUNC RecordPlayFuncs[]; /**************************************************************************\ * * Function Description: * * If the points were stored as 16-bit points, then convert them back to * REAL points. Otherwise, just return convert the point data pointer * to a REAL point pointer and return. * * Arguments: * * [IN] pointData - the point data that was recorded * [IN] count - the number of points * [IN] flags - says if the point data is 16-bit points or not * [IN] bufferSize - the size of the buffer * [IN/OUT] buffer - for converting back to REAL points * [IN/OUT] allocedBuffer - if buffer not big enough, alloc new one here * * Return Value: * * GpPointF * - the REAL points to play back * * Created: * * 6/15/1999 DCurtis * \**************************************************************************/ GpPointF * GetPointsForPlayback( const BYTE * pointData, UINT pointDataSize, INT count, INT flags, UINT bufferSize, BYTE * buffer, BYTE * & allocedBuffer ) { GpPointF * points = NULL; if (count > 0) { if ((flags & GDIP_EPRFLAGS_COMPRESSED) != 0) { if (pointDataSize >= (sizeof(GpPoint16) * count)) { UINT sizePoints = count * sizeof(GpPointF); if (sizePoints <= bufferSize) { points = reinterpret_cast(buffer); } else { if ((allocedBuffer = new BYTE[sizePoints]) == NULL) { return NULL; } points = reinterpret_cast(allocedBuffer); } const GpPoint16 * points16 = reinterpret_cast(pointData); do { count--; points[count].X = points16[count].X; points[count].Y = points16[count].Y; } while (count > 0); } else { WARNING(("pointDataSize is too small")); } } else if (pointDataSize >= (sizeof(GpPointF) * count)) { points = (GpPointF *)(pointData); } else { WARNING(("pointDataSize is too small")); } } return points; } /**************************************************************************\ * * Function Description: * * If the rects were stored as 16-bit rects, then convert them back to * REAL rects. Otherwise, just return convert the rect data pointer * to a REAL rect pointer and return. * * Arguments: * * [IN] rectData - the rect data that was recorded * [IN] count - the number of rects * [IN] flags - says if the point data is 16-bit rects or not * [IN] bufferSize - the size of the buffer * [IN/OUT] buffer - for converting back to REAL rects * [IN/OUT] allocedBuffer - if buffer not big enough, alloc new one here * * Return Value: * * GpPointF * - the REAL points to play back * * Created: * * 6/15/1999 DCurtis * \**************************************************************************/ GpRectF * GetRectsForPlayback( const BYTE * rectData, UINT rectDataSize, INT count, INT flags, UINT bufferSize, BYTE * buffer, BYTE * & allocedBuffer ) { GpRectF * rects = NULL; if (count > 0) { if ((flags & GDIP_EPRFLAGS_COMPRESSED) != 0) { if (rectDataSize >= (sizeof(GpRect16) * count)) { UINT sizeRects = count * sizeof(GpRectF); if (sizeRects <= bufferSize) { rects = reinterpret_cast(buffer); } else { if ((allocedBuffer = new BYTE[sizeRects]) == NULL) { return NULL; } rects = reinterpret_cast(allocedBuffer); } const GpRect16 * rects16 = reinterpret_cast(rectData); do { count--; rects[count].X = rects16[count].X; rects[count].Y = rects16[count].Y; rects[count].Width = rects16[count].Width; rects[count].Height = rects16[count].Height; } while (count > 0); } else { WARNING(("rectDataSize is too small")); } } else if (rectDataSize >= (sizeof(GpRectF) * count)) { rects = (GpRectF *)(rectData); } else { WARNING(("rectDataSize is too small")); } } return rects; } inline INT16 GetWmfPlaceableCheckSum( const WmfPlaceableFileHeader * wmfPlaceableFileHeader ) { const INT16 * headerWords = (const INT16 *)wmfPlaceableFileHeader; INT16 checkSum = *headerWords++; for (INT i = 9; i > 0; i--) { checkSum ^= *headerWords++; } return checkSum; } inline BOOL WmfPlaceableHeaderIsValid( const WmfPlaceableFileHeader * wmfPlaceableFileHeader ) { ASSERT(wmfPlaceableFileHeader != NULL); return ((wmfPlaceableFileHeader->Key == GDIP_WMF_PLACEABLEKEY) && (wmfPlaceableFileHeader->Checksum == GetWmfPlaceableCheckSum(wmfPlaceableFileHeader)) && (wmfPlaceableFileHeader->BoundingBox.Left != wmfPlaceableFileHeader->BoundingBox.Right) && (wmfPlaceableFileHeader->BoundingBox.Top != wmfPlaceableFileHeader->BoundingBox.Bottom)); } inline BOOL WmfHeaderIsValid( const METAHEADER * wmfHeader ) { return (((wmfHeader->mtType == MEMORYMETAFILE) || (wmfHeader->mtType == DISKMETAFILE)) && (wmfHeader->mtHeaderSize == (sizeof(METAHEADER)/sizeof(WORD))) && ((wmfHeader->mtVersion == METAVERSION300) || (wmfHeader->mtVersion ==METAVERSION100))); } VOID Init32BppDibToTransparent( UINT32 * bits, UINT numPixels ); GpStatus Draw32BppDib( GpGraphics * g, UINT32 * bits, INT width, INT height, const GpRectF & destRect, REAL dpi, BOOL compareAlpha ); extern "C" BOOL CALLBACK GdipPlayMetafileRecordCallback( EmfPlusRecordType recordType, UINT recordFlags, UINT recordDataSize, const BYTE * recordData, VOID * callbackData // player ); // This method (defined below) enumerates/plays EMF+ comment records and also // plays down-level GDI records, when appropriate. extern "C" int CALLBACK EnumEmfWithDownLevel( HDC hdc, HANDLETABLE FAR * gdiHandleTable, CONST ENHMETARECORD * emfRecord, int numHandles, LPARAM play ); extern "C" int CALLBACK EnumEmfDownLevel( HDC hdc, HANDLETABLE FAR * gdiHandleTable, CONST ENHMETARECORD * emfRecord, int numHandles, LPARAM play ); extern "C" int CALLBACK EnumEmfToStream( HDC hdc, HANDLETABLE FAR * gdiHandleTable, CONST ENHMETARECORD * emfRecord, int numHandles, LPARAM stream ); // Separate this out so we can initialize it to 0 all at once class MetafilePlayerBuffers { protected: BYTE RecordBuffer [GDIP_METAFILE_BUFFERSIZE]; BYTE PointsBuffer [GDIP_METAFILE_BUFFERSIZE]; GpObject * ObjectList [GDIP_MAX_OBJECTS]; INT MemberStack [GDIP_SAVE_STACK_SIZE]; GpObject * BackupObject [ObjectTypeMax - ObjectTypeMin + 1]; }; class MetafilePlayer : public MetafilePlayerBuffers { protected: BOOL Valid; UINT32 MaxStackSize; INT * Stack; IStream * Stream; BYTE * RecordAllocedBuffer; BYTE * PointsAllocedBuffer; GpSolidFill SolidBrush; BYTE * ConcatRecordBuffer; INT ConcatRecordBufferSize; BYTE * ConcatRecord; INT ConcatRecordTotalSize; INT ConcatRecordSize; UINT ConcatRecordFlags; InterpolationMode Interpolation; public: GpGraphics * Graphics; // The graphics we're playing to BOOL PlayEMFRecords; // TRUE when we see GetDC record HDC Hdc; // For playing downlevel records GpMatrix PreContainerMatrix; // Xform to use for down-level UINT32 * BitmapBits; INT BitmapWidth; INT BitmapHeight; GpRectF BitmapDestRect; REAL BitmapDpi; GpRecolor * Recolor; MfEnumState * MfState; ColorAdjustType AdjustType; UINT MultiFormatSection; UINT CurFormatSection; BOOL PlayMultiFormatSection; EnumerateMetafileProc EnumerateCallback; // for metafile enumeration VOID * CallbackData; // for metafile enumeration BOOL EnumerateAborted; DrawImageAbort DrawImageCallback; VOID* DrawImageCallbackData; INT DrawImageCallbackCount; BOOL RopUsed; public: // stream is NULL if using GDI to enumerate the hEmf. MetafilePlayer( GpGraphics * g, UINT maxStackSize, GpRecolor * recolor, ColorAdjustType adjustType, EnumerateMetafileProc enumerateCallback, VOID * callbackData, DrawImageAbort drawImageCallback, VOID* drawImageCallbackData ); ~MetafilePlayer(); BOOL IsValid() const { return Valid; } VOID PrepareToPlay( GpGraphics * g, GpRecolor * recolor, ColorAdjustType adjustType, EnumerateMetafileProc enumerateCallback, VOID * callbackData, DrawImageAbort drawImageCallback, VOID* drawImageCallbackData ); VOID DonePlaying(); VOID InitForDownLevel() { if (Hdc == NULL) { Hdc = Graphics->GetHdc(); ASSERT(Hdc != NULL); if (BitmapBits != NULL) { Init32BppDibToTransparent(BitmapBits, BitmapWidth*BitmapHeight); MfState->ResetRopUsed(); } } } VOID DoneWithDownLevel() { PlayEMFRecords = FALSE; if (Hdc != NULL) { Graphics->ReleaseHdc(Hdc); Hdc = NULL; if (BitmapBits != NULL) { // This is a hack to get around the problem that we are // inside a container, but we don't want to be in the // container for drawing the down-level records. We also // don't want any transforms inside the EMF+ to affect the // down-level records. // We should probably do something about the clipping too, // but for now, we won't worry about it. GpMatrix saveWorldToDevice = Graphics->Context->WorldToDevice; Graphics->Context->WorldToDevice = PreContainerMatrix; // Don't use NearestNeighbor to draw the rotated metafile -- // it looks bad, and doesn't really save any time. InterpolationMode saveInterpolationMode = Graphics->Context->FilterType; if (saveInterpolationMode == InterpolationModeNearestNeighbor) { Graphics->Context->FilterType = InterpolationModeBilinear; } Graphics->Context->InverseOk = FALSE; Draw32BppDib(Graphics, BitmapBits, BitmapWidth, BitmapHeight, BitmapDestRect, BitmapDpi, !RopUsed); // restore the interpolation mode (in case we changed it). Graphics->Context->FilterType = saveInterpolationMode; Graphics->Context->WorldToDevice = saveWorldToDevice; Graphics->Context->InverseOk = FALSE; } } } // returns 0 to abort playback, 1 to continue INT ProcessDrawImageCallback( BOOL forceCallback ) { if (DrawImageCallback) { // A DrawImage record could have already been aborted, so // we should immediately return. if (EnumerateAborted) { return 0; // abort } if (forceCallback) { DrawImageCallbackCount = 0; } if ((DrawImageCallbackCount++ & GDIP_DO_CALLBACK_MASK) == 0) { // The callback returns TRUE to abort, FALSE to continue. return ((*DrawImageCallback)(DrawImageCallbackData)) ? 0 : 1; } } return 1; } GpPointF * GetPoints( const BYTE * pointData, UINT pointDataSize, INT count, INT flags ) { return GetPointsForPlayback(pointData, pointDataSize, count, flags, GDIP_METAFILE_BUFFERSIZE, PointsBuffer, PointsAllocedBuffer); } GpRectF * GetRects( const BYTE * rectData, UINT rectDataSize, INT count, INT flags ) { return GetRectsForPlayback(rectData, rectDataSize, count, flags, GDIP_METAFILE_BUFFERSIZE, PointsBuffer, PointsAllocedBuffer); } GpObject * GetObject( UINT metaObjectId, ObjectType objectType ); GpBrush * GetBrush( UINT brushValue, INT flags ); GpString * GetString( const BYTE * stringData, INT len, INT flags ) { // !!! convert back from 8-bit to 16-bit chars if necessary return new GpString((const WCHAR *)stringData, len); } VOID AddObject( INT flags, const BYTE * data, UINT dataSize ); VOID NewSave( UINT stackIndex, INT saveID ); INT GetSaveID( UINT stackIndex ); VOID FreePointsBuffer() { if (PointsAllocedBuffer != NULL) { delete [] PointsAllocedBuffer; PointsAllocedBuffer = NULL; } } GpStatus ConcatenateRecords( UINT recordFlags, INT recordDataSize, const BYTE * recordData ); GpStatus EnumerateEmfPlusRecords( UINT dataSize, // size of EMF+ record data const BYTE * data // pointer to the EMF+ record data ); GpStatus EnumerateEmfRecords( HDC hdc, HENHMETAFILE hEmf, const RECT * dest, const RECT * deviceRect, ENHMFENUMPROC enumProc ); GpStatus EnumerateWmfRecords( HDC hdc, HMETAFILE hWmf, const RECT * dstRect, const RECT * deviceRect ); }; VOID MetafilePlayer::PrepareToPlay( GpGraphics * g, GpRecolor * recolor, ColorAdjustType adjustType, EnumerateMetafileProc enumerateCallback, VOID * callbackData, DrawImageAbort drawImageCallback, VOID* drawImageCallbackData ) { ASSERT(g != NULL); GpMemset(Stack, 0, MaxStackSize * sizeof (INT)); // Initialize all the buffers to 0 MetafilePlayerBuffers * buffers = this; GpMemset(buffers, 0, sizeof(MetafilePlayerBuffers)); PlayEMFRecords = FALSE; Hdc = NULL; Graphics = g; BitmapBits = NULL; BitmapWidth = 0; BitmapHeight = 0; Interpolation = g->GetInterpolationMode(); Recolor = recolor; AdjustType = adjustType; MultiFormatSection = 0; CurFormatSection = 0; PlayMultiFormatSection = TRUE; EnumerateAborted = FALSE; RopUsed = FALSE; if (enumerateCallback == NULL) { EnumerateCallback = GdipPlayMetafileRecordCallback; CallbackData = this; } else { EnumerateCallback = enumerateCallback; CallbackData = callbackData; } DrawImageCallback = drawImageCallback; DrawImageCallbackData = drawImageCallbackData; DrawImageCallbackCount = 0; ConcatRecord = NULL; ConcatRecordTotalSize = 0; ConcatRecordSize = 0; ConcatRecordFlags = 0; // We need this for rendering GDI records within a GDI+ file. // We have to do it before starting the container. g->GetWorldToDeviceTransform(&(this->PreContainerMatrix)); } MetafilePlayer::MetafilePlayer( GpGraphics * g, UINT maxStackSize, GpRecolor * recolor, ColorAdjustType adjustType, EnumerateMetafileProc enumerateCallback, VOID * callbackData, DrawImageAbort drawImageCallback, VOID* drawImageCallbackData ) { Valid = FALSE; MaxStackSize = GDIP_SAVE_STACK_SIZE; Stack = MemberStack; if (maxStackSize > GDIP_SAVE_STACK_SIZE) { Stack = new INT[maxStackSize]; if (Stack == NULL) { return; // Valid is FALSE } MaxStackSize = maxStackSize; } RecordAllocedBuffer = NULL; PointsAllocedBuffer = NULL; Recolor = NULL; MfState = NULL; ConcatRecordBuffer = NULL; ConcatRecordBufferSize = 0; PrepareToPlay(g, recolor, adjustType, enumerateCallback, callbackData, drawImageCallback, drawImageCallbackData ); Valid = TRUE; } MetafilePlayer::~MetafilePlayer() { if (Stack != MemberStack) { delete [] Stack; } if (ConcatRecordBuffer) { GpFree(ConcatRecordBuffer); } } inline bool ObjectTypeIsText(ObjectType type) { return type == ObjectTypeFont || type == ObjectTypeStringFormat; } VOID MetafilePlayer::DonePlaying() { INT i; i = 0; do { GpObject* pObject = ObjectList[i]; if (pObject) { GlobalTextLockConditional(ObjectTypeIsText(pObject->GetObjectType())); delete pObject; } } while ((++i) < GDIP_MAX_OBJECTS); } GpObject * MetafilePlayer::GetObject( UINT metaObjectId, ObjectType objectType ) { GpObject * object = NULL; // If the object was an unused optional parameter of some kind // it knows how to handle a NULL object, so we return that. if(metaObjectId == GDIP_OBJECTID_NONE) { return NULL; } ASSERT(metaObjectId < GDIP_MAX_OBJECTS); if (metaObjectId < GDIP_MAX_OBJECTS) { object = ObjectList[metaObjectId]; ASSERT (object != NULL); if (object != NULL) { ASSERT(object->GetObjectType() == objectType); if (object->GetObjectType() == objectType) { return object; } } } if (ObjectTypeIsValid(objectType)) { return BackupObject[objectType - ObjectTypeMin]; } return NULL; } GpBrush * MetafilePlayer::GetBrush( UINT brushValue, INT flags ) { GpBrush * brush; if ((flags & GDIP_EPRFLAGS_SOLIDCOLOR) != 0) { brush = &SolidBrush; (reinterpret_cast(brush))->SetColor(GpColor(brushValue)); if (Recolor != NULL) { brush->ColorAdjust(Recolor, AdjustType); } } else { brush = (GpBrush *)this->GetObject(brushValue, ObjectTypeBrush); } return brush; } VOID MetafilePlayer::AddObject( INT flags, const BYTE * data, UINT dataSize ) { ObjectType objectType = GetObjectType(flags); UINT objectId = GetMetaObjectId(flags); GpObject ** objectList = ObjectList; ASSERT((objectId < GDIP_MAX_OBJECTS) || (objectId == GDIP_BACKUP_OBJECTID)); GlobalTextLockConditional(ObjectTypeIsText(objectType)); // First see if this is a backup object if ((objectId == GDIP_BACKUP_OBJECTID) && ObjectTypeIsValid(objectType)) { objectList = BackupObject; objectId = objectType - ObjectTypeMin; } if (objectId < GDIP_MAX_OBJECTS) { GpObject * object = objectList[objectId]; if (object != NULL) { object->Dispose(); } object = GpObject::Factory(objectType, (const ObjectData *)data, dataSize); if (object) { if (object->SetData(data, dataSize) == Ok) { if (Recolor != NULL) { object->ColorAdjust(Recolor, AdjustType); } if (!object->IsValid()) { WARNING(("Object is not valid")); object->Dispose(); object = NULL; } } else { WARNING(("Object Set Data failed")); object->Dispose(); object = NULL; } } else { WARNING(("Object Factory failed to create object")); } objectList[objectId] = object; } } VOID MetafilePlayer::NewSave( UINT stackIndex, INT saveID ) { if (stackIndex >= MaxStackSize) { UINT maxStackSize = MaxStackSize + GDIP_SAVE_STACK_SIZE; if (stackIndex >= maxStackSize) { ASSERT (0); return; } INT * newStack = new INT[maxStackSize]; if (newStack == NULL) { return; } GpMemcpy(newStack, Stack, MaxStackSize * sizeof(INT)); GpMemset(newStack + MaxStackSize, 0, GDIP_SAVE_STACK_SIZE * sizeof (INT)); MaxStackSize = maxStackSize; if (Stack != MemberStack) { delete [] Stack; } Stack = newStack; } Stack[stackIndex] = saveID; } INT MetafilePlayer::GetSaveID( UINT stackIndex ) { ASSERT(stackIndex < MaxStackSize); INT saveID = 0; if (stackIndex < MaxStackSize) { saveID = Stack[stackIndex]; Stack[stackIndex] = 0; } return saveID; } GpStatus MetafilePlayer::ConcatenateRecords( UINT recordFlags, INT recordDataSize, const BYTE * recordData ) { ASSERT((recordData != NULL) && (recordDataSize > sizeof(INT32))); GpStatus status = Ok; if ((recordFlags & GDIP_EPRFLAGS_CONTINUEOBJECT) != 0) { INT dataSizeLeft = ((const INT32 *)recordData)[0]; recordData += sizeof(INT32); recordDataSize -= sizeof(INT32); if (dataSizeLeft <= recordDataSize) { WARNING(("Total Data Size incorrect")); status = InvalidParameter; goto DoneWithRecord; } recordFlags &= ~GDIP_EPRFLAGS_CONTINUEOBJECT; if (ConcatRecord == NULL) { if ((ConcatRecordBuffer == NULL) || (ConcatRecordBufferSize < dataSizeLeft)) { GpFree(ConcatRecordBuffer); ConcatRecordBuffer = (BYTE *)GpMalloc(dataSizeLeft); if (ConcatRecordBuffer == NULL) { ConcatRecordBufferSize = 0; return OutOfMemory; } ConcatRecordBufferSize = dataSizeLeft; } ConcatRecord = ConcatRecordBuffer; ConcatRecordTotalSize = dataSizeLeft; ConcatRecordSize = 0; ConcatRecordFlags = recordFlags; goto SkipContinueChecks; } } if (recordFlags != ConcatRecordFlags) { WARNING(("Record headers do not match")); status = InvalidParameter; goto DoneWithRecord; } SkipContinueChecks: if (recordDataSize + ConcatRecordSize > ConcatRecordTotalSize) { WARNING(("sizes do not match")); recordDataSize = ConcatRecordTotalSize - ConcatRecordSize; } GpMemcpy(ConcatRecord + ConcatRecordSize, recordData, recordDataSize); ConcatRecordSize += recordDataSize; // see if we're done concatenating this record if (ConcatRecordSize >= ConcatRecordTotalSize) { if (EnumerateCallback(EmfPlusRecordTypeObject, recordFlags, ConcatRecordTotalSize, ConcatRecord, CallbackData) == 0) { status = Aborted; } DoneWithRecord: ConcatRecord = NULL; ConcatRecordTotalSize = 0; ConcatRecordSize = 0; ConcatRecordFlags = 0; } return status; } // Enumerate a set of EMF+ record contained inside an EMF comment record // which has been enumerated from an EMF file. // // NOTE that we can't change the metafile data. If we need to change it, // we must change a copy of it. GpStatus MetafilePlayer::EnumerateEmfPlusRecords( UINT dataSize, // size of EMF+ record data const BYTE * data // pointer to the EMF+ record data ) { ASSERT((dataSize > 0) && (data != NULL)); UINT curSize = 0; UINT recordSize; EmfPlusRecordType recordType; UINT recordFlags; UINT recordDataSize; const BYTE * recordData; // while there is at least one record header size left while (curSize <= (dataSize - sizeof(EmfPlusRecord))) { recordSize = ((const EmfPlusRecord *)data)->Size; recordDataSize = recordSize - sizeof(EmfPlusRecord); // Make sure we don't read past the end of the buffer // and make sure the size field is valid. if ((recordSize >= sizeof(EmfPlusRecord)) && ((curSize + recordSize) <= dataSize) && (recordDataSize == ((const EmfPlusRecord *)data)->DataSize)) { recordType = (EmfPlusRecordType)(((const EmfPlusRecord *)data)->Type); // make sure the recordType is in some reasonable range // before we enumerate this record if ((recordType >= EmfPlusRecordTypeMin) && (recordType < (EmfPlusRecordTypeMax + 1000))) { recordFlags = ((const EmfPlusRecord *)data)->Flags; if (recordDataSize == 0) { recordData = NULL; } else { recordData = data + sizeof(EmfPlusRecord); // if this object record is spread over several GDI comment // records, then we need to concatenate them together before // giving it to the callback // The GDIP_EPRFLAGS_CONTINUEOBJECT flag is only valid // with object records (since that bit is reused for other // flags with other record types). if ((recordType == EmfPlusRecordTypeObject) && (((recordFlags & GDIP_EPRFLAGS_CONTINUEOBJECT) != 0) || (ConcatRecord != NULL))) { if (this->ConcatenateRecords(recordFlags, recordDataSize, recordData) == Aborted) { return Aborted; } goto Increment; } } if (EnumerateCallback(recordType, recordFlags, recordDataSize, recordData, CallbackData) == 0) { return Aborted; } } else { WARNING1("Bad EMF+ record type"); } Increment: data += recordSize; curSize += recordSize; // We have to set this here, because if we are just enumerating // for an application (not playing), then the GetDCEPR::Play // method will never be hit, so it will never get set! if (recordType == EmfPlusRecordTypeGetDC) { // Flag that the next down-level records should be played. PlayEMFRecords = TRUE; } } else { WARNING1("Bad EMF+ record size"); return InvalidParameter; } } return Ok; } // Callback for EnumerateMetafile methods. The parameters are: // recordType (if >= EmfPlusRecordTypeMin, it's an EMF+ record) // flags (always 0 for EMF records) // dataSize size of the data, or 0 if no data // data pointer to the data, or NULL if no data (UINT32 aligned) // callbackData pointer to callbackData, if any // This method can then call Metafile::PlayRecord to play the // record that was just enumerated. If this method returns // FALSE, the enumeration process is aborted. Otherwise, it continues. extern "C" BOOL CALLBACK GdipPlayMetafileRecordCallback( EmfPlusRecordType recordType, UINT recordFlags, UINT recordDataSize, const BYTE * recordData, VOID * callbackData // player ) { MetafilePlayer * player = (MetafilePlayer *)callbackData; // See if it is an EMF+ record if ((recordType >= EmfPlusRecordTypeMin) && (recordType <= EmfPlusRecordTypeMax)) { if (player->PlayMultiFormatSection) { (((const EmfPlusRecordPlay *)recordData)->*RecordPlayFuncs[recordType-EmfPlusRecordTypeMin])(player, recordType, recordFlags, recordDataSize); return player->ProcessDrawImageCallback(FALSE); } return 1; } // See if we should play the WMF or EMF record // Always play the header and EOF EMF records if (player->PlayEMFRecords || (recordType == EmfRecordTypeHeader) || (recordType == EmfRecordTypeEOF)) { ASSERT(player->MfState != NULL); BOOL forceCallback = player->MfState->ProcessRecord( recordType, recordDataSize, recordData); return player->ProcessDrawImageCallback(forceCallback); } ASSERT (0); // shouldn't get here unless caller is doing something strange return 1; // Keep playing } GpStatus GpMetafile::PlayRecord( EmfPlusRecordType recordType, UINT recordFlags, UINT recordDataSize, // must be multiple of 4 for EMF const BYTE * recordData ) const { if ((State != PlayingMetafileState) || (((recordDataSize & 0x03) != 0) && (!GDIP_IS_WMF_RECORDTYPE(recordType)))) { return InvalidParameter; } ASSERT(Player != NULL); GdipPlayMetafileRecordCallback( recordType, recordFlags, recordDataSize, recordData, Player ); return Ok; } inline BOOL IsEmfPlusRecord( CONST ENHMETARECORD * emfRecord ) { // dParm[0] is the comment data size return ((emfRecord->iType == EMR_GDICOMMENT) && (emfRecord->nSize >= (sizeof(EMR) + (2 * sizeof(DWORD)))) && (emfRecord->dParm[1] == EMFPLUS_SIGNATURE)); } // This method enumerates/plays EMF+ comment records and also // plays down-level GDI records, when appropriate. extern "C" int CALLBACK EnumEmfWithDownLevel( HDC hdc, // should be non-NULL HANDLETABLE FAR * gdiHandleTable, CONST ENHMETARECORD * emfRecord, int numHandles, LPARAM play ) { if ((emfRecord != NULL) && (emfRecord->nSize >= sizeof(EMR)) && (play != NULL)) { MetafilePlayer * player = (MetafilePlayer *)play; if (IsEmfPlusRecord(emfRecord)) { // We're done displaying GDI down-level records player->DoneWithDownLevel(); // NOTE: cbData is the size of the comment data, not including // the record header and not including itself. // // Must subtract out the Signature INT dataSize = ((CONST EMRGDICOMMENT *)emfRecord)->cbData; // subtract out signature dataSize -= sizeof(INT32); if (dataSize > 0) { if (player->EnumerateEmfPlusRecords( dataSize, ((CONST EMRGDICOMMENT *)emfRecord)->Data + sizeof(INT32)) == Aborted) { player->EnumerateAborted = TRUE; return 0; } } } else { EmfPlusRecordType recordType = (EmfPlusRecordType)(emfRecord->iType); if (player->PlayEMFRecords || (recordType == EmfRecordTypeHeader) || (recordType == EmfRecordTypeEOF)) { if ((recordType != EmfRecordTypeHeader) && (recordType != EmfRecordTypeEOF)) { player->InitForDownLevel(); } INT recordDataSize = emfRecord->nSize - sizeof(EMR); const BYTE * recordData = (const BYTE *)emfRecord->dParm; if (recordDataSize <= 0) { recordDataSize = 0; recordData = NULL; } player->MfState->StartRecord(hdc, gdiHandleTable, numHandles, emfRecord, recordType, recordDataSize, recordData); if (player->EnumerateCallback(recordType, 0, recordDataSize, recordData, player->CallbackData) == 0) { player->EnumerateAborted = TRUE; return 0; } } } } else { WARNING(("Bad Enumeration Parameter")); } return 1; } #define GDIP_MAX_DIBSECTION_SIZE 1024 #define GDIP_MINSCALED_DIBSECTION_SIZE (GDIP_MAX_DIBSECTION_SIZE / 2) inline VOID AdjustForMaximumSize( LONG & bigSide, LONG & smallSide ) { // Try to keep the aspect ratio the same, // but don't let the smaller side get too small. REAL scaleFactor = GDIP_MAX_DIBSECTION_SIZE / (REAL)bigSide; bigSide = GDIP_MAX_DIBSECTION_SIZE; if (smallSide > GDIP_MINSCALED_DIBSECTION_SIZE) { smallSide = GpRound(scaleFactor * smallSide); if (smallSide < GDIP_MINSCALED_DIBSECTION_SIZE) { smallSide = GDIP_MINSCALED_DIBSECTION_SIZE; } } } // !!! If the hdc is a EMF, we really should take the rasterization limit // into account when deciding the size of the dest bitmap. static HBITMAP CreateDibSection32Bpp( HDC hdc, const GpRectF & destRect, RECT & dest, // actual dest UINT32 ** bits, REAL * dpi, // must init dpi before calling this method GpMatrix * matrix ) { GpPointF destPoints[3]; REAL width; REAL height; // When we rasterize a WMF or EMF into a Dib Section, we limit the size // so that we don't use huge amounts of memory when printing or when // drawing the rotated metafile into another metafile. *bits = NULL; // the capped dpi keeps the image from getting too large destPoints[0].X = destRect.X; destPoints[0].Y = destRect.Y; destPoints[1].X = destRect.GetRight(); destPoints[1].Y = destRect.Y; destPoints[2].X = destRect.X; destPoints[2].Y = destRect.GetBottom(); matrix->Transform(destPoints, 3); // determine the size of the image by getting the distance // between the transformed device points width = ::GetDistance(destPoints[0], destPoints[1]); height = ::GetDistance(destPoints[0], destPoints[2]); dest.left = 0; dest.top = 0; dest.right = GpRound(width); dest.bottom = GpRound(height); // make sure we don't transform down to 0 size if ((dest.right == 0) || (dest.bottom == 0)) { return NULL; } if ((dest.right > GDIP_MAX_DIBSECTION_SIZE) || (dest.bottom > GDIP_MAX_DIBSECTION_SIZE)) { REAL area = (REAL) dest.right * dest.bottom; if (dest.right >= dest.bottom) { AdjustForMaximumSize(dest.right, dest.bottom); } else { AdjustForMaximumSize(dest.bottom, dest.right); } REAL newArea = (REAL) dest.right * dest.bottom; ASSERT(newArea > 0.0f && newArea <= area); // Adjust the effective DPI of the bitmap based on how much smaller it is. *dpi = (*dpi)*newArea/area; } BITMAPINFO bmi; // Create a 32-bpp dib section so we can add alpha to it GpMemset(&bmi, 0, sizeof(bmi)); bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader); bmi.bmiHeader.biWidth = dest.right; bmi.bmiHeader.biHeight = dest.bottom; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = dest.right * dest.bottom * 4; return CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (VOID**)(bits), NULL, 0); } VOID Init32BppDibToTransparent( UINT32 * bits, UINT numPixels ) { ASSERT((bits != NULL) && (numPixels > 0)); // initialize the image to a "transparent" color while (numPixels--) { *bits++ = GDIP_TRANSPARENT_COLOR_KEY; } } GpStatus Draw32BppDib( GpGraphics * g, UINT32 * bits, INT width, INT height, const GpRectF & destRect, REAL dpi, BOOL compareAlpha ) { // Make sure Gdi is done drawing to the dib section ::GdiFlush(); // Set the alpha value to 0 whereever the transparent // color is still in the image and to FF everywhere else UINT32 * bitmapBits = bits; UINT numPixels = width * height; if (compareAlpha) { while (numPixels--) { if (*bitmapBits != GDIP_TRANSPARENT_COLOR_KEY) { *bitmapBits |= 0xFF000000; } else { *bitmapBits = 0; } bitmapBits++; } } else { while (numPixels--) { if ((*bitmapBits & 0x00FFFFFF) != (GDIP_TRANSPARENT_COLOR_KEY & 0x00FFFFFF)) { *bitmapBits |= 0xFF000000; } else { *bitmapBits = 0; } bitmapBits++; } } // Create a bitamp from the dib section memory (which // we've added alpha to). This constructor uses the // memory we give it without doing a copy. GpStatus status = GenericError; GpBitmap * bitmap = new GpBitmap(width, height, -(width * 4), PIXFMT_32BPP_PARGB, (BYTE *)(bits + (width * (height - 1)))); if (bitmap != NULL) { if (bitmap->IsValid()) { bitmap->SetResolution(dpi, dpi); // If we want the outside edges to look smooth, then we have // to outcrop both the src and dest rects (by at least a pixel). GpRectF srcRect(-1.0f, -1.0f, width + 2.0f, height + 2.0f); GpRectF outCroppedDestRect; REAL xSize; REAL ySize; g->GetWorldPixelSize(xSize, ySize); if (destRect.Width < 0.0f) { xSize = -xSize; } if (destRect.Height < 0.0f) { ySize = -ySize; } outCroppedDestRect.X = destRect.X - xSize; outCroppedDestRect.Width = destRect.Width + (xSize * 2.0f); outCroppedDestRect.Y = destRect.Y - ySize; outCroppedDestRect.Height = destRect.Height + (ySize * 2.0f); if (g->IsPrinter()) { // If the resulting transform (and source rect/dest rect) is // a rotation by 90, 180, or 270 degrees. Then flip the bitmap // appropriately. Fix up source rect, dest rect, and world to // device appropriately. Restore W2D afterwards. GpMatrix worldToDevice; g->GetWorldToDeviceTransform(&worldToDevice); // Create the entire image source to device mapping to determine // the entire rotation. GpMatrix transform; transform.InferAffineMatrix(destRect, srcRect); GpMatrix::MultiplyMatrix(transform, transform, worldToDevice); MatrixRotate rotation = transform.GetRotation(); if (rotation == MatrixRotateBy90 || rotation == MatrixRotateBy180 || rotation == MatrixRotateBy270) { // Normalize the destination rectangle TransformBounds(NULL, outCroppedDestRect.GetLeft(), outCroppedDestRect.GetTop(), outCroppedDestRect.GetRight(), outCroppedDestRect.GetBottom(), &outCroppedDestRect); // Compute the destination rectangle in device space. Transform // to device space and normalize. // We know the world transform can have a 90 degree rotation // so we need to do a point transform. We can do a 2 point // transform and get the min and the max to make the bounding // box GpRectF deviceDestRect; TransformBounds(&worldToDevice, outCroppedDestRect.GetLeft(), outCroppedDestRect.GetTop(), outCroppedDestRect.GetRight(), outCroppedDestRect.GetBottom(), &deviceDestRect); // Construct new world to page transform. Infers from the // normalized outCroppedDestRect to normalized deviceDestRect. // // The World To Device is ordinarily computed as: // // World-To-Page * Scale(PageMultipliers) * // Translate-By-Pixel-Offset * ContainerTransform // // The SetWorldTransform API only sets the World-To-Page. // // So we set the new World Transform as: // // World-To-Page * Inverse(World-To-Device)* // Transform-CroppedDestRect-To-DeviceDestRect // // The result, as you can see from substitution is just // Transform-CroppedDestRect-To-DeviceDestRect GpMatrix newTransform; newTransform.InferAffineMatrix(deviceDestRect, outCroppedDestRect); g->GetDeviceToWorldTransform(&transform); GpMatrix::MultiplyMatrix(newTransform, newTransform, transform); g->GetWorldTransform(transform); // really World To Page XForm GpMatrix::MultiplyMatrix(newTransform, newTransform, transform); ASSERT(newTransform.IsTranslateScale()); // We are free to rotate in place because we know this is a // throw away bitmap. switch (rotation) { case MatrixRotateBy90: status = bitmap->RotateFlip(Rotate90FlipNone); break; case MatrixRotateBy180: status = bitmap->RotateFlip(Rotate180FlipNone); break; case MatrixRotateBy270: status = bitmap->RotateFlip(Rotate270FlipNone); break; default: status = GenericError; ASSERT(FALSE); break; } if (status == Ok) { g->SetWorldTransform(newTransform); // Get new size (in case Height & Width were flipped. Size bitmapSize; bitmap->GetSize(&bitmapSize); srcRect.Width = bitmapSize.Width + 2.0f; srcRect.Height = bitmapSize.Height + 2.0f; // Because the bitmap is already at device resolution // (in most cases), nearest neighbor best preserves // the image when printing. InterpolationMode interpolationMode= g->GetInterpolationMode(); if (interpolationMode != InterpolationModeNearestNeighbor) { g->SetInterpolationMode(InterpolationModeNearestNeighbor); } // Draw the new image with the rotation/shear status = g->DrawImage(bitmap, outCroppedDestRect, srcRect, UnitPixel); if (interpolationMode != InterpolationModeNearestNeighbor) { g->SetInterpolationMode(interpolationMode); } g->SetWorldTransform(worldToDevice); } goto cleanupBitmap; } } // Draw the new image with the rotation/shear status = g->DrawImage(bitmap, outCroppedDestRect, srcRect, UnitPixel); } cleanupBitmap: // Now clean up everything bitmap->Dispose(); } return status; } // Get multipliers to convert to pixel units VOID GetPixelMultipliers( GpPageUnit srcUnit, REAL srcDpiX, REAL srcDpiY, REAL * pixelMultiplierX, REAL * pixelMultiplierY ) { REAL multiplierX; REAL multiplierY; // UnitDisplay is device-dependent and cannot be used for a source unit ASSERT(srcUnit != UnitDisplay); switch (srcUnit) { default: ASSERT(0); // FALLTHRU case UnitPixel: // Each unit represents one device pixel. multiplierX = 1.0f; multiplierY = 1.0f; break; case UnitPoint: // Each unit represents a 1/72 inch. multiplierX = srcDpiX / 72.0f; multiplierY = srcDpiY / 72.0f; break; case UnitInch: // Each unit represents 1 inch. multiplierX = srcDpiX; multiplierY = srcDpiY; break; case UnitDocument: // Each unit represents 1/300 inch. multiplierX = srcDpiX / 300.0f; multiplierY = srcDpiY / 300.0f; break; case UnitMillimeter: // Each unit represents 1 millimeter. // One Millimeter is 0.03937 inches // One Inch is 25.4 millimeters multiplierX = srcDpiX / 25.4f; multiplierY = srcDpiY / 25.4f; break; } *pixelMultiplierX = multiplierX; *pixelMultiplierY = multiplierY; } extern "C" int CALLBACK EnumEmfDownLevel( HDC hdc, // handle to device context HANDLETABLE FAR * gdiHandleTable, // pointer to metafile handle table CONST ENHMETARECORD * emfRecord, // pointer to metafile record int numHandles, // count of objects LPARAM play // pointer to optional data ) { if ((emfRecord != NULL) && (emfRecord->nSize >= sizeof(EMR)) && (play != NULL)) { // If we're in this method, we don't want to play any EMF+ records, // so skip them, so we don't record them into another metafile. if (!IsEmfPlusRecord(emfRecord)) { EmfPlusRecordType recordType = (EmfPlusRecordType)(emfRecord->iType); const BYTE * recordData = (const BYTE *)emfRecord->dParm; INT recordDataSize = emfRecord->nSize - sizeof(EMR); if (recordDataSize <= 0) { recordDataSize = 0; recordData = NULL; } MetafilePlayer * player = (MetafilePlayer *)play; player->MfState->StartRecord(hdc, gdiHandleTable, numHandles, emfRecord, recordType, recordDataSize, recordData); if (player->EnumerateCallback(recordType, 0, recordDataSize, recordData, player->CallbackData) == 0) { player->EnumerateAborted = TRUE; return 0; } } } else { WARNING(("Bad Enumeration Parameter")); } return 1; } // Assumes the hdc has already been set up with the correct transform and // clipping for displaying the metafile. GpStatus MetafilePlayer::EnumerateEmfRecords( HDC hdc, HENHMETAFILE hEmf, const RECT * dest, const RECT * deviceRect, ENHMFENUMPROC enumProc ) { ASSERT(hdc != NULL); ASSERT(hEmf != NULL); ASSERT(dest->bottom > dest->top && dest->right > dest->left); // GDI uses an Inclusive-Inclusive bound for Metafile Playback RECT destRect = *dest; destRect.bottom--; destRect.right--; GpStatus status = GenericError; BOOL externalEnumeration = (EnumerateCallback != GdipPlayMetafileRecordCallback); EmfEnumState emfState(hdc, hEmf, &destRect, deviceRect, externalEnumeration, Interpolation, Graphics->Context, Recolor, AdjustType); if (emfState.IsValid()) { MfState = &emfState; // If the metafile is empty the following fails. status = ::EnumEnhMetaFile(hdc, hEmf, enumProc, this, &destRect) ? Ok : GenericError; RopUsed = MfState->GetRopUsed(); MfState = NULL; if (EnumerateAborted) { status = Aborted; } } return status; } extern "C" int CALLBACK EnumWmfDownLevel( HDC hdc, HANDLETABLE FAR * gdiHandleTable, METARECORD FAR * wmfRecord, int numHandles, LPARAM play ) { if ((wmfRecord != NULL) && (((UNALIGNED METARECORD *)wmfRecord)->rdSize >= 3) && (play != NULL)) { EmfPlusRecordType recordType = (EmfPlusRecordType)(GDIP_WMF_RECORD_TO_EMFPLUS(wmfRecord->rdFunction)); const BYTE * recordData = (const BYTE *)((UNALIGNED METARECORD *)wmfRecord)->rdParm; INT recordDataSize = (((UNALIGNED METARECORD *)wmfRecord)->rdSize * 2) - SIZEOF_METARECORDHEADER; if (recordDataSize <= 0) { recordDataSize = 0; recordData = NULL; } MetafilePlayer * player = (MetafilePlayer *)play; player->MfState->StartRecord(hdc, gdiHandleTable, numHandles, wmfRecord, recordType, recordDataSize, recordData); if (player->EnumerateCallback(recordType, 0, recordDataSize, recordData, player->CallbackData) == 0) { player->EnumerateAborted = TRUE; return 0; } } else { WARNING(("Bad Enumeration Parameter")); } return 1; } // Assumes the hdc has already been set up with the correct transform and // clipping for displaying the metafile. GpStatus MetafilePlayer::EnumerateWmfRecords( HDC hdc, HMETAFILE hWmf, const RECT * dstRect, const RECT * deviceRect ) { ASSERT(hdc != NULL); ASSERT(hWmf != NULL); GpStatus status = GenericError; BOOL externalEnumeration = (EnumerateCallback != GdipPlayMetafileRecordCallback); WmfEnumState wmfState(hdc, hWmf, externalEnumeration, Interpolation, dstRect, deviceRect, Graphics->Context, Recolor, AdjustType); if (wmfState.IsValid()) { MfState = &wmfState; // If the metafile is empty the following fails. status = ::EnumMetaFile(hdc, hWmf, EnumWmfDownLevel, (LPARAM)this) ? Ok : GenericError; RopUsed = MfState->GetRopUsed(); MfState = NULL; if (EnumerateAborted) { status = Aborted; } } return status; } inline BOOL IsMetafileHdc( HDC hdc ) { DWORD hdcType = GetDCType(hdc); return ((hdcType == OBJ_ENHMETADC) || (hdcType == OBJ_METADC)); } class SetupClippingForMetafilePlayback { public: SetupClippingForMetafilePlayback( HDC hdc, DpDriver * driver, DpContext * context, BOOL forEMFPlus = FALSE ) { Hdc = hdc; Driver = driver; IsClip = FALSE; ClippedOut = FALSE; ReenableClipEscapes = FALSE; if (!context->VisibleClip.IsInfinite()) { // Use GDI path clipping for playback to metafile only UsePathClipping = IsMetafileHdc(hdc) && !context->IsPrinter; // NT4 has a postscript driver bug where embedded EPS corrupt the // current postscript clipping stack. To get around this, we resort to // using GDI to clip for us. // The problem is not limited to NT4 drivers alooe. There seems to // be a family of injected EPS which doesn't interop with embedded // postscript clipping escapes. The reason may have to do with the // fact that many implementations don't reset the current path after // sending the escape. See Office bugs 284388, 316074 if (context->IsPrinter) { if ((!forEMFPlus && !Globals::IsNt) || (Globals::IsNt && Globals::VersionInfoInitialized && ((Globals::OsVer.dwMajorVersion <= 4) || ((Globals::OsVer.dwMajorVersion >= 5) && (context->VisibleClip.IsSimple())) ))) { DriverPrint *pdriver = (DriverPrint*) Driver; pdriver->DisableClipEscapes(); ReenableClipEscapes = TRUE; } } // The trick here is we want to force the driver to clip, even if // totally visible because cropping requires this. We pass in the flag // to force clipping GpRect drawBounds; context->VisibleClip.GetBounds(&drawBounds); if (drawBounds.IsEmpty()) { ClippedOut = TRUE; return; } // Use appropriate driver clipping on playback Driver->SetupClipping(Hdc, context, &drawBounds, IsClip, UsePathClipping, TRUE); // Prevent metafile from drawing outside of the DestRect // Can only do it for NT because Win9x doesn't restore the // MetaRgn properly // We handle this in the Metafile Player for Win9x if (Globals::IsNt) { ::SetMetaRgn(hdc); } } } ~SetupClippingForMetafilePlayback() { if (IsClip) { Driver->RestoreClipping(Hdc, IsClip, UsePathClipping); if (ReenableClipEscapes) { DriverPrint *pdriver = (DriverPrint*) Driver; pdriver->EnableClipEscapes(); } } } BOOL IsClippedOut() { return ClippedOut; } private: DpDriver * Driver; HDC Hdc; BOOL IsClip; BOOL UsePathClipping; BOOL ClippedOut; BOOL ReenableClipEscapes; }; // We already set up the transform to handle the srcRect and also to // handle any flipping in the srcRect and destRect, so the 2 rects // should have positive widths and heights at this point. GpStatus GpGraphics::EnumEmf( MetafilePlayer * player, HENHMETAFILE hEmf, const GpRectF & destRect, const GpRectF & srcRect, // in pixel units const GpRectF & deviceDestRect, // The destRect in Device Units MetafileType type, BOOL isTranslateScale, BOOL renderToBitmap, const GpMatrix & flipAndCropTransform ) { ASSERT(hEmf != NULL); HDC hdc = Context->GetHdc(Surface); if (hdc == NULL) { return GenericError; } INT saveDC; if ((saveDC = ::SaveDC(hdc)) == 0) { Context->ReleaseHdc(hdc, Surface); return GenericError; } // Since we might have an HDC from a GpBitmap that's not clean, clean the // HDC for now.... Context->CleanTheHdc(hdc); player->PlayEMFRecords = TRUE; // play all EMF records GpStatus status = Ok; // the srcRect is already in pixel units GpRect deviceSrcRect; deviceSrcRect.X = GpRound(srcRect.X); deviceSrcRect.Y = GpRound(srcRect.Y); deviceSrcRect.Width = GpRound(srcRect.Width); deviceSrcRect.Height = GpRound(srcRect.Height); RECT deviceClipRect; deviceClipRect.left = RasterizerCeiling(deviceDestRect.X); deviceClipRect.top = RasterizerCeiling(deviceDestRect.Y); deviceClipRect.right = RasterizerCeiling(deviceDestRect.GetRight()); deviceClipRect.bottom = RasterizerCeiling(deviceDestRect.GetBottom()); // If it's a translate/scale matrix, do the transform ourselves, // even on NT, so that we can control how the rounding is done // to avoid cases where we round the metafile dest differently // than the clipping rect, resulting in clipped out edges. if (isTranslateScale) { SetupClippingForMetafilePlayback clipPlayback(hdc, Driver, Context); if (!clipPlayback.IsClippedOut()) { RECT deviceRect; GpPointF points[2]; points[0] = GpPointF(destRect.X, destRect.Y); points[1] = GpPointF(destRect.GetRight(), destRect.GetBottom()); player->PreContainerMatrix.Transform(points, 2); // We have to use the same method to convert REAL -> INT // that we do when we set up the clipping. Otherwise, some // of the points get rounded differently, causing a // portion of the metafile to get clipped out. deviceRect.left = RasterizerCeiling(points[0].X); deviceRect.top = RasterizerCeiling(points[0].Y); deviceRect.right = RasterizerCeiling(points[1].X); deviceRect.bottom = RasterizerCeiling(points[1].Y); if (deviceRect.left < deviceRect.right && deviceRect.top < deviceRect.bottom) { if ((type == MetafileTypeWmf) || (type == MetafileTypeWmfPlaceable)) { // map the source rect to the dest rect to play the metafile ::SetMapMode(hdc, MM_ANISOTROPIC); ::SetWindowOrgEx(hdc, deviceSrcRect.X, deviceSrcRect.Y, NULL); ::SetWindowExtEx(hdc, deviceSrcRect.Width, deviceSrcRect.Height, NULL); ::SetViewportOrgEx(hdc, deviceRect.left, deviceRect.top, NULL); ::SetViewportExtEx(hdc, deviceRect.right - deviceRect.left, deviceRect.bottom - deviceRect.top, NULL); status = player->EnumerateWmfRecords(hdc, (HMETAFILE)hEmf, &deviceRect, &deviceClipRect); } else // play as down-level EMF { ASSERT((type == MetafileTypeEmf) || (type == MetafileTypeEmfPlusDual)); status = player->EnumerateEmfRecords(hdc, hEmf, &deviceRect, &deviceClipRect, EnumEmfDownLevel); } } // else empty rect, nothing to draw } // else it's all clipped out } else // flip and/or rotate and/or shear { RECT dest; // Can't play a WMF with any rotate or skew transformation. // If we're on NT but we're drawing to a metafile hdc, then we // can't rely on the transforms working for that case. if (!renderToBitmap) { dest.left = GpRound(destRect.X); dest.top = GpRound(destRect.Y); dest.right = GpRound(destRect.GetRight()); dest.bottom = GpRound(destRect.GetBottom()); if ((dest.bottom > dest.top) && (dest.right > dest.left)) { // If NT, then set the transform in GDI, and play the metafile SetupClippingForMetafilePlayback clipPlayback(hdc, Driver, Context); if (!clipPlayback.IsClippedOut()) { ASSERT(Globals::IsNt); SetGraphicsMode(hdc, GM_ADVANCED); ASSERT(sizeof(XFORM) == sizeof(REAL)*6); XFORM xform; player->PreContainerMatrix.GetMatrix((REAL*) &xform); ::SetWorldTransform(hdc, &xform); RECT dummyRect = {0,0,0,0}; status = player->EnumerateEmfRecords(hdc, hEmf, &dest, &dummyRect, EnumEmfDownLevel); } } } else // Win9x with rotation or shear // WinNT WMF with Rotate or shear { // 1 - Draw into a 32-bit DIB Section // 2 - Create an image from the DIB Section // 3 - Call g->DrawImage status = GenericError; UINT32 * bits; HBITMAP hBitmap; player->BitmapDpi = Context->ContainerDpiX; hBitmap = CreateDibSection32Bpp(hdc, destRect, dest, &bits, &player->BitmapDpi, &player->PreContainerMatrix); if (hBitmap != NULL) { Init32BppDibToTransparent(bits, dest.right * dest.bottom); HDC hdcDib = CreateCompatibleDC(NULL); if (hdcDib != NULL) { ::SelectObject(hdcDib, hBitmap); if ((type == MetafileTypeWmf) || (type == MetafileTypeWmfPlaceable)) { // map the source rect to the dest rect to play the metafile ::SetMapMode(hdcDib, MM_ANISOTROPIC); ::SetWindowOrgEx(hdcDib, deviceSrcRect.X, deviceSrcRect.Y, NULL); ::SetWindowExtEx(hdcDib, deviceSrcRect.Width, deviceSrcRect.Height, NULL); ::SetViewportOrgEx(hdcDib, 0, 0, NULL); ::SetViewportExtEx(hdcDib, dest.right, dest.bottom, NULL); status = player->EnumerateWmfRecords(hdcDib, (HMETAFILE)hEmf, &dest, &dest); } else // play as down-level EMF { ASSERT((type == MetafileTypeEmf) || (type == MetafileTypeEmfPlusDual)); status = player->EnumerateEmfRecords(hdcDib, hEmf, &dest, &dest, EnumEmfDownLevel); } ::DeleteDC(hdcDib); if (status != Aborted) { // Don't use NearestNeighbor to draw the rotated metafile -- // it looks bad, and doesn't really save any time. InterpolationMode saveInterpolationMode = Context->FilterType; if (saveInterpolationMode == InterpolationModeNearestNeighbor) { Context->FilterType = InterpolationModeBilinear; } // Apply the flip/crop transform. Now the worldToDevice transform // should be equivalent to the PreContainerMatrix. this->SetWorldTransform(flipAndCropTransform); status = Draw32BppDib(this, bits, dest.right, dest.bottom, destRect, player->BitmapDpi, !player->RopUsed); // restore the interpolation mode (in case we changed it). Context->FilterType = saveInterpolationMode; } } DeleteObject(hBitmap); } else if ((dest.right == 0) || (dest.bottom == 0)) { status = Ok; } } } ::RestoreDC(hdc, saveDC); Context->ReleaseHdc(hdc, Surface); return status; } // We already set up the transform to handle the srcRect and also to // handle any flipping in the srcRect and destRect, so the 2 rects // should have positive widths and heights at this point. GpStatus GpGraphics::EnumEmfPlusDual( MetafilePlayer * player, HENHMETAFILE hEmf, const GpRectF& destRect, // inclusive, exclusive const GpRectF& deviceDestRect, // inclusive, exclusive BOOL isTranslateScale, BOOL renderToBitmap ) { GpStatus status = Ok; HDC hdc; HWND hwnd = Context->Hwnd; INT saveDC = -1; BOOL needToReleaseHdc = FALSE; // We are going to take the role of the application and set up the HDC // like we want it and then let GDI+ change it from there. This is so // that when we play back the GDI records, the HDC will already be set // up correctly so those records get played back in the right place. // In other words, I'm doing my own version of Context->GetHdc(). Surface->Flush(FlushIntentionFlush); if (hwnd != NULL) { // We have to guarantee that we use the same HDC throughout the // enumeration/playing of the metafile -- so change how the HDC is // set up in the graphics context (if we need to). ASSERT(Context->Hdc == NULL); ASSERT(Context->SaveDc == 0); hdc = ::GetCleanHdc(hwnd); if (hdc == NULL) { WARNING(("GetCleanHdc failed")); return Win32Error; } Context->Hwnd = NULL; Context->Hdc = hdc; } else { if ((hdc = Context->Hdc) != NULL) { // Restore the HDC back to the state the application had it in. Context->ResetHdc(); } else // might be a bitmap surface { hdc = Context->GetHdc(Surface); // Still have to call CleanTheHdc to fix bug #121666. // It seems like the hdc should have come back clean // from the context. if (hdc == NULL) { WARNING(("Could not get an hdc")); return InvalidParameter; } needToReleaseHdc = TRUE; } // Now save the state of the HDC so we can get back to it later. saveDC = SaveDC(hdc); // Get the hdc into a clean state before we start. Context->CleanTheHdc(hdc); } // This block needs to be within braces so that SetupClippingForMetafile // will have it's destructor called before the cleanup code. { // set the clipping for the down-level records SetupClippingForMetafilePlayback clipPlayback(hdc, Driver, Context, TRUE); if (!clipPlayback.IsClippedOut()) { RECT deviceClipRect; deviceClipRect.left = RasterizerCeiling(deviceDestRect.X); deviceClipRect.top = RasterizerCeiling(deviceDestRect.Y); deviceClipRect.right = RasterizerCeiling(deviceDestRect.GetRight()); deviceClipRect.bottom = RasterizerCeiling(deviceDestRect.GetBottom()); // If it's a translate/scale matrix, do the transform ourselves, // even on NT, so that we can control how the rounding is done // to avoid cases where we round the metafile dest differently // than the clipping rect, resulting in clipped out edges. if (isTranslateScale) { RECT deviceRect; GpPointF points[2]; points[0] = GpPointF(destRect.X, destRect.Y); points[1] = GpPointF(destRect.GetRight(), destRect.GetBottom()); player->PreContainerMatrix.Transform(points, 2); // We have to use the same method to convert REAL -> INT // that we do when we set up the clipping. Otherwise, some // of the points get rounded differently, causing a // portion of the metafile to get clipped out. deviceRect.left = RasterizerCeiling(points[0].X); deviceRect.top = RasterizerCeiling(points[0].Y); deviceRect.right = RasterizerCeiling(points[1].X); deviceRect.bottom = RasterizerCeiling(points[1].Y); // If we don't have a destrect then we are done if (deviceRect.left < deviceRect.right && deviceRect.top < deviceRect.bottom) { status = player->EnumerateEmfRecords(hdc, hEmf, &deviceRect, &deviceClipRect, EnumEmfWithDownLevel); } } else // flip and/or rotate and/or shear { RECT dest; dest.left = GpRound(destRect.X); dest.top = GpRound(destRect.Y); dest.right = GpRound(destRect.GetRight()); dest.bottom = GpRound(destRect.GetBottom()); if ((dest.bottom > dest.top) && (dest.right > dest.left)) { // If we're on NT but we're drawing to a metafile hdc, then we // can't rely on the transforms working for that case. if (!renderToBitmap) { ASSERT(Globals::IsNt); // set the transform for the down-level records SetGraphicsMode(hdc, GM_ADVANCED); ASSERT(sizeof(XFORM) == sizeof(REAL)*6); // We want to set the transform in the HDC to the Pre-container matrix, // so that it will be used to render the down-level records. XFORM xform; player->PreContainerMatrix.GetMatrix((REAL*)(&xform)); ::SetWorldTransform(hdc, &xform); RECT dummyRect = {0,0,0,0}; status = player->EnumerateEmfRecords(hdc, hEmf, &dest, &dummyRect, EnumEmfWithDownLevel); } else { UINT32 * bits; HBITMAP hBitmap; // The down-level records will get drawn into a dib section HDC // which will then be drawn to the real hdc by g->DrawImage. // !!! I should probably save the visible clip region at this // point so that clipping in the EMF+ doesn't affect the down-level // records. // Set the World Tranform to be the PreContainer Transform // And restore it after we're transformed the dest player->BitmapDpi = Context->ContainerDpiX; hBitmap = CreateDibSection32Bpp(hdc, destRect, dest, &bits, &player->BitmapDpi, &player->PreContainerMatrix); status = GenericError; if (hBitmap != NULL) { HDC hdcDib = CreateCompatibleDC(NULL); if (hdcDib != NULL) { // set up the player data player->BitmapBits = bits; player->BitmapWidth = dest.right; player->BitmapHeight = dest.bottom; player->BitmapDestRect = destRect; ::SelectObject(hdcDib, hBitmap); status = player->EnumerateEmfRecords(hdcDib, hEmf, &dest, &dest, EnumEmfWithDownLevel); ::DeleteDC(hdcDib); // so DoneWithDownLevel call below works right player->BitmapBits = NULL; } DeleteObject(hBitmap); } else if ((dest.right == 0) || (dest.bottom == 0)) { status = Ok; } } } } } // else Nothing to play Everything is clipped out } // The Hdc should get set back to null when we reach the EMF+ EOF record // But clean up anyway, just in case something went wrong. player->DoneWithDownLevel(); // Restore the HDC back to the state we initially set up. Context->ResetHdc(); if (hwnd != NULL) { ReleaseDC(hwnd, hdc); // Now, restore the hwnd in the graphics context. Context->Hwnd = hwnd; Context->Hdc = NULL; } else { // Now restore the HDC back to the real application state. RestoreDC(hdc, saveDC); if (needToReleaseHdc) { Context->ReleaseHdc(hdc); } } return status; } /**************************************************************************\ * * Function Description: * * GpMetafile destructor * * Arguments: * * NONE * * Return Value: * * NONE * * Created: * * 6/15/1999 DCurtis * \**************************************************************************/ GpMetafile::~GpMetafile() { CleanUp(); } VOID GpMetafile::CleanUp() { if ((MetaGraphics != NULL) && (!RequestedMetaGraphics)) { // If for some reason the app never requsted the MetaGraphics, // then we'd better delete it. delete MetaGraphics; } if (State == RecordingMetafileState) { // EndRecording was never called, which means that the MetaGraphics // was never deleted. So clean things up and invalidate the // MetaGraphics. ASSERT(MetaGraphics->Metafile != NULL); MetaGraphics->Metafile->EndRecording(); // deletes the recorder // Endrecording sets the MetaGraphics to NULL so don't touch it anymore WARNING(("Deleted Metafile before deleting MetaGraphics")); } if ((Hemf != NULL) && DeleteHemf) { if (Header.IsEmfOrEmfPlus()) { DeleteEnhMetaFile(Hemf); } else { DeleteMetaFile((HMETAFILE)Hemf); } } if (Filename != NULL) { GpFree(Filename); } else if (Stream != NULL) // only for recording { // the stream position should already be at the end // of the metafile. Stream->Release(); } delete Player; } extern "C" int CALLBACK EnumGetEmfPlusHeader( HDC hdc, // should be NULL HANDLETABLE FAR * gdiHandleTable, CONST ENHMETARECORD * emfRecord, int numHandles, LPARAM emfPlusHeader ) { if ((emfRecord != NULL) && (emfRecord->nSize >= sizeof(EMR)) && (emfPlusHeader != NULL)) { if (emfRecord->iType == EMR_HEADER) { return 1; // skip the header and keep enumerating } if (IsEmfPlusRecord(emfRecord) && (emfRecord->nSize >= (sizeof(EMR) + sizeof(DWORD) + // comment data size sizeof(INT32) + // signature sizeof(EmfPlusRecord) + sizeof(EmfPlusHeaderRecord)))) { GpMemcpy((VOID*)emfPlusHeader, ((CONST EMRGDICOMMENT *)emfRecord)->Data + sizeof(INT32), sizeof(EmfPlusRecord) + sizeof(EmfPlusHeaderRecord)); } } else { WARNING(("Bad Enumeration Parameter")); } return 0; // don't enumerate any more records } HENHMETAFILE GetEmfFromWmfData( HMETAFILE hWmf, BYTE * wmfData, UINT size ) { if (wmfData == NULL || hWmf == NULL || size < (sizeof(METAHEADER)+sizeof(META_ESCAPE_ENHANCED_METAFILE))) { ASSERTMSG(FALSE, ("GetEmfFromWmfData: Someone passed an invalid argument")); return NULL; } HENHMETAFILE hemf32 = NULL; HDC hMFDC = NULL; PMETA_ESCAPE_ENHANCED_METAFILE pmfeEnhMF; PBYTE pMetaData32 = (PBYTE) NULL; pmfeEnhMF = (PMETA_ESCAPE_ENHANCED_METAFILE) &wmfData[sizeof(METAHEADER)]; if (IsMetaEscapeEnhancedMetafile(pmfeEnhMF)) { UINT i; UINT cbMetaData32; if (pmfeEnhMF->fFlags != 0) { ASSERTMSG(FALSE, ("GetEmfFromWmfData: Unrecognized Windows metafile\n")); goto SWMFB_UseConverter; } // Validate checksum if (GetWordCheckSum(size, (PWORD) wmfData)) { ASSERTMSG(FALSE, ("GetEmfFromWmfData: Metafile has been modified\n")); goto SWMFB_UseConverter; } // Unpack the data from the small chunks of metafile comment records // Windows 3.0 chokes on Comment Record > 8K? // We probably could probably just error out if out of memory but // lets try to convert just because the embedded comment might be bad. TERSE(("GetEmfFromWmfData: Using embedded enhanced metafile\n")); cbMetaData32 = (UINT) pmfeEnhMF->cbEnhMetaFile; if (!(pMetaData32 = (PBYTE) GpMalloc(cbMetaData32))) { ASSERTMSG(FALSE, ("GetEmfFromWmfData: LocalAlloc Failed")); goto SWMFB_UseConverter; } i = 0; do { if (i + pmfeEnhMF->cbCurrent > cbMetaData32) { ASSERTMSG(FALSE, ("GetEmfFromWmfData: Bad metafile comment")); goto SWMFB_UseConverter; } GpMemcpy(&pMetaData32[i], (PBYTE) &pmfeEnhMF[1], pmfeEnhMF->cbCurrent); i += (UINT) pmfeEnhMF->cbCurrent; pmfeEnhMF = (PMETA_ESCAPE_ENHANCED_METAFILE) ((PWORD) pmfeEnhMF + pmfeEnhMF->rdSize); } while (IsMetaEscapeEnhancedMetafile(pmfeEnhMF)); if (i != cbMetaData32) { ASSERTMSG(FALSE, ("GetEmfFromWmfData: Insufficient metafile data")); goto SWMFB_UseConverter; } // Set the memory directly into the enhanced metafile and return the // metafile. hemf32 = SetEnhMetaFileBits(cbMetaData32, pMetaData32); } SWMFB_UseConverter: if( hemf32 == NULL) { hMFDC = CreateEnhMetaFileA(NULL, NULL, NULL, NULL); if (hMFDC != NULL) { // Set the MapMode and Extent to INT iMapMode = MM_ANISOTROPIC; HDC hdcRef = ::GetDC(NULL); INT xExtPels = ::GetDeviceCaps(hdcRef, HORZRES); INT yExtPels = ::GetDeviceCaps(hdcRef, VERTRES); ::ReleaseDC(NULL, hdcRef); BOOL success = (::SetMapMode(hMFDC, iMapMode) && ::SetViewportExtEx(hMFDC, xExtPels, yExtPels, NULL) && ::SetWindowExtEx(hMFDC, xExtPels, yExtPels, NULL) && ::PlayMetaFile(hMFDC, hWmf)); hemf32 = CloseEnhMetaFile(hMFDC); if ((!success) && (hemf32 != NULL)) { DeleteEnhMetaFile(hemf32); hemf32 = NULL; } } } if (pMetaData32 != NULL) { GpFree(pMetaData32); } return hemf32 ; } GpStatus GetEmfHeader( MetafileHeader & header, ENHMETAHEADER3 & emfHeader, EmfPlusRecord * record, INT signature ) { GpStatus status = Ok; // !!! how to handle versioning for shipping? // !!! allow different minor versions, but not major versions? EmfPlusHeaderRecord * emfPlusHeader = (EmfPlusHeaderRecord *)(record + 1); // See if this is an EMF+ file if ((signature == EMFPLUS_SIGNATURE) && (record->Size >= (sizeof(EmfPlusRecord) + sizeof(EmfPlusHeaderRecord))) && (record->Type == EmfPlusRecordTypeHeader) && (record->DataSize == (record->Size - sizeof(EmfPlusRecord))) && (ObjectData::MajorVersionMatches(emfPlusHeader->Version)) && (emfPlusHeader->LogicalDpiX > 0) && (emfPlusHeader->LogicalDpiY > 0)) { if (GetIsEmfPlusDual(record->Flags)) { header.Type = MetafileTypeEmfPlusDual; } else { header.Type = MetafileTypeEmfPlusOnly; } header.EmfPlusHeaderSize = record->Size; header.Version = emfPlusHeader->Version; header.EmfPlusFlags = emfPlusHeader->EmfPlusFlags; header.LogicalDpiX = emfPlusHeader->LogicalDpiX; header.LogicalDpiY = emfPlusHeader->LogicalDpiY; } else { header.Type = MetafileTypeEmf; header.Version = emfHeader.nVersion; } header.Size = emfHeader.nBytes; // EmfHeaderIsValid() verifies that these are all > 0 REAL dpmmX = ((REAL)(emfHeader.szlDevice.cx) / (REAL)(emfHeader.szlMillimeters.cx)); REAL dpmmY = ((REAL)(emfHeader.szlDevice.cy) / (REAL)(emfHeader.szlMillimeters.cy)); header.DpiX = dpmmX * 25.4f; header.DpiY = dpmmY * 25.4f; INT top; INT left; INT right; INT bottom; // Make sure we have a normalized frameRect if (emfHeader.rclFrame.left <= emfHeader.rclFrame.right) { left = emfHeader.rclFrame.left; right = emfHeader.rclFrame.right; } else { left = emfHeader.rclFrame.right; right = emfHeader.rclFrame.left; } if (emfHeader.rclFrame.top <= emfHeader.rclFrame.bottom) { top = emfHeader.rclFrame.top; bottom = emfHeader.rclFrame.bottom; } else { top = emfHeader.rclFrame.bottom; bottom = emfHeader.rclFrame.top; } // Make the device bounds reflect the frameRect, // not the actual size of the drawing. dpmmX *= 0.01f; dpmmY *= 0.01f; // The frameRect is inclusive-inclusive, but the bounds in // the header is inclusive-exclusive. REAL x = (REAL)(left) * dpmmX; REAL y = (REAL)(top) * dpmmY; REAL w = ((REAL)(right - left) * dpmmX) + 1.0f; REAL h = ((REAL)(bottom - top) * dpmmY) + 1.0f; header.X = GpRound(x); header.Y = GpRound(y); header.Width = GpRound(w); header.Height = GpRound(h); header.EmfHeader = emfHeader; if ((header.Width == 0) || (header.Height == 0)) { status = InvalidParameter; } return status; } HENHMETAFILE GetEmf( IStream * stream, BOOL isWmf, UINT size ) { HENHMETAFILE hEmf = NULL; #if PROFILE_MEMORY_USAGE MC_LogAllocation(size); #endif HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_NODISCARD, size); if (hGlobal != NULL) { HRESULT hResult; IStream * memoryStream = NULL; hResult = CreateStreamOnHGlobal(hGlobal, TRUE, &memoryStream); if (HResultSuccess(hResult) && (memoryStream != NULL)) { if (CopyStream(stream, memoryStream, size)) { BYTE * metaData = (BYTE *)GlobalLock(hGlobal); if (metaData != NULL) { if (isWmf) { hEmf = (HENHMETAFILE)SetMetaFileBitsEx(size, metaData); } else { hEmf = SetEnhMetaFileBits(size, metaData); } } GlobalUnlock(hGlobal); } memoryStream->Release(); // frees the memory } else { GlobalFree(hGlobal); } } return hEmf; } static VOID GetWmfHeader( MetafileHeader & header, METAHEADER & wmfHeader, const WmfPlaceableFileHeader * wmfPlaceableFileHeader ) { ASSERT(WmfPlaceableHeaderIsValid(wmfPlaceableFileHeader)); ASSERT(WmfHeaderIsValid(&wmfHeader)); header.Type = MetafileTypeWmfPlaceable; header.Size = wmfHeader.mtSize * 2L; header.Version = wmfHeader.mtVersion; header.WmfHeader = wmfHeader; if (wmfPlaceableFileHeader->Inch > 0) { header.DpiX = wmfPlaceableFileHeader->Inch; header.DpiY = wmfPlaceableFileHeader->Inch; } else // guess at the Dpi { header.DpiX = 1440.0f; header.DpiY = 1440.0f; // Something wrong but continue } // already verified the checksum // Unlike the EMF header the Placeable header is Inclusive-Exclusive // So don't add 1 device unit if (wmfPlaceableFileHeader->BoundingBox.Left < wmfPlaceableFileHeader->BoundingBox.Right) { header.X = wmfPlaceableFileHeader->BoundingBox.Left; header.Width = wmfPlaceableFileHeader->BoundingBox.Right - wmfPlaceableFileHeader->BoundingBox.Left; } else { header.X = wmfPlaceableFileHeader->BoundingBox.Right; header.Width = wmfPlaceableFileHeader->BoundingBox.Left - wmfPlaceableFileHeader->BoundingBox.Right; } if (wmfPlaceableFileHeader->BoundingBox.Top < wmfPlaceableFileHeader->BoundingBox.Bottom) { header.Y = wmfPlaceableFileHeader->BoundingBox.Top; header.Height = wmfPlaceableFileHeader->BoundingBox.Bottom - wmfPlaceableFileHeader->BoundingBox.Top; } else { header.Y = wmfPlaceableFileHeader->BoundingBox.Bottom; header.Height = wmfPlaceableFileHeader->BoundingBox.Top - wmfPlaceableFileHeader->BoundingBox.Bottom; } } extern "C" int CALLBACK EnumWmfToGetHeader( HDC hdc, // should be NULL HANDLETABLE FAR * gdiHandleTable, METARECORD FAR * wmfRecord, int numHandles, LPARAM wmfHeader ) { ASSERT(wmfHeader != NULL); if ((wmfRecord != NULL) && (((UNALIGNED METARECORD *)wmfRecord)->rdSize >= 3)) { // The first record that it gives us is the first one past the header, // not the header itself, so we have to back up on the pointer. GpMemcpy((VOID *)wmfHeader, ((BYTE *)wmfRecord) - sizeof(METAHEADER), sizeof(METAHEADER)); } else { WARNING(("Bad Enumeration Parameter")); } return 0; // Don't enumerate any more records } GpStatus GetMetafileHeader( HMETAFILE hWmf, const WmfPlaceableFileHeader * wmfPlaceableFileHeader, MetafileHeader & header ) { ASSERT((hWmf != NULL) && (wmfPlaceableFileHeader != NULL)); GpMemset(&header, 0, sizeof(header)); if (WmfPlaceableHeaderIsValid(wmfPlaceableFileHeader)) { METAHEADER wmfHeader; GpMemset(&wmfHeader, 0, sizeof(wmfHeader)); ::EnumMetaFile(NULL, hWmf, EnumWmfToGetHeader, (LPARAM)&wmfHeader); if (!WmfHeaderIsValid(&wmfHeader)) { //ASSERT(WmfHeaderIsValid(&wmfHeader)); WARNING(("GetMetafileHeader: WmfHeaderIsValid FAILED!")); wmfHeader.mtType = MEMORYMETAFILE; wmfHeader.mtHeaderSize = sizeof(METAHEADER) / sizeof(WORD); wmfHeader.mtVersion = METAVERSION300; wmfHeader.mtSize = GetMetaFileBitsEx(hWmf, 0, NULL) / 2; wmfHeader.mtNoObjects = 0; wmfHeader.mtMaxRecord = 0; wmfHeader.mtNoParameters = 0; } GetWmfHeader(header, wmfHeader, wmfPlaceableFileHeader); return Ok; } return InvalidParameter; } GpStatus GetMetafileHeader( HENHMETAFILE hEmf, MetafileHeader & header, BOOL * isCorrupted ) { ASSERT(hEmf != NULL); GpMemset(&header, 0, sizeof(header)); ENHMETAHEADER3 emfHeader; if ((GetEnhMetaFileHeader(hEmf, sizeof(emfHeader), (ENHMETAHEADER*)(&emfHeader)) <= 0) || !EmfHeaderIsValid(emfHeader)) { if (isCorrupted != NULL) { *isCorrupted = FALSE; } return InvalidParameter; } // Now we know it is an EMF BYTE buffer[sizeof(EmfPlusRecord) + sizeof(EmfPlusHeaderRecord)]; GpMemset(buffer, 0, sizeof(EmfPlusRecord) + sizeof(EmfPlusHeaderRecord)); // No reason to enumerate the metafile if there are only // header and EOF records. if (emfHeader.nRecords > 2) { ::EnumEnhMetaFile(NULL, hEmf, EnumGetEmfPlusHeader, buffer, NULL); } GpStatus status; status = GetEmfHeader(header, emfHeader, (EmfPlusRecord *)buffer, (((EmfPlusRecord *)buffer)->Size != 0) ? EMFPLUS_SIGNATURE : 0); if (isCorrupted != NULL) { *isCorrupted = (status != Ok); } return status; } GpStatus GetEmfFromWmf( IStream * stream, UINT streamSize, MetafileHeader & header, HENHMETAFILE * hEMF ) { if (stream == NULL || hEMF == NULL) { ASSERT(FALSE); return InvalidParameter; } GpStatus status = Win32Error; IStream * memStream; ASSERT(hEMF != NULL); *hEMF = NULL ; HMETAFILE hWMF = (HMETAFILE) GetEmf(stream, TRUE, streamSize); if (hWMF != NULL) { BYTE * wmfData = (BYTE*)GpMalloc(streamSize); if (wmfData != NULL) { GetMetaFileBitsEx(hWMF, streamSize, wmfData); *hEMF = GetEmfFromWmfData(hWMF, wmfData, streamSize); if (*hEMF != NULL) { status = GetMetafileHeader(*hEMF, header); } GpFree(wmfData); } } if (hWMF != NULL) { DeleteMetaFile(hWMF); } return status; } // If we fail, the stream position will be right where it started. // If we succeed, the stream position will be at the end of the WMF/EMF static GpStatus GetHeaderAndMetafile( IStream * stream, MetafileHeader & header, HENHMETAFILE * hEMF, // We can have a NULL hEMF, then we just want the header. BOOL * isCorrupted, BOOL tryWmfOnly = FALSE ) { GpMemset(&header, 0, sizeof(header)); if (stream == NULL || isCorrupted == NULL) { WARNING(("IN Parameter Stream or Corruption flag is NULL")); return InvalidParameter; } GpStatus status = InvalidParameter; LONGLONG startPosition; LONGLONG streamSize; STATSTG statstg; BOOL corrupted = FALSE; // Save the start position of the metafile in case we have to try // more than once. if (!GetStreamPosition(stream, startPosition)) { return Win32Error; } // We don't want to read past the end of the steam so make sure // that we don't exceed it. If we succeed the set the streamSize if(SUCCEEDED(stream->Stat(&statstg, STATFLAG_NONAME))) { streamSize = statstg.cbSize.QuadPart; } else { WARNING1("Couldn't get size of Stream"); streamSize = INT_MAX; } if (!tryWmfOnly) { ENHMETAHEADER3 emfHeader; BOOL isEmf; // Read the EMF header and make sure it's valid isEmf = (ReadBytes(stream, &emfHeader, sizeof(emfHeader)) && EmfHeaderIsValid(emfHeader)); if (isEmf) { struct EmfPlusSecondMetafileRecord { EMR emr; DWORD commentDataSize; INT32 signature; EmfPlusRecord record; EmfPlusHeaderRecord emfPlusHeader; } secondRecord; GpMemset(&secondRecord, 0, sizeof(secondRecord)); // No reason to read the metafile if there are only // header and EOF records. if ((emfHeader.nRecords > 2) && (emfHeader.nBytes >= (emfHeader.nSize + sizeof(secondRecord)))) { if (SeekFromStart(stream, startPosition + emfHeader.nSize)) { ReadBytes(stream, &secondRecord, sizeof(secondRecord)); if (!IsEmfPlusRecord((ENHMETARECORD *)&secondRecord)) { // make sure that whatever data was there isn't // interpreted as a EMF+ header secondRecord.signature = 0; } } } status = GetEmfHeader(header, emfHeader, &secondRecord.record, secondRecord.signature); // Seek back to the start of the metafile. if ((hEMF != NULL) && (status == Ok)) { if (!SeekFromStart(stream, startPosition)) { *isCorrupted = TRUE; return Win32Error; } *hEMF = GetEmf(stream, FALSE /*isWMF*/, (UINT)min(header.GetMetafileSize(), streamSize - startPosition)); if (*hEMF == NULL) { status = GenericError; } } corrupted = (status != Ok); goto Exit; } // Seek back to the start of the metafile so we can try WMF if (!SeekFromStart(stream, startPosition)) { *isCorrupted = FALSE; return Win32Error; } } // It's not an EMF, try a WMF { WmfPlaceableFileHeader wmfPlaceableFileHeader; METAHEADER wmfHeader; BOOL isPlaceable; BOOL isWMF; isPlaceable = (ReadBytes(stream, &wmfPlaceableFileHeader, sizeof(wmfPlaceableFileHeader)) && WmfPlaceableHeaderIsValid(&wmfPlaceableFileHeader) && ReadBytes(stream, &wmfHeader, sizeof(wmfHeader)) && WmfHeaderIsValid(&wmfHeader)); if (isPlaceable) { GetWmfHeader(header, wmfHeader, &wmfPlaceableFileHeader); status = Ok; corrupted = FALSE; if (hEMF != NULL) { if (!SeekFromStart(stream, startPosition + sizeof(wmfPlaceableFileHeader))) { *isCorrupted = TRUE; return Win32Error; } *hEMF = GetEmf(stream, TRUE /* isWMF */, (UINT)min(header.GetMetafileSize(), streamSize - (startPosition + sizeof(wmfPlaceableFileHeader)))); if (*hEMF == NULL) { status = GenericError; corrupted = TRUE; } } goto Exit; } // We could have an placeableWmf header with bad data in it, so skip // the placeable header for subsequent access to the WMF. INT wmfOffset = (wmfPlaceableFileHeader.Key == GDIP_WMF_PLACEABLEKEY) ? sizeof(WmfPlaceableFileHeader) : 0; if (!SeekFromStart(stream, startPosition + wmfOffset)) { *isCorrupted = FALSE; return Win32Error; } isWMF = (ReadBytes(stream, &wmfHeader, sizeof(wmfHeader)) && WmfHeaderIsValid(&wmfHeader)); if (isWMF) { // Seek to the start of the WMF metafile. if (!SeekFromStart(stream, startPosition + wmfOffset)) { *isCorrupted = TRUE; return Win32Error; } UINT wmfSize = min((wmfHeader.mtSize * 2L), (UINT)(streamSize - (startPosition + wmfOffset))); if (hEMF != NULL) { status = GetEmfFromWmf(stream, wmfSize, header, hEMF); } else { HENHMETAFILE tmpEMF = NULL; status = GetEmfFromWmf(stream, wmfSize, header, &tmpEMF); if (tmpEMF != NULL) { DeleteEnhMetaFile(tmpEMF); } } corrupted = (status != Ok); } } Exit: *isCorrupted = corrupted; if (status == Ok) { // set the stream position to the end of the metafile SeekFromStart(stream, startPosition + header.GetMetafileSize()); return Ok; } // set the stream position to the start of the metafile SeekFromStart(stream, startPosition); return status; } VOID GpMetafile::InitStream( IStream* stream, BOOL tryWmfOnly ) { BOOL isCorrupted = FALSE; // We just use the stream long enough to create an hEMF stream->AddRef(); if ((GetHeaderAndMetafile(stream, Header, &Hemf, &isCorrupted, tryWmfOnly) == Ok) && (Hemf != NULL)) { State = DoneRecordingMetafileState; } else if (isCorrupted) { State = CorruptedMetafileState; } stream->Release(); } GpStatus GetMetafileHeader( IStream * stream, MetafileHeader & header, BOOL tryWmfOnly ) { BOOL isCorrupted = FALSE; return GetHeaderAndMetafile(stream, header, NULL, &isCorrupted, tryWmfOnly); } GpStatus GetMetafileHeader( const WCHAR * filename, MetafileHeader & header ) { GpStatus status = InvalidParameter; ASSERT(filename != NULL); if (filename != NULL) { const WCHAR* ext = UnicodeStringReverseSearch(filename, L'.'); // Get a stream only long enough to validate the metafile IStream * metaStream = CreateStreamOnFile(filename, GENERIC_READ); if (metaStream != NULL) { // apm is for a Placeable Metafile BOOL tryWmf = (ext && (UnicodeStringCompareCI(ext, L".WMF") || UnicodeStringCompareCI(ext, L".APM"))); BOOL isCorrupted = FALSE; status = GetHeaderAndMetafile(metaStream, header, NULL, &isCorrupted, tryWmf); // if we tried a WMF, but it's not a WMF, then try an EMF if ((status != Ok) && tryWmf && !isCorrupted) { status = GetHeaderAndMetafile(metaStream, header, NULL, &isCorrupted, FALSE); } metaStream->Release(); } } return status; } VOID GpMetafile::InitWmf( HMETAFILE hWmf, const WmfPlaceableFileHeader * wmfPlaceableFileHeader, BOOL deleteWmf ) { // See if there is an wmfPlaceableFileHeader we can use if ((wmfPlaceableFileHeader != NULL) && (WmfPlaceableHeaderIsValid(wmfPlaceableFileHeader))) { if (GetMetafileHeader(hWmf, wmfPlaceableFileHeader, Header) == Ok) { DeleteHemf = (deleteWmf != 0); Hemf = (HENHMETAFILE)hWmf; State = DoneRecordingMetafileState; return; } else { // we know it's a WMF, but we couldn't get the header from it State = CorruptedMetafileState; } } else // no valid wmfPlaceableFileHeader { // We can have a null or invalid header since we accept WMF files // (by turning them into EMFs). UINT size = GetMetaFileBitsEx(hWmf, 0, NULL); if (size > 0) { BYTE * wmfData = (BYTE*) GpMalloc(size); if (wmfData != NULL) { if (GetMetaFileBitsEx(hWmf, size, wmfData) > 0) { HENHMETAFILE hEmf = GetEmfFromWmfData(hWmf, wmfData, size); if (hEmf != NULL) { BOOL isCorrupted; if (GetMetafileHeader(hEmf, Header, &isCorrupted) == Ok) { // Since we created this EMF we need to delete it afterwards DeleteHemf = TRUE; Hemf = hEmf; State = DoneRecordingMetafileState; } else { if (isCorrupted) { // we know it's a metafile, but we couldn't get the header State = CorruptedMetafileState; } DeleteEnhMetaFile(hEmf); } } } GpFree(wmfData); } } } if (deleteWmf) { DeleteMetaFile(hWmf); } } VOID GpMetafile::InitEmf( HENHMETAFILE hEmf, BOOL deleteEmf ) { BOOL isCorrupted; if (GetMetafileHeader(hEmf, Header, &isCorrupted) == Ok) { DeleteHemf = (deleteEmf != 0); Hemf = hEmf; State = DoneRecordingMetafileState; return; } if (deleteEmf) { DeleteEnhMetaFile(hEmf); } if (isCorrupted) { State = CorruptedMetafileState; } } /**************************************************************************\ * * Function Description: * * GpMetafile constructor for read-only access to a metafile. * * Arguments: * * [IN] hWmf - the handle to the metafile to open for playback * [IN] wmfPlaceableFileHeader - the Placeable header to give size info about the WMF * * Return Value: * * NONE * * Created: * * 10/06/1999 DCurtis * \**************************************************************************/ GpMetafile::GpMetafile( HMETAFILE hWmf, const WmfPlaceableFileHeader * wmfPlaceableFileHeader, BOOL deleteWmf ) : GpImage(ImageTypeMetafile) { ASSERT(hWmf != NULL); InitDefaults(); if (IsValidMetaFile(hWmf)) { InitWmf(hWmf, wmfPlaceableFileHeader, deleteWmf); } } /**************************************************************************\ * * Function Description: * * GpMetafile constructor for read-only access to a metafile. * * Arguments: * * [IN] hEmf - the handle to the metafile to open for playback * * Return Value: * * NONE * * Created: * * 10/06/1999 DCurtis * \**************************************************************************/ GpMetafile::GpMetafile( HENHMETAFILE hEmf, BOOL deleteEmf ) : GpImage(ImageTypeMetafile) { ASSERT(hEmf != NULL); InitDefaults(); if (GetObjectTypeInternal(hEmf) == OBJ_ENHMETAFILE) { InitEmf(hEmf, deleteEmf); } } /**************************************************************************\ * * Function Description: * * GpMetafile constructor for read-only access to a metafile. * * Arguments: * * [IN] filename - the metafile to open for playback * * Return Value: * * NONE * * Created: * * 6/15/1999 DCurtis * \**************************************************************************/ GpMetafile::GpMetafile( const WCHAR* filename, const WmfPlaceableFileHeader * wmfPlaceableFileHeader ) : GpImage(ImageTypeMetafile) { ASSERT(filename != NULL); InitDefaults(); if ((Filename = UnicodeStringDuplicate(filename)) != NULL) { const WCHAR* ext = UnicodeStringReverseSearch(filename, L'.'); // apm is for a Placeable Metafile BOOL tryWmf = ((wmfPlaceableFileHeader != NULL) || (ext && (!UnicodeStringCompareCI(ext, L".WMF") || !UnicodeStringCompareCI(ext, L".APM")))); BOOL triedEmf = FALSE; AnsiStrFromUnicode nameStr(filename); // If possible, use the filename to create the metafile handle // so that we don't have to load the metafile into memory // (GDI uses memory mapped files to access the metafile data). if (Globals::IsNt || nameStr.IsValid()) { TryWmf: if (tryWmf) { HMETAFILE hWmf; if (Globals::IsNt) { hWmf = ::GetMetaFileW(filename); } else { hWmf = ::GetMetaFileA(nameStr); } if (hWmf != NULL) { InitWmf(hWmf, wmfPlaceableFileHeader, TRUE); if (IsValid() || IsCorrupted()) { return; } } else // might be a Placeable WMF file { IStream * metaStream = CreateStreamOnFile(filename, GENERIC_READ); if (metaStream != NULL) { InitStream(metaStream, TRUE /* tryWmfOnly */); metaStream->Release(); if (IsValid() || IsCorrupted()) { return; } } } } if (!triedEmf) { triedEmf = TRUE; HENHMETAFILE hEmf; if (Globals::IsNt) { hEmf = ::GetEnhMetaFileW(filename); } else { hEmf = ::GetEnhMetaFileA(nameStr); } if (hEmf != NULL) { InitEmf(hEmf, TRUE); if (IsValid() || IsCorrupted()) { return; } } if (!tryWmf) { tryWmf = TRUE; goto TryWmf; } } } } } /**************************************************************************\ * * Function Description: * * GpMetafile constructor for read-only access to a metafile. * * Arguments: * * [IN] stream - the metafile to read for playback * * Return Value: * * NONE * * Created: * * 6/15/1999 DCurtis * \**************************************************************************/ GpMetafile::GpMetafile( IStream* stream ) : GpImage(ImageTypeMetafile) { ASSERT(stream != NULL); InitDefaults(); InitStream(stream); } GpStatus GpMetafile::GetHemf( HENHMETAFILE * hEmf ) const { if ((State == DoneRecordingMetafileState) || (State == ReadyToPlayMetafileState)) { ASSERT(Hemf != NULL); *hEmf = Hemf; Hemf = NULL; State = InvalidMetafileState; return Ok; } *hEmf = NULL; return InvalidParameter; } GpStatus GpMetafile::PrepareToPlay( GpGraphics * g, GpRecolor * recolor, ColorAdjustType adjustType, EnumerateMetafileProc enumerateCallback, VOID * callbackData, DrawImageAbort drawImageCallback, VOID* drawImageCallbackData ) const { if (State == DoneRecordingMetafileState) { ASSERT(Hemf != NULL); if (Player == NULL) { // Create a Player object Player = new MetafilePlayer(g, MaxStackSize, recolor, adjustType, enumerateCallback, callbackData, drawImageCallback, drawImageCallbackData ); if (!CheckValid(Player)) { return GenericError; } } State = ReadyToPlayMetafileState; return Ok; } if (State == ReadyToPlayMetafileState) { ASSERT(Hemf != NULL); ASSERT(Player != NULL); Player->PrepareToPlay(g, recolor, adjustType, enumerateCallback, callbackData, drawImageCallback, drawImageCallbackData ); return Ok; } return InvalidParameter; } GpStatus GpMetafile::EnumerateForPlayback( const RectF & destRect, const RectF & srcRect, Unit srcUnit, GpGraphics * g, EnumerateMetafileProc callback, // if null, just play the metafile VOID * callbackData, GpRecolor * recolor, ColorAdjustType adjustType, DrawImageAbort drawImageCallback, VOID* drawImageCallbackData ) const { ASSERT (IsValid()); if ((destRect.Width == 0) || (destRect.Height == 0) || (srcRect.Width == 0) || (srcRect.Height == 0) || (Header.IsEmf() && (Header.EmfHeader.nRecords <= 2))) { return Ok; // nothing to play } GpRectF metaSrcRect = srcRect; GpRectF metaDestRect = destRect; // The metafile player does not handle negative width/height // in srcRect and destRect, so handle any negative values // by setting up a flipping transform. GpMatrix flipMatrix; // starts as identity BOOL posWidths; BOOL posHeights; posWidths = ((metaSrcRect.Width >= 0) && (metaDestRect.Width >= 0)); posHeights = ((metaSrcRect.Height >= 0) && (metaDestRect.Height >= 0)); if (!posWidths || !posHeights) { if (!posWidths) { if (metaSrcRect.Width < 0) { if (metaDestRect.Width < 0) { posWidths = TRUE; metaSrcRect.X = metaSrcRect.GetRight(); metaSrcRect.Width = -(metaSrcRect.Width); metaDestRect.X = metaDestRect.GetRight(); metaDestRect.Width = -(metaDestRect.Width); } else { metaSrcRect.X = metaSrcRect.GetRight(); metaSrcRect.Width = -(metaSrcRect.Width); } } else // metaDestRect.Width < 0 { metaDestRect.X = metaDestRect.GetRight(); metaDestRect.Width = -(metaDestRect.Width); } } if (!posHeights) { if (metaSrcRect.Height < 0) { if (metaDestRect.Height < 0) { posHeights = TRUE; metaSrcRect.Y = metaSrcRect.GetBottom(); metaSrcRect.Height = -(metaSrcRect.Height); metaDestRect.Y = metaDestRect.GetBottom(); metaDestRect.Height = -(metaDestRect.Height); } else { metaSrcRect.Y = metaSrcRect.GetBottom(); metaSrcRect.Height = -(metaSrcRect.Height); } } else // metaDestRect.Height < 0 { metaDestRect.Y = metaDestRect.GetBottom(); metaDestRect.Height = -(metaDestRect.Height); } } REAL scaleX = 1.0f; REAL scaleY = 1.0f; REAL dX = 0.0f; REAL dY = 0.0f; // Create a matrix that is the equivalent of: // 1) translate to the origin // 2) do the flip // 3) translate back if (!posWidths) { scaleX = -1.0f; dX = metaDestRect.X + metaDestRect.GetRight(); } if (!posHeights) { scaleY = -1.0f; dY = metaDestRect.Y + metaDestRect.GetBottom(); } flipMatrix.Translate(dX, dY, MatrixOrderPrepend); flipMatrix.Scale(scaleX, scaleY, MatrixOrderPrepend); } // Note that even though the visibility of the destRect might be // fully visible, we should still setup the clipping because: // (1) we might do cropping based on the srcRect // (2) the frameRect of the metafile might not include all // the actual drawing within the metafile. GpStatus status = GenericError; // Must convert the source rect into UnitPixels (if not already // in pixel units), to account for the dpi of the source metafile. REAL multiplierX; REAL multiplierY; GetPixelMultipliers(srcUnit, Header.GetDpiX(), Header.GetDpiY(), &multiplierX, &multiplierY); GpRectF pixelsSrcRect; pixelsSrcRect.X = metaSrcRect.X * multiplierX; pixelsSrcRect.Y = metaSrcRect.Y * multiplierY; pixelsSrcRect.Width = metaSrcRect.Width * multiplierX; pixelsSrcRect.Height = metaSrcRect.Height * multiplierY; INT saveId = g->Save(); if (saveId != 0) { // We need to take into account the region from the source that we // are drawing in order to do that we need to re-translate and // rescale and the transform. The clipping will take care of only // drawing the region that we are interested in. // In order to acheive this we need to translate the dest rect back // to the origin. Scale it by the same factor as the scale of the // src rect and then translate it back to when it should be which // is the scaled version of the left cropping of the src image. GpMatrix preFlipPreCropTransform; g->GetWorldTransform(preFlipPreCropTransform); // apply the flipping transform g->MultiplyWorldTransform(flipMatrix, MatrixOrderPrepend); BOOL widthsDifferent = (Header.Width != pixelsSrcRect.Width); BOOL heightsDifferent = (Header.Height != pixelsSrcRect.Height); BOOL cropOrOffset = ((Header.X != pixelsSrcRect.X) || (Header.Y != pixelsSrcRect.Y) || widthsDifferent || heightsDifferent); if (cropOrOffset) { g->TranslateWorldTransform(((((REAL)(Header.X - pixelsSrcRect.X)) *metaDestRect.Width) /pixelsSrcRect.Width) + metaDestRect.X, ((((REAL)(Header.Y - pixelsSrcRect.Y)) *metaDestRect.Height)/pixelsSrcRect.Height) + metaDestRect.Y); REAL xScale = 1.0f; REAL yScale = 1.0f; if (widthsDifferent) { xScale = (REAL) Header.Width / pixelsSrcRect.Width; } if (heightsDifferent) { yScale = (REAL) Header.Height / pixelsSrcRect.Height; } g->ScaleWorldTransform(xScale, yScale); g->TranslateWorldTransform(-metaDestRect.X, -metaDestRect.Y); } // We don't use the deviceRect if we're rendering to a bitmap. GpMatrix flipAndCropTransform; GpRectF deviceRect = metaDestRect; // sets the PreContainerMatrix to the WorldToDevice Transform, which // includes the flipping and cropping transforms. if ((status = this->PrepareToPlay(g, recolor, adjustType, callback, callbackData, drawImageCallback, drawImageCallbackData)) != Ok) { goto CleanUp; } ASSERT(Player != NULL); State = PlayingMetafileState; BOOL renderToBitmap = FALSE; GpMatrix * playMatrix = &(Player->PreContainerMatrix); BOOL isTranslateScale = playMatrix->IsTranslateScale(); // On Win9x and WinNT (except Whistler and beyond), stretchblt calls // don't work if there is any flipping. // On Win9x text does not work if there is any flipping. // On WinNT, bitmap fonts don't work for 90,180,270 degree rotation // (but we map all bitmap fonts to true-type fonts anyway). if (isTranslateScale) { // if there is any flipping, render to a bitmap if ((playMatrix->GetM11() < 0.0f) || (playMatrix->GetM22() < 0.0f)) { isTranslateScale = FALSE; renderToBitmap = TRUE; } } else { // It's okay to render rotated directly to the HDC on NT, // unless the dest is a metafile or the src is a WMF. renderToBitmap = (!Globals::IsNt || (g->Type == GpGraphics::GraphicsMetafile) || Header.IsWmf()); } // Save what we have done into flipAndCropTransform. We will prepare the // container with this world transform since the precontainerMatrix // is only for the Downlevel and it needs that modified transform g->GetWorldTransform(flipAndCropTransform); // Restore the world transform to it's original self // (w/o flipping and cropping transform applied). g->SetWorldTransform(preFlipPreCropTransform); // When we render to a bitmap, we render the entire metafile to // the entire bitmap and then we clip out the cropped part of the // metafile from the bitmap. So we have to set the clipping // when we render to a bitmap if there is any cropping. // It would be nice as an enhancement to just draw to a pre-cropped // bitmap instead of clipping out part of the bitmap, but the math // for that is tricky. if ((!renderToBitmap) || cropOrOffset) { GpMatrix worldToDeviceTransform; g->GetWorldToDeviceTransform(&worldToDeviceTransform); if (isTranslateScale) { worldToDeviceTransform.TransformRect(deviceRect); } // Don't set the clipping if we're rendering to a bitmap, // because the rendering into the bitmap will do the clipping // automatically, and if we also clip against the graphics, we // sometimes clip too much, which can cause jagged edges on // rotated metafiles. // Clipping into a metafile causes problems. For example, if // we're drawing outside the bounds of the referenece HDC, it // works fine, but then when we add clipping into the HDC, it doesn't // work anymore -- nothing gets drawn into the metafile, even though // everything is within the clipping rect (but the clipping rect is // outside the bounds of the reference HDC). if (g->Type != GpGraphics::GraphicsMetafile) { if ((!(renderToBitmap && cropOrOffset)) && isTranslateScale) { g->SetClip(metaDestRect, CombineModeIntersect); } else // rendering to a bitmap with cropping or // rotating to the screen { // Since we want the filtered (smooth) edges on the // bitmap, we have to add in a little extra room on // the edges of our clip rect. // On rotations we need to inflate by one pixel also // because it seems that GDI doesn't rasterize clipregions // the same we that it rasterized rects. Do rects on the // edges can have pixels missing. We might be introducing // more pixels that should have been clipped out but we // can live with that for now. GpRectF tmpClipRect = metaDestRect; REAL xSize; REAL ySize; g->GetWorldPixelSize(xSize, ySize); // add 1 pixel all the way around tmpClipRect.Inflate(xSize, ySize); g->SetClip(tmpClipRect, CombineModeIntersect); } if (isTranslateScale) { // We need to intersect the destRect with the Visible Clip // in order to make sure that we don't draw outside the bounds // in Win9x since we can't use a MetaRgn GpRectF clipBounds; g->GetVisibleClipBounds(clipBounds); worldToDeviceTransform.TransformRect(clipBounds); GpRectF::Intersect(deviceRect, deviceRect, clipBounds); } } } // If we're playing an EMF+ into another metafile, we have to be // careful not to double-transform points. The HDC will have // the srcRect to destRect transform in it, and the graphics might // have a transform too, so we can end up double-transforming the // points of any GDI+ records that are in an EMF+ file. // One easy way to get around that is that if we are playing an // EMF+ dual, we could just play the down-level records (i.e. play it // as an EMF, not an EMF+), so that all the records get transformed // the same way. But of course, that doesn't work if it's an // EMF+ only file. A solution that works for both EMF+ dual and // EMF+ only is to force the GDI+ transform to be the identity so that // the down-level records that are generated by DriverMeta are in // the original coordinate system of the metafile, not in the // destination coordinate system (which then get transformed again // erroneously). if (Header.IsWmf() || Header.IsEmf()) { status = g->EnumEmf(Player, Hemf, metaDestRect, pixelsSrcRect, deviceRect, Header.GetType(), isTranslateScale, renderToBitmap, flipAndCropTransform); } else { ASSERT(Header.IsEmfPlus()); // When playing from a metafile into a metafile, Win9x does NOT // allow you to override (reset) the srcRect->destRect metafile // transform. So to keep from double transforming the records, // we have to set the GDI+ transform to identity, instead of // setting the HDC transform to identity as we would typically do. // When rendering to a bitmap, we don't have to worry about // double-transforming, because we play the metafile to the // bitmap HDC, not to the dest metafile hdc, so there won't // be a transform on the metafile hdc to mess us up. INT containerId; if ((g->Type != GpGraphics::GraphicsMetafile) || renderToBitmap) { // Now apply the flipping matrix. // The g->Restore call below will reset the transform. g->MultiplyWorldTransform(flipMatrix, MatrixOrderPrepend); GpRectF gdiDestRect = metaDestRect; // We need to calculate our transform so that the last point in the // src maps to the last point in the destination. This is how GDI does // it and we also need to do it so that we can play metafile properly if (pixelsSrcRect.Width >= 2.0f) { pixelsSrcRect.Width -= 1.0f; } if (pixelsSrcRect.Height >= 2.0f) { pixelsSrcRect.Height -= 1.0f; } if (gdiDestRect.Width >= 2.0f) { gdiDestRect.Width -= 1.0f; } if (gdiDestRect.Height >= 2.0f) { gdiDestRect.Height -= 1.0f; } containerId = g->BeginContainer( gdiDestRect, pixelsSrcRect, UnitPixel, (REAL)Header.LogicalDpiX, (REAL)Header.LogicalDpiY, Header.IsDisplay()); } else // we're drawing into a metafile { containerId = g->BeginContainer( TRUE, // force xform to identity (REAL)Header.LogicalDpiX, (REAL)Header.LogicalDpiY, Header.IsDisplay()); } if (containerId != 0) { // There may be GDI records that we need to play! status = g->EnumEmfPlusDual(Player, Hemf, metaDestRect, deviceRect, isTranslateScale, renderToBitmap); g->EndContainer(containerId); Player->DonePlaying(); // free up objects created by Player } // make sure the status reflect the abort state of the player ASSERT(!Player->EnumerateAborted || (status == Aborted)); } CleanUp: g->Restore(saveId); } // Don't change the state unless we were playing the metafile if (State == PlayingMetafileState) { State = ReadyToPlayMetafileState; } return status; } /**************************************************************************\ * * Function Description: * * Initialize the metafile object members to their default values. * * Arguments: * * NONE * * Return Value: * * NONE * * Created: * * 6/15/1999 DCurtis * \**************************************************************************/ VOID GpMetafile::InitDefaults() { ThreadId = 0; State = InvalidMetafileState; Filename = NULL; Stream = NULL; Hemf = NULL; MetaGraphics = NULL; Player = NULL; MaxStackSize = GDIP_SAVE_STACK_SIZE; DeleteHemf = TRUE; RequestedMetaGraphics = FALSE; GpMemset(&Header, 0, sizeof(Header)); // Set the version for recording. If we're plyaing back, // this will get overwritten later. Header.Version = EMFPLUS_VERSION; } GpStatus GpMetafile::GetImageInfo( ImageInfo * imageInfo ) const { ASSERT(imageInfo != NULL); ASSERT(IsValid()); if ((State == DoneRecordingMetafileState) || (State == ReadyToPlayMetafileState)) { if (Header.IsEmfOrEmfPlus()) { imageInfo->RawDataFormat = IMGFMT_EMF; } else // Wmf { imageInfo->RawDataFormat = IMGFMT_WMF; } imageInfo->PixelFormat = PIXFMT_32BPP_RGB; imageInfo->Width = Header.Width; imageInfo->Height = Header.Height; imageInfo->TileWidth = Header.Width; imageInfo->TileHeight = 1; imageInfo->Xdpi = Header.DpiX; imageInfo->Ydpi = Header.DpiY; imageInfo->Flags = SinkFlagsTopDown | SinkFlagsFullWidth | SinkFlagsScalable | SinkFlagsHasAlpha; return Ok; } return InvalidParameter; } GpImage * GpMetafile::Clone() const { GpMetafile * clonedMetafile = NULL; if ((State == DoneRecordingMetafileState) || (State == ReadyToPlayMetafileState)) { if (Header.IsEmfOrEmfPlus()) { HENHMETAFILE hEmf = CopyEnhMetaFileA(Hemf, NULL); if (hEmf != NULL) { clonedMetafile = new GpMetafile(hEmf, TRUE); if (clonedMetafile != NULL) { if (!clonedMetafile->IsValid()) { DeleteEnhMetaFile(hEmf); clonedMetafile->Hemf = NULL; clonedMetafile->Dispose(); clonedMetafile = NULL; } } } } else // Wmf { HMETAFILE hWmf = CopyMetaFileA((HMETAFILE)Hemf, NULL); if (hWmf != NULL) { WmfPlaceableFileHeader wmfPlaceableFileHeader; wmfPlaceableFileHeader.Key = GDIP_WMF_PLACEABLEKEY; wmfPlaceableFileHeader.Hmf = 0; wmfPlaceableFileHeader.BoundingBox.Left = static_cast(Header.X); wmfPlaceableFileHeader.BoundingBox.Right = static_cast(Header.X + Header.Width); wmfPlaceableFileHeader.BoundingBox.Top = static_cast(Header.Y); wmfPlaceableFileHeader.BoundingBox.Bottom = static_cast(Header.Y + Header.Height); wmfPlaceableFileHeader.Inch = static_cast(GpRound(Header.DpiX)); wmfPlaceableFileHeader.Reserved = 0; wmfPlaceableFileHeader.Checksum = GetWmfPlaceableCheckSum(&wmfPlaceableFileHeader); clonedMetafile = new GpMetafile(hWmf, &wmfPlaceableFileHeader, TRUE); if (clonedMetafile != NULL) { if (!clonedMetafile->IsValid()) { DeleteMetaFile(hWmf); clonedMetafile->Hemf = NULL; clonedMetafile->Dispose(); clonedMetafile = NULL; } } } } } return clonedMetafile; } GpImage* GpMetafile::CloneColorAdjusted( GpRecolor * recolor, ColorAdjustType adjustType ) const { ASSERT(recolor != NULL); if ((State == DoneRecordingMetafileState) || (State == ReadyToPlayMetafileState)) { GpMetafile* clonedMetafile; // FrameRect is Inclusive-Inclusive so subtrace 1 device unit GpRectF frameRect((REAL)Header.X, (REAL)Header.Y, (REAL)(Header.Width - 1), (REAL)(Header.Height - 1)); EmfType type; if (Header.Type <= MetafileTypeEmf) { type = EmfTypeEmfOnly; } else { // we don't need the down-level dual sections for embedded files type = EmfTypeEmfPlusOnly; } // It doesn't matter if we lose the description string, since this // metafile is just being embedded inside another one anyway. clonedMetafile = new GpMetafile(Globals::DesktopIc, type, &frameRect,MetafileFrameUnitPixel,NULL); if ((clonedMetafile != NULL) && (clonedMetafile->IsValid())) { GpStatus status; GpPageUnit srcUnit; GpRectF srcRect; GpGraphics * g = clonedMetafile->GetGraphicsContext(); ASSERT (g != NULL); this->GetBounds(&srcRect, &srcUnit); // We pass Inclusive-Exclusive bounds to play so add 1 device // unit to the framerect frameRect.Width++; frameRect.Height++; status = this->Play(frameRect, srcRect, srcUnit, g, recolor, adjustType); delete g; if ((status == Ok) && (clonedMetafile->State == DoneRecordingMetafileState)) { return clonedMetafile; } } delete clonedMetafile; } return NULL; } GpStatus GpMetafile::ColorAdjust( GpRecolor * recolor, ColorAdjustType adjustType ) { ASSERT(recolor != NULL); GpMetafile * clone; if (DeleteHemf && ((clone = (GpMetafile *)CloneColorAdjusted(recolor, adjustType)) != NULL)) { CleanUp(); InitDefaults(); if (GetMetafileHeader(clone->Hemf, Header) == Ok) { Hemf = clone->Hemf; DeleteHemf = TRUE; State = DoneRecordingMetafileState; clone->DeleteHemf = FALSE; delete clone; return Ok; } } return GenericError; } VOID GpMetafile::Dispose() { delete this; } class RemoveDualRecords { public: BYTE * MetaData; INT Size; INT NumRecords; BOOL GetGdiRecords; RemoveDualRecords() { Init(); } VOID Init() { MetaData = NULL; Size = 0; NumRecords = 0; GetGdiRecords = TRUE; // so we write the EMR_HEADER record } VOID GetRecord(CONST ENHMETARECORD * emfRecord) { UINT recordSize = emfRecord->nSize; if (MetaData != NULL) { GpMemcpy(MetaData, emfRecord, recordSize); MetaData += recordSize; } Size += recordSize; NumRecords++; } }; extern "C" int CALLBACK EnumEmfRemoveDualRecords( HDC hdc, // should be NULL HANDLETABLE FAR * gdiHandleTable, CONST ENHMETARECORD * emfRecord, int numHandles, LPARAM removeDualRecords ) { if ((emfRecord != NULL) && (emfRecord->nSize >= sizeof(EMR)) && (removeDualRecords != NULL)) { if (IsEmfPlusRecord(emfRecord)) { // See if the last record of this set of EMF+ records is a GetDC // record. If it is, then we know to play the next set of // GDI records that we encounter. // I prefer not to have to parse through all these records, // but there is always the slight possibility that this will // result in a false positive. But the worst thing that can // happen is that we write a little too much data to the stream. EmfPlusRecord * lastRecord; lastRecord = (EmfPlusRecord *)(((BYTE *)emfRecord) + emfRecord->nSize - sizeof(EmfPlusRecord)); ((RemoveDualRecords *)removeDualRecords)->GetGdiRecords = ((lastRecord->Type == EmfPlusRecordTypeGetDC) && (lastRecord->Size == sizeof(EmfPlusRecord)) && (lastRecord->DataSize == 0)); } else if ((emfRecord->iType != EMR_EOF) && // Write EOF record (!(((RemoveDualRecords *)removeDualRecords)->GetGdiRecords))) { return 1; // skip this GDI record } ((RemoveDualRecords *)removeDualRecords)->GetRecord(emfRecord); } else { WARNING(("Bad Enumeration Parameter")); } return 1; } extern "C" int CALLBACK EnumEmfToStream( HDC hdc, // handle to device context HANDLETABLE FAR * gdiHandleTable, // pointer to metafile handle table CONST ENHMETARECORD * emfRecord, // pointer to metafile record int numHandles, // count of objects LPARAM stream // pointer to optional data ) { if ((emfRecord != NULL) && (emfRecord->nSize >= sizeof(EMR)) && (stream != NULL)) { ((IStream *)stream)->Write(emfRecord, emfRecord->nSize, NULL); } else { WARNING(("Bad Enumeration Parameter")); } return 1; } class MetafileData : public ObjectTypeData { public: INT32 MetaType; INT32 MetaDataSize; }; /**************************************************************************\ * * Function Description: * * Get the metafile data. * * Arguments: * * [IN] dataBuffer - fill this buffer with the data * [IN/OUT] size - IN - size of buffer; OUT - number bytes written * * Return Value: * * GpStatus - Ok or error code * * Created: * * 9/13/1999 DCurtis * \**************************************************************************/ GpStatus GpMetafile::GetData( IStream * stream ) const { ASSERT (stream != NULL); if ((State != DoneRecordingMetafileState) && (State != ReadyToPlayMetafileState)) { WARNING(("Wrong State To GetData")); return WrongState; } ASSERT(Hemf != NULL); MetafileData metafileData; metafileData.Type = ImageTypeMetafile; if (Header.IsWmf()) { INT wmfDataSize = GetMetaFileBitsEx((HMETAFILE)Hemf, 0, NULL); if (wmfDataSize <= 0) { WARNING(("Empty WMF")); return Win32Error; } BYTE * wmfData = (BYTE *)GpMalloc(wmfDataSize); if (wmfData == NULL) { return OutOfMemory; } if (GetMetaFileBitsEx((HMETAFILE)Hemf, wmfDataSize, wmfData) == 0) { WARNING(("Problem retrieving WMF Data")); GpFree(wmfData); return Win32Error; } // We don't save MetafileTypeWmf -- we convert it to the Placeable type metafileData.MetaType = MetafileTypeWmfPlaceable; metafileData.MetaDataSize = wmfDataSize; stream->Write(&metafileData, sizeof(metafileData), NULL); ASSERT(sizeof(WmfPlaceableFileHeader) == 22); #define PLACEABLE_BUFFER_SIZE (sizeof(WmfPlaceableFileHeader) + 2) BYTE placeableBuffer[PLACEABLE_BUFFER_SIZE]; WmfPlaceableFileHeader * wmfPlaceableFileHeader = (WmfPlaceableFileHeader *)placeableBuffer; REAL aveDpi; // set pad word to 0 *((INT16 *)(placeableBuffer + sizeof(WmfPlaceableFileHeader))) = 0; aveDpi = (Header.GetDpiX() + Header.GetDpiY()) / 2.0f; wmfPlaceableFileHeader->Key = GDIP_WMF_PLACEABLEKEY; wmfPlaceableFileHeader->Hmf = 0; wmfPlaceableFileHeader->BoundingBox.Left = static_cast(Header.X); wmfPlaceableFileHeader->BoundingBox.Top = static_cast(Header.Y); wmfPlaceableFileHeader->BoundingBox.Right = static_cast(Header.X + Header.Width); wmfPlaceableFileHeader->BoundingBox.Bottom = static_cast(Header.Y + Header.Height); wmfPlaceableFileHeader->Inch = static_cast(GpRound(aveDpi)); wmfPlaceableFileHeader->Reserved = 0; wmfPlaceableFileHeader->Checksum = GetWmfPlaceableCheckSum(wmfPlaceableFileHeader); stream->Write(placeableBuffer, PLACEABLE_BUFFER_SIZE, NULL); stream->Write(wmfData, wmfDataSize, NULL); GpFree(wmfData); // align if ((wmfDataSize & 0x03) != 0) { INT pad = 0; stream->Write(&pad, 4 - (wmfDataSize & 0x03), NULL); } } else if (!Header.IsEmfPlusDual()) { INT emfDataSize = GetEnhMetaFileBits(Hemf, 0, NULL); if (emfDataSize <= 0) { WARNING(("Empty EMF")); return Win32Error; } metafileData.MetaType = Header.GetType(); metafileData.MetaDataSize = emfDataSize; stream->Write(&metafileData, sizeof(metafileData), NULL); if (!::EnumEnhMetaFile(NULL, Hemf, EnumEmfToStream, stream, NULL)) { WARNING(("Problem retrieving EMF Data")); return Win32Error; } } else // it is EMF+ Dual. Remove the dual records for embedding. { RemoveDualRecords removeDualRecords; // First, figure out how big a buffer we need to allocate if (!::EnumEnhMetaFile(NULL, Hemf, EnumEmfRemoveDualRecords, &removeDualRecords, NULL)) { WARNING(("Problem retrieving EMF Data")); return Win32Error; } INT emfDataSize = removeDualRecords.Size; BYTE * emfData = (BYTE *)GpMalloc(emfDataSize); if (emfData == NULL) { return OutOfMemory; } removeDualRecords.Init(); removeDualRecords.MetaData = emfData; if (!::EnumEnhMetaFile(NULL, Hemf, EnumEmfRemoveDualRecords, &removeDualRecords, NULL)) { WARNING(("Problem retrieving EMF Data")); GpFree(emfData); return Win32Error; } // make sure we get the same value back the 2nd time ASSERT(emfDataSize == removeDualRecords.Size); // We convert MetafileTypeEmfPlusDual into MetafileTypeEmfPlusOnly metafileData.MetaType = MetafileTypeEmfPlusOnly; metafileData.MetaDataSize = removeDualRecords.Size; stream->Write(&metafileData, sizeof(metafileData), NULL); ((ENHMETAHEADER3 *)emfData)->nBytes = removeDualRecords.Size; ((ENHMETAHEADER3 *)emfData)->nRecords = removeDualRecords.NumRecords; stream->Write(emfData, removeDualRecords.Size, NULL); GpFree(emfData); } return Ok; } UINT GpMetafile::GetDataSize() const { if ((State != DoneRecordingMetafileState) && (State != ReadyToPlayMetafileState)) { WARNING(("Wrong State To GetDataSize")); return 0; } ASSERT(Hemf != NULL); UINT dataSize = sizeof(MetafileData); if (Header.IsWmf()) { INT wmfDataSize = GetMetaFileBitsEx((HMETAFILE)Hemf, 0, NULL); if (wmfDataSize <= 0) { WARNING(("Empty WMF")); return 0; } // add aligned size of the placeable header and aligned wmf size dataSize += 24 + ((wmfDataSize + 3) & ~3); } else if (!Header.IsEmfPlusDual()) { INT emfDataSize = GetEnhMetaFileBits(Hemf, 0, NULL); if (emfDataSize <= 0) { WARNING(("Empty EMF")); return 0; } dataSize += emfDataSize; } else // it is EMF+ Dual. Remove the dual records for embedding. { RemoveDualRecords removeDualRecords; if (!::EnumEnhMetaFile(NULL, Hemf, EnumEmfRemoveDualRecords, &removeDualRecords, NULL)) { WARNING(("Problem retrieving EMF Data")); return 0; } dataSize += removeDualRecords.Size; } return dataSize; } /**************************************************************************\ * * Function Description: * * Read the metafile object from memory. * * Arguments: * * [IN] data - the data to set the metafile with * [IN] size - the size of the data * * Return Value: * * GpStatus - Ok or failure status * * Created: * * 4/26/1999 DCurtis * \**************************************************************************/ GpStatus GpMetafile::SetData( const BYTE * dataBuffer, UINT size ) { ASSERT ((GpImageType)(((MetafileData *)dataBuffer)->Type) == ImageTypeMetafile); InitDefaults(); if (dataBuffer == NULL) { WARNING(("dataBuffer is NULL")); return InvalidParameter; } if (size < sizeof(MetafileData)) { WARNING(("size too small")); return InvalidParameter; } const MetafileData * metaData; metaData = reinterpret_cast(dataBuffer); if (!metaData->MajorVersionMatches()) { WARNING(("Version number mismatch")); return InvalidParameter; } dataBuffer += sizeof(MetafileData); size -= sizeof(MetafileData); MetafileType type = (MetafileType)metaData->MetaType; UINT metaDataSize = metaData->MetaDataSize; if (type == MetafileTypeWmfPlaceable) { HMETAFILE hWmf; if (size < (metaDataSize + 24)) { WARNING(("size too small")); return InvalidParameter; } hWmf = SetMetaFileBitsEx(metaDataSize, dataBuffer + 24); if (hWmf != NULL) { if (GetMetafileHeader(hWmf, (WmfPlaceableFileHeader*)dataBuffer, Header) == Ok) { Hemf = (HENHMETAFILE)hWmf; State = DoneRecordingMetafileState; return Ok; } DeleteMetaFile(hWmf); } } else { // We'll let the object think it's dual, even if we've removed // all the dual records. It shouldn't hurt anything. HENHMETAFILE hEmf; if (size < metaDataSize) { WARNING(("size too small")); return InvalidParameter; } hEmf = SetEnhMetaFileBits(metaDataSize, dataBuffer); if (hEmf != NULL) { BOOL isCorrupted; if (GetMetafileHeader(hEmf, Header, &isCorrupted) == Ok) { Hemf = hEmf; State = DoneRecordingMetafileState; return Ok; } if (isCorrupted) { State = CorruptedMetafileState; } DeleteEnhMetaFile(hEmf); } } return GenericError; } class CommentEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeComment); return; } }; class GetDCEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeGetDC); // Flag that the next down-level records should be played. #if 0 // This is now done in the enumerator, so that it will happen // for enumeration as well as playback. player->PlayEMFRecords = TRUE; #endif } }; #define EMFPLUS_MAJORVERSION(v) ((v) & 0xFFFF0000) #define EMFPLUS_MINORVERSION(v) ((v) & 0x0000FFFF) #define EMF_SKIP_ALL_MULTIFORMAT_SECTIONS 0x7FFFFFFF #define MULTIFORMATSTARTEPR_MINSIZE (sizeof(UINT32) + sizeof(UINT32)) // Note: nesting multiformat records does NOT work. class MultiFormatStartEPR : public EmfPlusRecordPlay { protected: UINT32 NumSections; UINT32 Version[1]; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeMultiFormatStart); if (dataSize < MULTIFORMATSTARTEPR_MINSIZE) { WARNING(("MultiFormatStartEPR::Play dataSize is too small")); return; } UINT sectionToPlay = EMF_SKIP_ALL_MULTIFORMAT_SECTIONS; if (NumSections > 0) { if (dataSize < MULTIFORMATSTARTEPR_MINSIZE + ((NumSections - 1) * sizeof(UINT32))) { WARNING(("MultiFormatStartEPR::Play dataSize is too small")); return; } if ((Version[0] == EMFPLUS_VERSION) || (NumSections == 1)) { sectionToPlay = 1; // start counting from 1, not 0 } else { UINT playVersion = 0; UINT curVersion; // The multiformat section must match the major version. // The first format whose minor version <= the current // minor version is the one we play. If we don't find // one of those, then we play the one whose minor version // is closest to the current minor version. for (UINT i = 0; i < NumSections; i++) { curVersion = Version[i]; if (EMFPLUS_MAJORVERSION(curVersion) == EMFPLUS_MAJORVERSION(EMFPLUS_VERSION)) { if (EMFPLUS_MINORVERSION(curVersion) <= EMFPLUS_MINORVERSION(EMFPLUS_VERSION)) { sectionToPlay = i + 1; break; } else if ((playVersion == 0) || (EMFPLUS_MINORVERSION(curVersion) < EMFPLUS_MINORVERSION(playVersion))) { playVersion = curVersion; sectionToPlay = i + 1; } } } } } player->MultiFormatSection = sectionToPlay; player->CurFormatSection = 0; player->PlayMultiFormatSection = FALSE; } }; class MultiFormatSectionEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeMultiFormatSection); if (player->MultiFormatSection != 0) { player->PlayMultiFormatSection = (++(player->CurFormatSection) == player->MultiFormatSection); } } }; class MultiFormatEndEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeMultiFormatEnd); player->MultiFormatSection = 0; player->CurFormatSection = 0; player->PlayMultiFormatSection = TRUE; } }; class SetAntiAliasModeEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetAntiAliasMode); player->Graphics->SetAntiAliasMode(GetAntiAliasMode(flags)); } }; class SetTextRenderingHintEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetTextRenderingHint); player->Graphics->SetTextRenderingHint(GetTextRenderingHint(flags)); } }; class SetTextContrastEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetTextContrast); player->Graphics->SetTextContrast(GetTextContrast(flags)); } }; class SetInterpolationModeEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetInterpolationMode); player->Graphics->SetInterpolationMode(GetInterpolationMode(flags)); } }; class SetPixelOffsetModeEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetPixelOffsetMode); player->Graphics->SetPixelOffsetMode(GetPixelOffsetMode(flags)); } }; class SetCompositingModeEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetCompositingMode); player->Graphics->SetCompositingMode(GetCompositingMode(flags)); } }; class SetCompositingQualityEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetCompositingQuality); player->Graphics->SetCompositingQuality(GetCompositingQuality(flags)); } }; class SetRenderingOriginEPR : public EmfPlusRecordPlay { INT x; INT y; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetRenderingOrigin); player->Graphics->SetRenderingOrigin(x, y); } }; #define SAVEEPR_MINSIZE (sizeof(UINT32)) class SaveEPR : public EmfPlusRecordPlay { protected: UINT32 StackIndex; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSave); if (dataSize < SAVEEPR_MINSIZE) { WARNING(("SaveEPR::Play dataSize is too small")); return; } player->NewSave(StackIndex, player->Graphics->Save()); } }; #define RESTOREEPR_MINSIZE (sizeof(UINT32)) class RestoreEPR : public EmfPlusRecordPlay { protected: UINT32 StackIndex; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeRestore); if (dataSize < RESTOREEPR_MINSIZE) { WARNING(("RestoreEPR::Play dataSize is too small")); return; } player->Graphics->Restore(player->GetSaveID(StackIndex)); } }; #define BEGINCONTAINEREPR_MINSIZE (sizeof(GpRectF) + sizeof(GpRectF) + sizeof(UINT32)) class BeginContainerEPR : public EmfPlusRecordPlay { protected: GpRectF DestRect; GpRectF SrcRect; UINT32 StackIndex; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeBeginContainer); if (dataSize < BEGINCONTAINEREPR_MINSIZE) { WARNING(("BeginContainerEPR::Play dataSize is too small")); return; } player->NewSave(StackIndex, player->Graphics->BeginContainer(DestRect, SrcRect, GetPageUnit(flags))); } }; #define BEGINCONTAINERNOPARAMSEPR_MINSIZE (sizeof(UINT32)) class BeginContainerNoParamsEPR : public EmfPlusRecordPlay { protected: UINT32 StackIndex; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeBeginContainerNoParams); if (dataSize < BEGINCONTAINERNOPARAMSEPR_MINSIZE) { WARNING(("BeginContainerNoParamsEPR::Play dataSize is too small")); return; } player->NewSave(StackIndex, player->Graphics->BeginContainer()); } }; #define ENDCONTAINEREPR_MINSIZE (sizeof(UINT32)) class EndContainerEPR : public EmfPlusRecordPlay { protected: UINT32 StackIndex; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeEndContainer); if (dataSize < ENDCONTAINEREPR_MINSIZE) { WARNING(("EndContainerEPR::Play dataSize is too small")); return; } player->Graphics->EndContainer(player->GetSaveID(StackIndex)); } }; #define SETWORLDTRANSFORMEPR_MINSIZE GDIP_MATRIX_SIZE class SetWorldTransformEPR : public EmfPlusRecordPlay { protected: REAL MatrixData[6]; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetWorldTransform); if (dataSize < SETWORLDTRANSFORMEPR_MINSIZE) { WARNING(("SetWorldTransformEPR::Play dataSize is too small")); return; } GpMatrix matrix(MatrixData[0], MatrixData[1], MatrixData[2], MatrixData[3], MatrixData[4], MatrixData[5]); player->Graphics->SetWorldTransform(matrix); } }; class ResetWorldTransformEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeResetWorldTransform); player->Graphics->ResetWorldTransform(); } }; #define MULTIPLYWORLDTRANSFORMEPR_MINSIZE GDIP_MATRIX_SIZE class MultiplyWorldTransformEPR : public EmfPlusRecordPlay { protected: REAL MatrixData[6]; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeMultiplyWorldTransform); if (dataSize < MULTIPLYWORLDTRANSFORMEPR_MINSIZE) { WARNING(("MultiplyWorldTransformEPR::Play dataSize is too small")); return; } GpMatrix matrix(MatrixData[0], MatrixData[1], MatrixData[2], MatrixData[3], MatrixData[4], MatrixData[5]); player->Graphics->MultiplyWorldTransform(matrix, GetMatrixOrder(flags)); } }; #define TRANSLATEWORLDTRANSFORMEPR_MINSIZE (sizeof(REAL) + sizeof(REAL)) class TranslateWorldTransformEPR : public EmfPlusRecordPlay { protected: REAL Dx; REAL Dy; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeTranslateWorldTransform); if (dataSize < TRANSLATEWORLDTRANSFORMEPR_MINSIZE) { WARNING(("TranslateWorldTransformEPR::Play dataSize is too small")); return; } player->Graphics->TranslateWorldTransform(Dx, Dy, GetMatrixOrder(flags)); } }; #define SCALEWORLDTRANSFORMEPR_MINSIZE (sizeof(REAL) + sizeof(REAL)) class ScaleWorldTransformEPR : public EmfPlusRecordPlay { protected: REAL Sx; REAL Sy; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeScaleWorldTransform); if (dataSize < SCALEWORLDTRANSFORMEPR_MINSIZE) { WARNING(("ScaleWorldTransformEPR::Play dataSize is too small")); return; } player->Graphics->ScaleWorldTransform(Sx, Sy, GetMatrixOrder(flags)); } }; #define ROTATEWORLDTRANSFORMEPR_MINSIZE (sizeof(REAL)) class RotateWorldTransformEPR : public EmfPlusRecordPlay { protected: REAL Angle; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeRotateWorldTransform); if (dataSize < ROTATEWORLDTRANSFORMEPR_MINSIZE) { WARNING(("RotateWorldTransformEPR::Play dataSize is too small")); return; } player->Graphics->RotateWorldTransform(Angle, GetMatrixOrder(flags)); } }; #define SETPAGETRANSFORMEPR_MINSIZE (sizeof(REAL)) class SetPageTransformEPR : public EmfPlusRecordPlay { protected: REAL Scale; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetPageTransform); if (dataSize < SETPAGETRANSFORMEPR_MINSIZE) { WARNING(("SetPageTransformEPR::Play dataSize is too small")); return; } player->Graphics->SetPageTransform(GetPageUnit(flags), Scale); } }; class ResetClipEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeResetClip); player->Graphics->ResetClip(); } }; #define SETCLIPRECTEPR_MINSIZE (sizeof(GpRectF)) class SetClipRectEPR : public EmfPlusRecordPlay { protected: GpRectF ClipRect; // !!! Handle 16-bit rect public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetClipRect); if (dataSize < SETCLIPRECTEPR_MINSIZE) { WARNING(("SetClipRectEPR::Play dataSize is too small")); return; } player->Graphics->SetClip(ClipRect, GetCombineMode(flags)); } }; class SetClipPathEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetClipPath); GpPath *path = (GpPath *)player->GetObject(GetMetaObjectId(flags), ObjectTypePath); if (path != NULL) { player->Graphics->SetClip(path, GetCombineMode(flags), GetIsDevicePath(flags)); } } }; class SetClipRegionEPR : public EmfPlusRecordPlay { public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeSetClipRegion); GpRegion *region = (GpRegion *)player->GetObject(GetMetaObjectId(flags), ObjectTypeRegion); if (region != NULL) { player->Graphics->SetClip(region, GetCombineMode(flags)); } } }; #define OFFSETCLIPEPR_MINSIZE (sizeof(REAL) + sizeof(REAL)) class OffsetClipEPR : public EmfPlusRecordPlay { protected: REAL Dx; REAL Dy; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeOffsetClip); if (dataSize < OFFSETCLIPEPR_MINSIZE) { WARNING(("OffsetClipEPR::Play dataSize is too small")); return; } player->Graphics->OffsetClip(Dx, Dy); } }; #define OBJECTEPR_MINSIZE (sizeof(UINT32)) class ObjectEPR : public EmfPlusRecordPlay { protected: BYTE ObjectData[1]; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { if (dataSize < OBJECTEPR_MINSIZE) { WARNING(("ObjectEPR::Play dataSize is too small")); return; } player->AddObject(flags, ObjectData, dataSize); } }; #define CLEAREPR_MINSIZE (sizeof(UINT32)) class ClearEPR : public EmfPlusBoundsRecord { protected: ARGB Color; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeClear); if (dataSize < CLEAREPR_MINSIZE) { WARNING(("ClearEPR::Play dataSize is too small")); return; } GpColor color; color.SetColor(Color); player->Graphics->Clear(color); } }; #define FILLRECTSEPR_MINSIZE (sizeof(UINT32) + sizeof(UINT32)) class FillRectsEPR : public EmfPlusBoundsRecord { protected: UINT32 BrushValue; UINT32 Count; BYTE RectData[1]; // GpRect16 or GpRectF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeFillRects); if (dataSize < FILLRECTSEPR_MINSIZE) { WARNING(("FillRectsEPR::Play dataSize is too small")); return; } GpBrush * brush = player->GetBrush(BrushValue, flags); GpRectF * rects = player->GetRects(RectData, dataSize - FILLRECTSEPR_MINSIZE, Count, flags); if (rects != NULL) { if (brush != NULL) { player->Graphics->FillRects(brush, rects, Count); } player->FreePointsBuffer(); } } }; #define DRAWRECTSEPR_MINSIZE (sizeof(UINT32)) class DrawRectsEPR : public EmfPlusBoundsRecord { protected: UINT32 Count; BYTE RectData[1]; // GpRect16 or GpRectF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawRects); if (dataSize < DRAWRECTSEPR_MINSIZE) { WARNING(("DrawRectsEPR::Play dataSize is too small")); return; } GpPen * pen = (GpPen *)player->GetObject(GetMetaObjectId(flags), ObjectTypePen); GpRectF * rects = player->GetRects(RectData, dataSize - DRAWRECTSEPR_MINSIZE, Count, flags); if (rects != NULL) { if (pen != NULL) { player->Graphics->DrawRects(pen, rects, Count); } player->FreePointsBuffer(); } } }; #define FILLPOLYGONEPR_MINSIZE (sizeof(UINT32) + sizeof(UINT32)) class FillPolygonEPR : public EmfPlusBoundsRecord { protected: UINT32 BrushValue; UINT32 Count; BYTE PointData[1]; // GpPoint16 or GpPointF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeFillPolygon); if (dataSize < FILLPOLYGONEPR_MINSIZE) { WARNING(("FillPolygonEPR::Play dataSize is too small")); return; } GpBrush * brush = player->GetBrush(BrushValue, flags); GpPointF * points = player->GetPoints(PointData, dataSize - FILLPOLYGONEPR_MINSIZE, Count, flags); if (points != NULL) { if (brush != NULL) { player->Graphics->FillPolygon(brush, points, Count, GetFillMode(flags)); } player->FreePointsBuffer(); } } }; #define DRAWLINESEPR_MINSIZE (sizeof(UINT32)) class DrawLinesEPR : public EmfPlusBoundsRecord { protected: UINT32 Count; BYTE PointData[1]; // GpPoint16 or GpPointF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawLines); if (dataSize < DRAWLINESEPR_MINSIZE) { WARNING(("DrawLinesEPR::Play dataSize is too small")); return; } GpPen * pen = (GpPen *)player->GetObject(GetMetaObjectId(flags), ObjectTypePen); GpPointF * points = player->GetPoints(PointData, dataSize - DRAWLINESEPR_MINSIZE, Count, flags); if (points != NULL) { if (pen != NULL) { player->Graphics->DrawLines(pen, points, Count, IsClosed(flags)); } player->FreePointsBuffer(); } } }; #define FILLELLIPSEEPR_MINSIZE (sizeof(UINT32)) class FillEllipseEPR : public EmfPlusBoundsRecord { protected: UINT32 BrushValue; BYTE RectData[1]; // GpRect16 or GpRectF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeFillEllipse); if (dataSize < FILLELLIPSEEPR_MINSIZE) { WARNING(("FillEllipseEPR::Play dataSize is too small")); return; } GpBrush * brush = player->GetBrush(BrushValue, flags); GpRectF * rect = player->GetRects(RectData, dataSize - FILLELLIPSEEPR_MINSIZE, 1, flags); if (rect != NULL) { if (brush != NULL) { player->Graphics->FillEllipse(brush, *rect); } player->FreePointsBuffer(); } } }; class DrawEllipseEPR : public EmfPlusBoundsRecord { protected: BYTE RectData[1]; // GpRect16 or GpRectF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawEllipse); GpPen * pen = (GpPen *)player->GetObject(GetMetaObjectId(flags), ObjectTypePen); GpRectF * rect = player->GetRects(RectData, dataSize, 1, flags); if (rect != NULL) { if (pen != NULL) { player->Graphics->DrawEllipse(pen, *rect); } player->FreePointsBuffer(); } } }; #define FILLPIEEPR_MINSIZE (sizeof(UINT32) + sizeof(REAL) + sizeof(REAL)) class FillPieEPR : public EmfPlusBoundsRecord { protected: UINT32 BrushValue; REAL StartAngle; REAL SweepAngle; BYTE RectData[1]; // GpRect16 or GpRectF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeFillPie); if (dataSize < FILLPIEEPR_MINSIZE) { WARNING(("FillPieEPR::Play dataSize is too small")); return; } GpBrush * brush = player->GetBrush(BrushValue, flags); GpRectF * rect = player->GetRects(RectData, dataSize - FILLPIEEPR_MINSIZE, 1, flags); if (rect != NULL) { if (brush != NULL) { player->Graphics->FillPie(brush, *rect, StartAngle, SweepAngle); } player->FreePointsBuffer(); } } }; #define DRAWPIEEPR_MINSIZE (sizeof(REAL) + sizeof(REAL)) class DrawPieEPR : public EmfPlusBoundsRecord { protected: REAL StartAngle; REAL SweepAngle; BYTE RectData[1]; // GpRect16 or GpRectF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawPie); if (dataSize < DRAWPIEEPR_MINSIZE) { WARNING(("DrawPieEPR::Play dataSize is too small")); return; } GpPen * pen = (GpPen *)player->GetObject(GetMetaObjectId(flags), ObjectTypePen); GpRectF * rect = player->GetRects(RectData, dataSize - DRAWPIEEPR_MINSIZE, 1, flags); if (rect != NULL) { if (pen != NULL) { player->Graphics->DrawPie(pen, *rect, StartAngle, SweepAngle); } player->FreePointsBuffer(); } } }; #define DRAWARCEPR_MINSIZE (sizeof(REAL) + sizeof(REAL)) class DrawArcEPR : public EmfPlusBoundsRecord { protected: REAL StartAngle; REAL SweepAngle; BYTE RectData[1]; // GpRect16 or GpRectF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawArc); if (dataSize < DRAWARCEPR_MINSIZE) { WARNING(("DrawArcEPR::Play dataSize is too small")); return; } GpPen * pen = (GpPen *)player->GetObject(GetMetaObjectId(flags), ObjectTypePen); GpRectF * rect = player->GetRects(RectData, dataSize - DRAWARCEPR_MINSIZE, 1, flags); if (rect != NULL) { if (pen != NULL) { player->Graphics->DrawArc(pen, *rect, StartAngle, SweepAngle); } player->FreePointsBuffer(); } } }; #define FILLREGIONEPR_MINSIZE (sizeof(UINT32)) class FillRegionEPR : public EmfPlusBoundsRecord { protected: UINT32 BrushValue; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeFillRegion); if (dataSize < FILLREGIONEPR_MINSIZE) { WARNING(("FillRegionEPR::Play dataSize is too small")); return; } GpBrush * brush = player->GetBrush(BrushValue, flags); GpRegion * region = (GpRegion *)player->GetObject(GetMetaObjectId(flags), ObjectTypeRegion); if ((brush != NULL) && (region != NULL)) { player->Graphics->FillRegion(brush, region); } } }; #define FILLPATHEPR_MINSIZE (sizeof(UINT32)) class FillPathEPR : public EmfPlusBoundsRecord { protected: UINT32 BrushValue; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeFillPath); if (dataSize < FILLPATHEPR_MINSIZE) { WARNING(("FillPathEPR::Play dataSize is too small")); return; } GpBrush * brush = player->GetBrush(BrushValue, flags); GpPath * path = (GpPath *)player->GetObject(GetMetaObjectId(flags), ObjectTypePath); if ((brush != NULL) && (path != NULL)) { player->Graphics->FillPath(brush, path); } } }; #define DRAWPATHEPR_MINSIZE (sizeof(UINT32)) class DrawPathEPR : public EmfPlusBoundsRecord { protected: UINT32 PenId; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawPath); if (dataSize < DRAWPATHEPR_MINSIZE) { WARNING(("DrawPathEPR::Play dataSize is too small")); return; } GpPen * pen = (GpPen *)player->GetObject(PenId, ObjectTypePen); GpPath * path = (GpPath *)player->GetObject(GetMetaObjectId(flags), ObjectTypePath); if ((pen != NULL) && (path != NULL)) { player->Graphics->DrawPath(pen, path); } } }; #define FILLCLOSEDCURVEEPR_MINSIZE (sizeof(UINT32) + sizeof(REAL) + sizeof(UINT32)) class FillClosedCurveEPR : public EmfPlusBoundsRecord { protected: UINT32 BrushValue; REAL Tension; UINT32 Count; BYTE PointData[1]; // GpPoint16 or GpPointF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeFillClosedCurve); if (dataSize < FILLCLOSEDCURVEEPR_MINSIZE) { WARNING(("FillClosedCurveEPR::Play dataSize is too small")); return; } GpBrush * brush = player->GetBrush(BrushValue, flags); GpPointF * points = player->GetPoints(PointData, dataSize - FILLCLOSEDCURVEEPR_MINSIZE, Count, flags); if (points != NULL) { if (brush != NULL) { player->Graphics->FillClosedCurve(brush, points, Count,Tension,GetFillMode(flags)); } player->FreePointsBuffer(); } } }; #define DRAWCLOSEDCURVEEPR_MINSIZE (sizeof(REAL) + sizeof(UINT32)) class DrawClosedCurveEPR : public EmfPlusBoundsRecord { protected: REAL Tension; UINT32 Count; BYTE PointData[1]; // GpPoint16 or GpPointF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawClosedCurve); if (dataSize < DRAWCLOSEDCURVEEPR_MINSIZE) { WARNING(("DrawClosedCurveEPR::Play dataSize is too small")); return; } GpPen * pen = (GpPen *)player->GetObject(GetMetaObjectId(flags), ObjectTypePen); GpPointF * points = player->GetPoints(PointData, dataSize - DRAWCLOSEDCURVEEPR_MINSIZE, Count, flags); if (points != NULL) { if (pen != NULL) { player->Graphics->DrawClosedCurve(pen, points, Count, Tension); } player->FreePointsBuffer(); } } }; #define DRAWCURVEEPR_MINSIZE (sizeof(REAL) + sizeof(INT32) + sizeof(UINT32) + sizeof(UINT32)) class DrawCurveEPR : public EmfPlusBoundsRecord { protected: REAL Tension; INT32 Offset; UINT32 NumSegments; UINT32 Count; BYTE PointData[1]; // GpPoint16 or GpPointF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawCurve); if (dataSize < DRAWCURVEEPR_MINSIZE) { WARNING(("DrawCurveEPR::Play dataSize is too small")); return; } GpPen * pen = (GpPen *)player->GetObject(GetMetaObjectId(flags), ObjectTypePen); GpPointF * points = player->GetPoints(PointData, dataSize - DRAWCURVEEPR_MINSIZE, Count, flags); if (points != NULL) { if (pen != NULL) { player->Graphics->DrawCurve(pen, points, Count, Tension, Offset, NumSegments); } player->FreePointsBuffer(); } } }; #define DRAWBEZIERSEPR_MINSIZE (sizeof(UINT32)) class DrawBeziersEPR : public EmfPlusBoundsRecord { protected: UINT32 Count; BYTE PointData[1]; // GpPoint16 or GpPointF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawBeziers); if (dataSize < DRAWBEZIERSEPR_MINSIZE) { WARNING(("DrawBeziersEPR::Play dataSize is too small")); return; } GpPen * pen = (GpPen *)player->GetObject(GetMetaObjectId(flags), ObjectTypePen); GpPointF * points = player->GetPoints(PointData, dataSize - DRAWBEZIERSEPR_MINSIZE, Count, flags); if (points != NULL) { if (pen != NULL) { player->Graphics->DrawBeziers(pen, points, Count); } player->FreePointsBuffer(); } } }; #define DRAWIMAGEEPR_MINSIZE (sizeof(INT32) + sizeof(GpRectF)) class DrawImageEPR : public EmfPlusBoundsRecord { protected: UINT32 ImageAttributesId; INT32 SrcUnit; GpRectF SrcRect; BYTE RectData[1]; // GpRect16 or GpRectF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawImage); if (dataSize < DRAWIMAGEEPR_MINSIZE) { WARNING(("DrawImageEPR::Play dataSize is too small")); return; } GpImage *image = (GpImage *)player->GetObject(GetMetaObjectId(flags), ObjectTypeImage); GpRectF *destRect = player->GetRects(RectData, dataSize - DRAWIMAGEEPR_MINSIZE, 1, flags); GpImageAttributes *imageAttributes = (GpImageAttributes *)player->GetObject( ImageAttributesId, ObjectTypeImageAttributes ); if ( (image != NULL) && (NULL != destRect) ) { GpStatus status = player->Graphics->DrawImage( image, *destRect, SrcRect, static_cast(SrcUnit), imageAttributes, player->DrawImageCallback, player->DrawImageCallbackData ); if (status == Aborted) { // stop enumerating records player->EnumerateAborted = TRUE; } } } }; #define DRAWIMAGEPOINTSEPR_MINSIZE (sizeof(INT32) + sizeof(GpRectF) + sizeof(UINT32)) class DrawImagePointsEPR : public EmfPlusBoundsRecord { protected: UINT32 ImageAttributesId; INT32 SrcUnit; GpRectF SrcRect; UINT32 Count; BYTE PointData[1]; // GpPoint16 or GpPointF public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawImagePoints); if (dataSize < DRAWIMAGEPOINTSEPR_MINSIZE) { WARNING(("DrawImagePointsEPR::Play dataSize is too small")); return; } GpImage *image = (GpImage *)player->GetObject(GetMetaObjectId(flags), ObjectTypeImage); GpPointF *destPoints = player->GetPoints(PointData, dataSize - DRAWIMAGEPOINTSEPR_MINSIZE, Count, flags); GpImageAttributes *imageAttributes = (GpImageAttributes *)player->GetObject( ImageAttributesId, ObjectTypeImageAttributes ); if (destPoints != NULL) { if (image != NULL) { GpStatus status = player->Graphics->DrawImage( image, destPoints, Count, SrcRect, static_cast(SrcUnit), imageAttributes, player->DrawImageCallback, player->DrawImageCallbackData ); if (status == Aborted) { // stop enumerating records player->EnumerateAborted = TRUE; } } player->FreePointsBuffer(); } } }; #define DRAWSTRINGEPR_MINSIZE (sizeof(UINT32) + sizeof(UINT32) + sizeof(UINT32) + sizeof(GpRectF)) class DrawStringEPR : public EmfPlusBoundsRecord { protected: UINT32 BrushValue; UINT32 FormatId; UINT32 Length; GpRectF LayoutRect; BYTE StringData[1]; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawString); if (dataSize < DRAWSTRINGEPR_MINSIZE) { WARNING(("DrawStringEPR::Play dataSize is too small")); return; } GlobalTextLock lock; GpBrush * brush = player->GetBrush(BrushValue, flags); GpFont * font = (GpFont *)player->GetObject(GetMetaObjectId(flags), ObjectTypeFont); // Optional parameter - can return NULL. GpStringFormat *format = (GpStringFormat *)player->GetObject( FormatId, ObjectTypeStringFormat ); if (Length > 0) { if (dataSize >= (DRAWSTRINGEPR_MINSIZE + (Length * sizeof(WCHAR)))) { if ((brush != NULL) && (font != NULL)) { // !!! TODO: // Determine whether the string is compressed or not. // If so, decompress it. player->Graphics->DrawString( (WCHAR *)StringData, Length, font, &LayoutRect, format, brush ); } } else { WARNING(("DrawStringEPR::Play dataSize is too small")); return; } player->FreePointsBuffer(); } } }; #define DRAWDRIVERSTRINGEPR_MINSIZE (sizeof(UINT32) + sizeof(INT) + sizeof(UINT32) + sizeof(UINT32)) class DrawDriverStringEPR : public EmfPlusBoundsRecord { protected: UINT32 BrushValue; INT ApiFlags; UINT32 MatrixPresent; UINT32 GlyphCount; BYTE Data[1]; public: VOID Play( MetafilePlayer * player, EmfPlusRecordType recordType, UINT flags, UINT dataSize ) const { ASSERT(recordType == EmfPlusRecordTypeDrawDriverString); if (dataSize < DRAWDRIVERSTRINGEPR_MINSIZE) { WARNING(("DrawDriverStringEPR::Play dataSize is too small")); return; } GlobalTextLock lock; GpBrush * brush = player->GetBrush(BrushValue, flags); GpFont * font = (GpFont *)player->GetObject(GetMetaObjectId(flags), ObjectTypeFont); if (GlyphCount > 0) { UINT requiredSize = DRAWDRIVERSTRINGEPR_MINSIZE + (GlyphCount * sizeof(WCHAR)) + (GlyphCount * sizeof(PointF)); if (dataSize >= requiredSize) { if ((brush != NULL) && (font != NULL)) { WCHAR *text = (WCHAR *) Data; PointF *positions = (PointF *) (Data + (GlyphCount * sizeof(WCHAR))); if (MatrixPresent > 0) { if (dataSize < requiredSize + GDIP_MATRIX_SIZE) { WARNING(("DrawDriverStringEPR::Play dataSize is too small")); return; } REAL *matrixData = (REAL *)((BYTE *) ((BYTE *)positions) + (GlyphCount * sizeof(PointF))); GpMatrix matrix(matrixData); player->Graphics->DrawDriverString( (unsigned short *)text, GlyphCount, font, brush, positions, ApiFlags | DriverStringOptionsMetaPlay, &matrix); } else { player->Graphics->DrawDriverString( (unsigned short *)text, GlyphCount, font, brush, positions, ApiFlags, NULL); } } } else { WARNING(("DrawDriverStringEPR::Play dataSize is too small")); return; } player->FreePointsBuffer(); } } }; // The order of these methods must exactly match // the order of the enums of the record numbers. PLAYRECORDFUNC RecordPlayFuncs[EmfPlusRecordTypeMax - EmfPlusRecordTypeMin + 1] = { (PLAYRECORDFUNC)&EmfPlusHeaderRecord::Play, // Header (PLAYRECORDFUNC)&EmfPlusRecordPlay::Play, // EndOfFile (PLAYRECORDFUNC)&CommentEPR::Play, (PLAYRECORDFUNC)&GetDCEPR::Play, (PLAYRECORDFUNC)&MultiFormatStartEPR::Play, (PLAYRECORDFUNC)&MultiFormatSectionEPR::Play, (PLAYRECORDFUNC)&MultiFormatEndEPR::Play, // For all persistent objects (PLAYRECORDFUNC)&ObjectEPR::Play, // Drawing Records (PLAYRECORDFUNC)&ClearEPR::Play, (PLAYRECORDFUNC)&FillRectsEPR::Play, (PLAYRECORDFUNC)&DrawRectsEPR::Play, (PLAYRECORDFUNC)&FillPolygonEPR::Play, (PLAYRECORDFUNC)&DrawLinesEPR::Play, (PLAYRECORDFUNC)&FillEllipseEPR::Play, (PLAYRECORDFUNC)&DrawEllipseEPR::Play, (PLAYRECORDFUNC)&FillPieEPR::Play, (PLAYRECORDFUNC)&DrawPieEPR::Play, (PLAYRECORDFUNC)&DrawArcEPR::Play, (PLAYRECORDFUNC)&FillRegionEPR::Play, (PLAYRECORDFUNC)&FillPathEPR::Play, (PLAYRECORDFUNC)&DrawPathEPR::Play, (PLAYRECORDFUNC)&FillClosedCurveEPR::Play, (PLAYRECORDFUNC)&DrawClosedCurveEPR::Play, (PLAYRECORDFUNC)&DrawCurveEPR::Play, (PLAYRECORDFUNC)&DrawBeziersEPR::Play, (PLAYRECORDFUNC)&DrawImageEPR::Play, (PLAYRECORDFUNC)&DrawImagePointsEPR::Play, (PLAYRECORDFUNC)&DrawStringEPR::Play, // Graphics State Records (PLAYRECORDFUNC)&SetRenderingOriginEPR::Play, (PLAYRECORDFUNC)&SetAntiAliasModeEPR::Play, (PLAYRECORDFUNC)&SetTextRenderingHintEPR::Play, (PLAYRECORDFUNC)&SetTextContrastEPR::Play, (PLAYRECORDFUNC)&SetInterpolationModeEPR::Play, (PLAYRECORDFUNC)&SetPixelOffsetModeEPR::Play, (PLAYRECORDFUNC)&SetCompositingModeEPR::Play, (PLAYRECORDFUNC)&SetCompositingQualityEPR::Play, (PLAYRECORDFUNC)&SaveEPR::Play, (PLAYRECORDFUNC)&RestoreEPR::Play, (PLAYRECORDFUNC)&BeginContainerEPR::Play, (PLAYRECORDFUNC)&BeginContainerNoParamsEPR::Play, (PLAYRECORDFUNC)&EndContainerEPR::Play, (PLAYRECORDFUNC)&SetWorldTransformEPR::Play, (PLAYRECORDFUNC)&ResetWorldTransformEPR::Play, (PLAYRECORDFUNC)&MultiplyWorldTransformEPR::Play, (PLAYRECORDFUNC)&TranslateWorldTransformEPR::Play, (PLAYRECORDFUNC)&ScaleWorldTransformEPR::Play, (PLAYRECORDFUNC)&RotateWorldTransformEPR::Play, (PLAYRECORDFUNC)&SetPageTransformEPR::Play, (PLAYRECORDFUNC)&ResetClipEPR::Play, (PLAYRECORDFUNC)&SetClipRectEPR::Play, (PLAYRECORDFUNC)&SetClipPathEPR::Play, (PLAYRECORDFUNC)&SetClipRegionEPR::Play, (PLAYRECORDFUNC)&OffsetClipEPR::Play, (PLAYRECORDFUNC)&DrawDriverStringEPR::Play, // New record types must be added here (at the end) -- do not add above, // since that will invalidate previous metafiles! }; HENHMETAFILE GetEmf( const WCHAR * fileName, MetafileType type ) { HENHMETAFILE hEmf = NULL; if (type == MetafileTypeWmfPlaceable) { IStream * wmfStream; IStream * memStream; wmfStream = CreateStreamOnFile(fileName, GENERIC_READ); if (wmfStream != NULL) { STATSTG statstg; HRESULT hResult; hResult = wmfStream->Stat(&statstg, STATFLAG_NONAME); if (!HResultSuccess(hResult)) { wmfStream->Release(); return hEmf; } INT size = (INT)(statstg.cbSize.QuadPart - sizeof(WmfPlaceableFileHeader)); if (SeekFromStart(wmfStream, sizeof(WmfPlaceableFileHeader))) { HGLOBAL hGlobal; hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_NODISCARD, size); if (hGlobal != NULL) { hResult = CreateStreamOnHGlobal(hGlobal, TRUE, &memStream); if (HResultSuccess(hResult) && (memStream != NULL)) { if (CopyStream(wmfStream, memStream, size)) { BYTE * wmfData = (BYTE *)GlobalLock(hGlobal); if (wmfData != NULL) { hEmf = (HENHMETAFILE) SetMetaFileBitsEx(size, wmfData); GlobalUnlock(hGlobal); } } memStream->Release(); } else { GlobalFree(hGlobal); } } } wmfStream->Release(); } } else { if (Globals::IsNt) { hEmf = GetEnhMetaFileW(fileName); } else // Windows 9x - non-Unicode { AnsiStrFromUnicode nameStr(fileName); if (nameStr.IsValid()) { hEmf = GetEnhMetaFileA(nameStr); } } } return hEmf; } #if 0 // don't need this right now WCHAR * GetTemporaryFilename() { if (Globals::IsNt) { WCHAR pathBuffer[MAX_PATH + 1]; WCHAR fileBuffer[MAX_PATH + 12 + 1]; // 12 for filename itself UINT len = GetTempPathW(MAX_PATH, pathBuffer); if ((len == 0) || (len > MAX_PATH)) { pathBuffer[0] = L'.'; pathBuffer[1] = L'\0'; } if (GetTempFileNameW(pathBuffer, L"Emp", 0, fileBuffer) == 0) { return NULL; } return UnicodeStringDuplicate(fileBuffer); } else // Windows 9x - non-Unicode { CHAR pathBuffer[MAX_PATH + 1]; CHAR fileBuffer[MAX_PATH + 12 + 1]; // 12 for filename itself UINT len = GetTempPathA(MAX_PATH, pathBuffer); if ((len == 0) || (len > MAX_PATH)) { pathBuffer[0] = '.'; pathBuffer[1] = '\0'; } if (GetTempFileNameA(pathBuffer, "Emp", 0, fileBuffer) == 0) { return NULL; } len = (strlen(fileBuffer) + 1) * sizeof(WCHAR); WCHAR * filename = (WCHAR *)GpMalloc(len); if (filename != NULL) { if (AnsiToUnicodeStr(fileBuffer, filename, len)) { return filename; } GpFree(filename); } return NULL; } } #endif // For now, don't handle a source rect GpBitmap * GpMetafile::GetBitmap( INT width, INT height, const GpImageAttributes * imageAttributes ) { GpRectF srcRect; GpPageUnit srcUnit; this->GetBounds(&srcRect, &srcUnit); ASSERT(srcUnit == UnitPixel); // Determine what size to make the bitmap. if ((width <= 0) || (height <= 0)) { if (this->IsEmfOrEmfPlus()) { width = GpRound(srcRect.Width); height = GpRound(srcRect.Height); } else // must be a WMF { // Convert size to use the dpi of this display. // This is somewhat of a hack, but what else could I do, // since I don't know where this brush will be used? REAL srcDpiX; REAL srcDpiY; REAL destDpiX = Globals::DesktopDpiX; // guess REAL destDpiY = Globals::DesktopDpiY; this->GetResolution(&srcDpiX, &srcDpiY); if ((srcDpiX <= 0) || (srcDpiY <= 0)) { WARNING(("bad dpi for WMF")); return NULL; } width = GpRound((srcRect.Width / srcDpiX) * destDpiX); height = GpRound((srcRect.Height / srcDpiY) * destDpiY); } if ((width <= 0) || (height <= 0)) { WARNING(("bad size for metafile")); return NULL; } } GpBitmap * bitmapImage = new GpBitmap(width, height, PIXFMT_32BPP_ARGB); if (bitmapImage != NULL) { if (bitmapImage->IsValid()) { GpGraphics * graphics = bitmapImage->GetGraphicsContext(); if (graphics != NULL) { if (graphics->IsValid()) { // we have to lock the graphics so the driver doesn't assert GpLock * lockGraphics = new GpLock(graphics->GetObjectLock()); if (lockGraphics != NULL) { ASSERT(lockGraphics->IsValid()); // now draw the metafile into the bitmap image GpRectF destRect(0.0f, 0.0f, (REAL)width, (REAL)height); // We don't want to interpolate the bitmaps in WMFs // and EMFs when converting them to a texture. graphics->SetInterpolationMode(InterpolationModeNearestNeighbor); GpStatus status; status = graphics->DrawImage( this, destRect, srcRect, srcUnit, imageAttributes); // have to delete the lock before deleting the graphics delete lockGraphics; if (status == Ok) { delete graphics; return bitmapImage; } WARNING(("DrawImage failed")); } else { WARNING(("Could not create graphics lock")); } } else { WARNING(("graphics from bitmap image not valid")); } delete graphics; } else { WARNING(("could not create graphics from bitmap image")); } } else { WARNING(("bitmap image is not valid")); } bitmapImage->Dispose(); } else { WARNING(("could not create bitmap image")); } return NULL; }