| /* |
| * Copyright (C) 2011 Vincent Povirk for CodeWeavers |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #include <stdarg.h> |
| #include <math.h> |
| |
| #include "windef.h" |
| #include "winbase.h" |
| #include "wingdi.h" |
| #include "wine/unicode.h" |
| |
| #define COBJMACROS |
| #include "objbase.h" |
| #include "ocidl.h" |
| #include "olectl.h" |
| #include "ole2.h" |
| |
| #include "winreg.h" |
| #include "shlwapi.h" |
| |
| #include "gdiplus.h" |
| #include "gdiplus_private.h" |
| #include "wine/debug.h" |
| #include "wine/list.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(gdiplus); |
| |
| typedef struct EmfPlusRecordHeader |
| { |
| WORD Type; |
| WORD Flags; |
| DWORD Size; |
| DWORD DataSize; |
| } EmfPlusRecordHeader; |
| |
| typedef struct EmfPlusHeader |
| { |
| EmfPlusRecordHeader Header; |
| DWORD Version; |
| DWORD EmfPlusFlags; |
| DWORD LogicalDpiX; |
| DWORD LogicalDpiY; |
| } EmfPlusHeader; |
| |
| static GpStatus METAFILE_AllocateRecord(GpMetafile *metafile, DWORD size, void **result) |
| { |
| DWORD size_needed; |
| EmfPlusRecordHeader *record; |
| |
| if (!metafile->comment_data_size) |
| { |
| DWORD data_size = max(256, size * 2 + 4); |
| metafile->comment_data = GdipAlloc(data_size); |
| |
| if (!metafile->comment_data) |
| return OutOfMemory; |
| |
| memcpy(metafile->comment_data, "EMF+", 4); |
| |
| metafile->comment_data_size = data_size; |
| metafile->comment_data_length = 4; |
| } |
| |
| size_needed = size + metafile->comment_data_length; |
| |
| if (size_needed > metafile->comment_data_size) |
| { |
| DWORD data_size = size_needed * 2; |
| BYTE *new_data = GdipAlloc(data_size); |
| |
| if (!new_data) |
| return OutOfMemory; |
| |
| memcpy(new_data, metafile->comment_data, metafile->comment_data_length); |
| |
| metafile->comment_data_size = data_size; |
| GdipFree(metafile->comment_data); |
| metafile->comment_data = new_data; |
| } |
| |
| *result = metafile->comment_data + metafile->comment_data_length; |
| metafile->comment_data_length += size; |
| |
| record = (EmfPlusRecordHeader*)*result; |
| record->Size = size; |
| record->DataSize = size - sizeof(EmfPlusRecordHeader); |
| |
| return Ok; |
| } |
| |
| static void METAFILE_WriteRecords(GpMetafile *metafile) |
| { |
| if (metafile->comment_data_length > 4) |
| { |
| GdiComment(metafile->record_dc, metafile->comment_data_length, metafile->comment_data); |
| metafile->comment_data_length = 4; |
| } |
| } |
| |
| static GpStatus METAFILE_WriteHeader(GpMetafile *metafile, HDC hdc) |
| { |
| GpStatus stat; |
| |
| if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual) |
| { |
| EmfPlusHeader *header; |
| |
| stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusHeader), (void**)&header); |
| if (stat != Ok) |
| return stat; |
| |
| header->Header.Type = EmfPlusRecordTypeHeader; |
| |
| if (metafile->metafile_type == MetafileTypeEmfPlusDual) |
| header->Header.Flags = 1; |
| else |
| header->Header.Flags = 0; |
| |
| header->Version = 0xDBC01002; |
| |
| if (GetDeviceCaps(hdc, TECHNOLOGY) == DT_RASDISPLAY) |
| header->EmfPlusFlags = 1; |
| else |
| header->EmfPlusFlags = 0; |
| |
| header->LogicalDpiX = GetDeviceCaps(hdc, LOGPIXELSX); |
| header->LogicalDpiY = GetDeviceCaps(hdc, LOGPIXELSY); |
| |
| METAFILE_WriteRecords(metafile); |
| } |
| |
| return Ok; |
| } |
| |
| static GpStatus METAFILE_WriteEndOfFile(GpMetafile *metafile) |
| { |
| GpStatus stat; |
| |
| if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual) |
| { |
| EmfPlusRecordHeader *record; |
| |
| stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusRecordHeader), (void**)&record); |
| if (stat != Ok) |
| return stat; |
| |
| record->Type = EmfPlusRecordTypeEndOfFile; |
| record->Flags = 0; |
| |
| METAFILE_WriteRecords(metafile); |
| } |
| |
| return Ok; |
| } |
| |
| GpStatus WINGDIPAPI GdipRecordMetafile(HDC hdc, EmfType type, GDIPCONST GpRectF *frameRect, |
| MetafileFrameUnit frameUnit, GDIPCONST WCHAR *desc, GpMetafile **metafile) |
| { |
| HDC record_dc; |
| REAL framerect_factor_x, framerect_factor_y; |
| RECT rc; |
| GpStatus stat; |
| |
| TRACE("(%p %d %p %d %p %p)\n", hdc, type, frameRect, frameUnit, desc, metafile); |
| |
| if (!hdc || type < EmfTypeEmfOnly || type > EmfTypeEmfPlusDual || !metafile) |
| return InvalidParameter; |
| |
| if (!frameRect) |
| { |
| FIXME("not implemented for NULL rect\n"); |
| return NotImplemented; |
| } |
| |
| switch (frameUnit) |
| { |
| case MetafileFrameUnitPixel: |
| framerect_factor_x = 2540.0 / GetDeviceCaps(hdc, LOGPIXELSX); |
| framerect_factor_y = 2540.0 / GetDeviceCaps(hdc, LOGPIXELSY); |
| break; |
| case MetafileFrameUnitPoint: |
| framerect_factor_x = framerect_factor_y = 2540.0 / 72.0; |
| break; |
| case MetafileFrameUnitInch: |
| framerect_factor_x = framerect_factor_y = 2540.0; |
| break; |
| case MetafileFrameUnitDocument: |
| framerect_factor_x = framerect_factor_y = 2540.0 / 300.0; |
| break; |
| case MetafileFrameUnitMillimeter: |
| framerect_factor_x = framerect_factor_y = 100.0; |
| break; |
| case MetafileFrameUnitGdi: |
| framerect_factor_x = framerect_factor_y = 1.0; |
| break; |
| default: |
| return InvalidParameter; |
| } |
| |
| rc.left = framerect_factor_x * frameRect->X; |
| rc.top = framerect_factor_y * frameRect->Y; |
| rc.right = rc.left + framerect_factor_x * frameRect->Width; |
| rc.bottom = rc.top + framerect_factor_y * frameRect->Height; |
| |
| record_dc = CreateEnhMetaFileW(hdc, NULL, &rc, desc); |
| |
| if (!record_dc) |
| return GenericError; |
| |
| *metafile = GdipAlloc(sizeof(GpMetafile)); |
| if(!*metafile) |
| { |
| DeleteEnhMetaFile(CloseEnhMetaFile(record_dc)); |
| return OutOfMemory; |
| } |
| |
| (*metafile)->image.type = ImageTypeMetafile; |
| (*metafile)->image.picture = NULL; |
| (*metafile)->image.flags = ImageFlagsNone; |
| (*metafile)->image.palette = NULL; |
| (*metafile)->bounds = *frameRect; |
| (*metafile)->unit = frameUnit; |
| (*metafile)->metafile_type = type; |
| (*metafile)->record_dc = record_dc; |
| (*metafile)->comment_data = NULL; |
| (*metafile)->comment_data_size = 0; |
| (*metafile)->comment_data_length = 0; |
| (*metafile)->hemf = NULL; |
| |
| stat = METAFILE_WriteHeader(*metafile, hdc); |
| |
| if (stat != Ok) |
| { |
| DeleteEnhMetaFile(CloseEnhMetaFile(record_dc)); |
| GdipFree(*metafile); |
| *metafile = NULL; |
| return OutOfMemory; |
| } |
| |
| return stat; |
| } |
| |
| /***************************************************************************** |
| * GdipRecordMetafileI [GDIPLUS.@] |
| */ |
| GpStatus WINGDIPAPI GdipRecordMetafileI(HDC hdc, EmfType type, GDIPCONST GpRect *frameRect, |
| MetafileFrameUnit frameUnit, GDIPCONST WCHAR *desc, GpMetafile **metafile) |
| { |
| GpRectF frameRectF, *pFrameRectF; |
| |
| TRACE("(%p %d %p %d %p %p)\n", hdc, type, frameRect, frameUnit, desc, metafile); |
| |
| if (frameRect) |
| { |
| frameRectF.X = frameRect->X; |
| frameRectF.Y = frameRect->Y; |
| frameRectF.Width = frameRect->Width; |
| frameRectF.Height = frameRect->Height; |
| pFrameRectF = &frameRectF; |
| } |
| else |
| pFrameRectF = NULL; |
| |
| return GdipRecordMetafile(hdc, type, pFrameRectF, frameUnit, desc, metafile); |
| } |
| |
| GpStatus METAFILE_GetGraphicsContext(GpMetafile* metafile, GpGraphics **result) |
| { |
| GpStatus stat; |
| |
| if (!metafile->record_dc || metafile->record_graphics) |
| return InvalidParameter; |
| |
| stat = graphics_from_image((GpImage*)metafile, &metafile->record_graphics); |
| |
| if (stat == Ok) |
| *result = metafile->record_graphics; |
| |
| return stat; |
| } |
| |
| GpStatus METAFILE_GetDC(GpMetafile* metafile, HDC *hdc) |
| { |
| if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual) |
| { |
| EmfPlusRecordHeader *record; |
| GpStatus stat; |
| |
| stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusRecordHeader), (void**)&record); |
| if (stat != Ok) |
| return stat; |
| |
| record->Type = EmfPlusRecordTypeGetDC; |
| record->Flags = 0; |
| |
| METAFILE_WriteRecords(metafile); |
| } |
| |
| *hdc = metafile->record_dc; |
| |
| return Ok; |
| } |
| |
| GpStatus METAFILE_ReleaseDC(GpMetafile* metafile, HDC hdc) |
| { |
| if (hdc != metafile->record_dc) |
| return InvalidParameter; |
| |
| return Ok; |
| } |
| |
| GpStatus METAFILE_GraphicsDeleted(GpMetafile* metafile) |
| { |
| GpStatus stat; |
| |
| stat = METAFILE_WriteEndOfFile(metafile); |
| metafile->record_graphics = NULL; |
| |
| metafile->hemf = CloseEnhMetaFile(metafile->record_dc); |
| metafile->record_dc = NULL; |
| |
| GdipFree(metafile->comment_data); |
| metafile->comment_data = NULL; |
| metafile->comment_data_size = 0; |
| |
| return stat; |
| } |
| |
| GpStatus WINGDIPAPI GdipGetHemfFromMetafile(GpMetafile *metafile, HENHMETAFILE *hEmf) |
| { |
| TRACE("(%p,%p)\n", metafile, hEmf); |
| |
| if (!metafile || !hEmf || !metafile->hemf) |
| return InvalidParameter; |
| |
| *hEmf = metafile->hemf; |
| metafile->hemf = NULL; |
| |
| return Ok; |
| } |
| |
| static GpStatus METAFILE_PlaybackGetDC(GpMetafile *metafile) |
| { |
| GpStatus stat = Ok; |
| |
| stat = GdipGetDC(metafile->playback_graphics, &metafile->playback_dc); |
| |
| if (stat == Ok) |
| { |
| /* The result of GdipGetDC always expects device co-ordinates, but the |
| * device co-ordinates of the source metafile do not correspond to |
| * device co-ordinates of the destination. Therefore, we set up the DC |
| * so that the metafile's bounds map to the destination points where we |
| * are drawing this metafile. */ |
| SetMapMode(metafile->playback_dc, MM_ANISOTROPIC); |
| |
| SetWindowOrgEx(metafile->playback_dc, metafile->bounds.X, metafile->bounds.Y, NULL); |
| SetWindowExtEx(metafile->playback_dc, metafile->bounds.Width, metafile->bounds.Height, NULL); |
| |
| SetViewportOrgEx(metafile->playback_dc, metafile->playback_points[0].X, metafile->playback_points[0].Y, NULL); |
| SetViewportExtEx(metafile->playback_dc, |
| metafile->playback_points[1].X - metafile->playback_points[0].X, |
| metafile->playback_points[2].Y - metafile->playback_points[0].Y, NULL); |
| } |
| |
| return stat; |
| } |
| |
| static void METAFILE_PlaybackReleaseDC(GpMetafile *metafile) |
| { |
| if (metafile->playback_dc) |
| { |
| GdipReleaseDC(metafile->playback_graphics, metafile->playback_dc); |
| metafile->playback_dc = NULL; |
| } |
| } |
| |
| GpStatus WINGDIPAPI GdipPlayMetafileRecord(GDIPCONST GpMetafile *metafile, |
| EmfPlusRecordType recordType, UINT flags, UINT dataSize, GDIPCONST BYTE *data) |
| { |
| TRACE("(%p,%x,%x,%d,%p)\n", metafile, recordType, flags, dataSize, data); |
| |
| if (!metafile || (dataSize && !data) || !metafile->playback_graphics) |
| return InvalidParameter; |
| |
| if (recordType >= 1 && recordType <= 0x7a) |
| { |
| /* regular EMF record */ |
| if (metafile->playback_dc) |
| { |
| ENHMETARECORD *record; |
| |
| record = GdipAlloc(dataSize + 8); |
| |
| if (record) |
| { |
| record->iType = recordType; |
| record->nSize = dataSize; |
| memcpy(record->dParm, data, dataSize); |
| |
| PlayEnhMetaFileRecord(metafile->playback_dc, metafile->handle_table, |
| record, metafile->handle_count); |
| |
| GdipFree(record); |
| } |
| else |
| return OutOfMemory; |
| } |
| } |
| else |
| { |
| METAFILE_PlaybackReleaseDC((GpMetafile*)metafile); |
| |
| switch(recordType) |
| { |
| case EmfPlusRecordTypeHeader: |
| case EmfPlusRecordTypeEndOfFile: |
| break; |
| case EmfPlusRecordTypeGetDC: |
| METAFILE_PlaybackGetDC((GpMetafile*)metafile); |
| break; |
| default: |
| FIXME("Not implemented for record type %x\n", recordType); |
| return NotImplemented; |
| } |
| } |
| |
| return Ok; |
| } |
| |
| struct enum_metafile_data |
| { |
| EnumerateMetafileProc callback; |
| void *callback_data; |
| GpMetafile *metafile; |
| }; |
| |
| static int CALLBACK enum_metafile_proc(HDC hDC, HANDLETABLE *lpHTable, const ENHMETARECORD *lpEMFR, |
| int nObj, LPARAM lpData) |
| { |
| BOOL ret; |
| struct enum_metafile_data *data = (struct enum_metafile_data*)lpData; |
| const BYTE* pStr; |
| |
| data->metafile->handle_table = lpHTable; |
| data->metafile->handle_count = nObj; |
| |
| /* First check for an EMF+ record. */ |
| if (lpEMFR->iType == EMR_GDICOMMENT) |
| { |
| const EMRGDICOMMENT *comment = (const EMRGDICOMMENT*)lpEMFR; |
| |
| if (comment->cbData >= 4 && memcmp(comment->Data, "EMF+", 4) == 0) |
| { |
| int offset = 4; |
| |
| while (offset + sizeof(EmfPlusRecordHeader) <= comment->cbData) |
| { |
| const EmfPlusRecordHeader *record = (const EmfPlusRecordHeader*)&comment->Data[offset]; |
| |
| if (record->DataSize) |
| pStr = (const BYTE*)(record+1); |
| else |
| pStr = NULL; |
| |
| ret = data->callback(record->Type, record->Flags, record->DataSize, |
| pStr, data->callback_data); |
| |
| if (!ret) |
| return 0; |
| |
| offset += record->Size; |
| } |
| |
| return 1; |
| } |
| } |
| |
| if (lpEMFR->nSize != 8) |
| pStr = (const BYTE*)lpEMFR->dParm; |
| else |
| pStr = NULL; |
| |
| return data->callback(lpEMFR->iType, 0, lpEMFR->nSize-8, |
| pStr, data->callback_data); |
| } |
| |
| GpStatus WINGDIPAPI GdipEnumerateMetafileSrcRectDestPoints(GpGraphics *graphics, |
| GDIPCONST GpMetafile *metafile, GDIPCONST GpPointF *destPoints, INT count, |
| GDIPCONST GpRectF *srcRect, Unit srcUnit, EnumerateMetafileProc callback, |
| VOID *callbackData, GDIPCONST GpImageAttributes *imageAttributes) |
| { |
| struct enum_metafile_data data; |
| GpStatus stat; |
| GpMetafile *real_metafile = (GpMetafile*)metafile; /* whoever made this const was joking */ |
| |
| TRACE("(%p,%p,%p,%i,%p,%i,%p,%p,%p)\n", graphics, metafile, |
| destPoints, count, srcRect, srcUnit, callback, callbackData, |
| imageAttributes); |
| |
| if (!graphics || !metafile || !destPoints || count != 3 || !srcRect) |
| return InvalidParameter; |
| |
| if (!metafile->hemf) |
| return InvalidParameter; |
| |
| if (metafile->playback_graphics) |
| return ObjectBusy; |
| |
| TRACE("%s %i -> %s %s %s\n", debugstr_rectf(srcRect), srcUnit, |
| debugstr_pointf(&destPoints[0]), debugstr_pointf(&destPoints[1]), |
| debugstr_pointf(&destPoints[2])); |
| |
| data.callback = callback; |
| data.callback_data = callbackData; |
| data.metafile = real_metafile; |
| |
| real_metafile->playback_graphics = graphics; |
| real_metafile->playback_dc = NULL; |
| |
| memcpy(real_metafile->playback_points, destPoints, sizeof(PointF) * 3); |
| stat = GdipTransformPoints(graphics, CoordinateSpaceDevice, CoordinateSpaceWorld, real_metafile->playback_points, 3); |
| |
| if (stat == Ok && metafile->metafile_type == MetafileTypeEmf) |
| stat = METAFILE_PlaybackGetDC((GpMetafile*)metafile); |
| |
| if (stat == Ok) |
| EnumEnhMetaFile(0, metafile->hemf, enum_metafile_proc, &data, NULL); |
| |
| METAFILE_PlaybackReleaseDC((GpMetafile*)metafile); |
| |
| real_metafile->playback_graphics = NULL; |
| |
| return stat; |
| } |
| |
| static int CALLBACK get_metafile_type_proc(HDC hDC, HANDLETABLE *lpHTable, const ENHMETARECORD *lpEMFR, |
| int nObj, LPARAM lpData) |
| { |
| MetafileType *result = (MetafileType*)lpData; |
| |
| if (lpEMFR->iType == EMR_GDICOMMENT) |
| { |
| const EMRGDICOMMENT *comment = (const EMRGDICOMMENT*)lpEMFR; |
| |
| if (comment->cbData >= 4 && memcmp(comment->Data, "EMF+", 4) == 0) |
| { |
| const EmfPlusRecordHeader *header = (const EmfPlusRecordHeader*)&comment->Data[4]; |
| |
| if (4 + sizeof(EmfPlusRecordHeader) <= comment->cbData && |
| header->Type == EmfPlusRecordTypeHeader) |
| { |
| if ((header->Flags & 1) == 1) |
| *result = MetafileTypeEmfPlusDual; |
| else |
| *result = MetafileTypeEmfPlusOnly; |
| } |
| } |
| else |
| *result = MetafileTypeEmf; |
| } |
| else |
| *result = MetafileTypeEmf; |
| |
| return FALSE; |
| } |
| |
| MetafileType METAFILE_GetEmfType(HENHMETAFILE hemf) |
| { |
| MetafileType result = MetafileTypeInvalid; |
| EnumEnhMetaFile(NULL, hemf, get_metafile_type_proc, &result, NULL); |
| return result; |
| } |