blob: d135e362e00f97ea61ddbd8efe9adfd6271d8f82 [file] [log] [blame]
/*
* 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 <assert.h>
#define NONAMELESSUNION
#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 EmfPlusARGB
{
BYTE Blue;
BYTE Green;
BYTE Red;
BYTE Alpha;
} EmfPlusARGB;
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;
typedef struct EmfPlusClear
{
EmfPlusRecordHeader Header;
DWORD Color;
} EmfPlusClear;
typedef struct EmfPlusFillRects
{
EmfPlusRecordHeader Header;
DWORD BrushID;
DWORD Count;
} EmfPlusFillRects;
typedef struct EmfPlusSetClipRect
{
EmfPlusRecordHeader Header;
GpRectF ClipRect;
} EmfPlusSetClipRect;
typedef struct EmfPlusSetPageTransform
{
EmfPlusRecordHeader Header;
REAL PageScale;
} EmfPlusSetPageTransform;
typedef struct EmfPlusRect
{
SHORT X;
SHORT Y;
SHORT Width;
SHORT Height;
} EmfPlusRect;
typedef struct EmfPlusSetWorldTransform
{
EmfPlusRecordHeader Header;
REAL MatrixData[6];
} EmfPlusSetWorldTransform;
typedef struct EmfPlusScaleWorldTransform
{
EmfPlusRecordHeader Header;
REAL Sx;
REAL Sy;
} EmfPlusScaleWorldTransform;
typedef struct EmfPlusMultiplyWorldTransform
{
EmfPlusRecordHeader Header;
REAL MatrixData[6];
} EmfPlusMultiplyWorldTransform;
typedef struct EmfPlusRotateWorldTransform
{
EmfPlusRecordHeader Header;
REAL Angle;
} EmfPlusRotateWorldTransform;
typedef struct EmfPlusTranslateWorldTransform
{
EmfPlusRecordHeader Header;
REAL dx;
REAL dy;
} EmfPlusTranslateWorldTransform;
typedef struct EmfPlusBeginContainer
{
EmfPlusRecordHeader Header;
GpRectF DestRect;
GpRectF SrcRect;
DWORD StackIndex;
} EmfPlusBeginContainer;
typedef struct EmfPlusContainerRecord
{
EmfPlusRecordHeader Header;
DWORD StackIndex;
} EmfPlusContainerRecord;
enum container_type
{
BEGIN_CONTAINER,
SAVE_GRAPHICS
};
typedef struct container
{
struct list entry;
DWORD id;
enum container_type type;
GraphicsContainer state;
GpMatrix world_transform;
GpUnit page_unit;
REAL page_scale;
GpRegion *clip;
} container;
enum PenDataFlags
{
PenDataTransform = 0x0001,
PenDataStartCap = 0x0002,
PenDataEndCap = 0x0004,
PenDataJoin = 0x0008,
PenDataMiterLimit = 0x0010,
PenDataLineStyle = 0x0020,
PenDataDashedLineCap = 0x0040,
PenDataDashedLineOffset = 0x0080,
PenDataDashedLine = 0x0100,
PenDataNonCenter = 0x0200,
PenDataCompoundLine = 0x0400,
PenDataCustomStartCap = 0x0800,
PenDataCustomEndCap = 0x1000
};
typedef struct EmfPlusTransformMatrix
{
REAL TransformMatrix[6];
} EmfPlusTransformMatrix;
enum LineStyle
{
LineStyleSolid,
LineStyleDash,
LineStyleDot,
LineStyleDashDot,
LineStyleDashDotDot,
LineStyleCustom
};
typedef struct EmfPlusPenData
{
DWORD PenDataFlags;
DWORD PenUnit;
REAL PenWidth;
BYTE OptionalData[1];
} EmfPlusPenData;
typedef struct EmfPlusSolidBrushData
{
EmfPlusARGB SolidColor;
} EmfPlusSolidBrushData;
typedef struct EmfPlusBrush
{
DWORD Version;
DWORD Type;
union {
EmfPlusSolidBrushData solid;
} BrushData;
} EmfPlusBrush;
typedef struct EmfPlusPen
{
DWORD Version;
DWORD Type;
/* EmfPlusPenData */
/* EmfPlusBrush */
BYTE data[1];
} EmfPlusPen;
typedef struct EmfPlusPath
{
DWORD Version;
DWORD PathPointCount;
DWORD PathPointFlags;
/* PathPoints[] */
/* PathPointTypes[] */
/* AlignmentPadding */
BYTE data[1];
} EmfPlusPath;
typedef struct EmfPlusRegion
{
DWORD Version;
DWORD RegionNodeCount;
BYTE RegionNode[1];
} EmfPlusRegion;
typedef enum
{
BitmapDataTypePixel,
BitmapDataTypeCompressed,
} BitmapDataType;
typedef struct EmfPlusBitmap
{
DWORD Width;
DWORD Height;
DWORD Stride;
DWORD PixelFormat;
DWORD Type;
BYTE BitmapData[1];
} EmfPlusBitmap;
typedef struct EmfPlusMetafile
{
DWORD Type;
DWORD MetafileDataSize;
BYTE MetafileData[1];
} EmfPlusMetafile;
typedef enum ImageDataType
{
ImageDataTypeUnknown,
ImageDataTypeBitmap,
ImageDataTypeMetafile,
} ImageDataType;
typedef struct EmfPlusImage
{
DWORD Version;
ImageDataType Type;
union
{
EmfPlusBitmap bitmap;
EmfPlusMetafile metafile;
} ImageData;
} EmfPlusImage;
typedef struct EmfPlusImageAttributes
{
DWORD Version;
DWORD Reserved1;
DWORD WrapMode;
EmfPlusARGB ClampColor;
DWORD ObjectClamp;
DWORD Reserved2;
} EmfPlusImageAttributes;
typedef enum ObjectType
{
ObjectTypeInvalid,
ObjectTypeBrush,
ObjectTypePen,
ObjectTypePath,
ObjectTypeRegion,
ObjectTypeImage,
ObjectTypeFont,
ObjectTypeStringFormat,
ObjectTypeImageAttributes,
ObjectTypeCustomLineCap,
} ObjectType;
typedef struct EmfPlusObject
{
EmfPlusRecordHeader Header;
union
{
EmfPlusBrush brush;
EmfPlusPen pen;
EmfPlusPath path;
EmfPlusRegion region;
EmfPlusImage image;
EmfPlusImageAttributes image_attributes;
} ObjectData;
} EmfPlusObject;
typedef struct EmfPlusRectF
{
float X;
float Y;
float Width;
float Height;
} EmfPlusRectF;
typedef struct EmfPlusPointF
{
float X;
float Y;
} EmfPlusPointF;
typedef struct EmfPlusDrawImagePoints
{
EmfPlusRecordHeader Header;
DWORD ImageAttributesID;
DWORD SrcUnit;
EmfPlusRectF SrcRect;
DWORD count;
union
{
/*EmfPlusPointR pointR;
EmfPlusPoint point;*/
EmfPlusPointF pointF;
} PointData[3];
} EmfPlusDrawImagePoints;
typedef struct EmfPlusDrawPath
{
EmfPlusRecordHeader Header;
DWORD PenId;
} EmfPlusDrawPath;
typedef struct EmfPlusFillPath
{
EmfPlusRecordHeader Header;
union
{
DWORD BrushId;
EmfPlusARGB Color;
} data;
} EmfPlusFillPath;
static DWORD METAFILE_AddObjectId(GpMetafile *metafile)
{
return (metafile->next_object_id++) % 64;
}
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 = heap_alloc_zero(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 = heap_alloc_zero(data_size);
if (!new_data)
return OutOfMemory;
memcpy(new_data, metafile->comment_data, metafile->comment_data_length);
metafile->comment_data_size = data_size;
heap_free(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_RemoveLastRecord(GpMetafile *metafile, EmfPlusRecordHeader *record)
{
assert(metafile->comment_data + metafile->comment_data_length == (BYTE*)record + record->Size);
metafile->comment_data_length -= record->Size;
}
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 = VERSION_MAGIC2;
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 dpix, dpiy;
REAL framerect_factor_x, framerect_factor_y;
RECT rc, *lprc;
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;
dpix = (REAL)GetDeviceCaps(hdc, HORZRES) / GetDeviceCaps(hdc, HORZSIZE) * 25.4;
dpiy = (REAL)GetDeviceCaps(hdc, VERTRES) / GetDeviceCaps(hdc, VERTSIZE) * 25.4;
if (frameRect)
{
switch (frameUnit)
{
case MetafileFrameUnitPixel:
framerect_factor_x = 2540.0 / dpix;
framerect_factor_y = 2540.0 / dpiy;
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;
lprc = &rc;
}
else
lprc = NULL;
record_dc = CreateEnhMetaFileW(hdc, NULL, lprc, desc);
if (!record_dc)
return GenericError;
*metafile = heap_alloc_zero(sizeof(GpMetafile));
if(!*metafile)
{
DeleteEnhMetaFile(CloseEnhMetaFile(record_dc));
return OutOfMemory;
}
(*metafile)->image.type = ImageTypeMetafile;
(*metafile)->image.flags = ImageFlagsNone;
(*metafile)->image.palette = NULL;
(*metafile)->image.xres = dpix;
(*metafile)->image.yres = dpiy;
(*metafile)->bounds.X = (*metafile)->bounds.Y = 0.0;
(*metafile)->bounds.Width = (*metafile)->bounds.Height = 1.0;
(*metafile)->unit = UnitPixel;
(*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;
list_init(&(*metafile)->containers);
if (!frameRect)
{
(*metafile)->auto_frame = TRUE;
(*metafile)->auto_frame_min.X = 0;
(*metafile)->auto_frame_min.Y = 0;
(*metafile)->auto_frame_max.X = -1;
(*metafile)->auto_frame_max.Y = -1;
}
stat = METAFILE_WriteHeader(*metafile, hdc);
if (stat != Ok)
{
DeleteEnhMetaFile(CloseEnhMetaFile(record_dc));
heap_free(*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 WINGDIPAPI GdipRecordMetafileStream(IStream *stream, HDC hdc, EmfType type, GDIPCONST GpRectF *frameRect,
MetafileFrameUnit frameUnit, GDIPCONST WCHAR *desc, GpMetafile **metafile)
{
GpStatus stat;
TRACE("(%p %p %d %p %d %p %p)\n", stream, hdc, type, frameRect, frameUnit, desc, metafile);
if (!stream)
return InvalidParameter;
stat = GdipRecordMetafile(hdc, type, frameRect, frameUnit, desc, metafile);
if (stat == Ok)
{
(*metafile)->record_stream = stream;
IStream_AddRef(stream);
}
return stat;
}
static void METAFILE_AdjustFrame(GpMetafile* metafile, const GpPointF *points,
UINT num_points)
{
int i;
if (!metafile->auto_frame || !num_points)
return;
if (metafile->auto_frame_max.X < metafile->auto_frame_min.X)
metafile->auto_frame_max = metafile->auto_frame_min = points[0];
for (i=0; i<num_points; i++)
{
if (points[i].X < metafile->auto_frame_min.X)
metafile->auto_frame_min.X = points[i].X;
if (points[i].X > metafile->auto_frame_max.X)
metafile->auto_frame_max.X = points[i].X;
if (points[i].Y < metafile->auto_frame_min.Y)
metafile->auto_frame_min.Y = points[i].Y;
if (points[i].Y > metafile->auto_frame_max.Y)
metafile->auto_frame_max.Y = points[i].Y;
}
}
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;
metafile->record_graphics->xres = 96.0;
metafile->record_graphics->yres = 96.0;
}
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_GraphicsClear(GpMetafile* metafile, ARGB color)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusClear *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusClear), (void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeClear;
record->Header.Flags = 0;
record->Color = color;
METAFILE_WriteRecords(metafile);
}
return Ok;
}
static BOOL is_integer_rect(const GpRectF *rect)
{
SHORT x, y, width, height;
x = rect->X;
y = rect->Y;
width = rect->Width;
height = rect->Height;
if (rect->X != (REAL)x || rect->Y != (REAL)y ||
rect->Width != (REAL)width || rect->Height != (REAL)height)
return FALSE;
return TRUE;
}
GpStatus METAFILE_FillRectangles(GpMetafile* metafile, GpBrush* brush,
GDIPCONST GpRectF* rects, INT count)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusFillRects *record;
GpStatus stat;
BOOL integer_rects = TRUE;
int i;
DWORD brushid;
int flags = 0;
if (brush->bt == BrushTypeSolidColor)
{
flags |= 0x8000;
brushid = ((GpSolidFill*)brush)->color;
}
else
{
FIXME("brush serialization not implemented\n");
return NotImplemented;
}
for (i=0; i<count; i++)
{
if (!is_integer_rect(&rects[i]))
{
integer_rects = FALSE;
break;
}
}
if (integer_rects)
flags |= 0x4000;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusFillRects) + count * (integer_rects ? sizeof(EmfPlusRect) : sizeof(GpRectF)),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeFillRects;
record->Header.Flags = flags;
record->BrushID = brushid;
record->Count = count;
if (integer_rects)
{
EmfPlusRect *record_rects = (EmfPlusRect*)(record+1);
for (i=0; i<count; i++)
{
record_rects[i].X = (SHORT)rects[i].X;
record_rects[i].Y = (SHORT)rects[i].Y;
record_rects[i].Width = (SHORT)rects[i].Width;
record_rects[i].Height = (SHORT)rects[i].Height;
}
}
else
memcpy(record+1, rects, sizeof(GpRectF) * count);
METAFILE_WriteRecords(metafile);
}
if (metafile->auto_frame)
{
GpPointF corners[4];
int i;
for (i=0; i<count; i++)
{
corners[0].X = rects[i].X;
corners[0].Y = rects[i].Y;
corners[1].X = rects[i].X + rects[i].Width;
corners[1].Y = rects[i].Y;
corners[2].X = rects[i].X;
corners[2].Y = rects[i].Y + rects[i].Height;
corners[3].X = rects[i].X + rects[i].Width;
corners[3].Y = rects[i].Y + rects[i].Height;
GdipTransformPoints(metafile->record_graphics, CoordinateSpaceDevice,
CoordinateSpaceWorld, corners, 4);
METAFILE_AdjustFrame(metafile, corners, 4);
}
}
return Ok;
}
GpStatus METAFILE_SetClipRect(GpMetafile* metafile, REAL x, REAL y, REAL width, REAL height, CombineMode mode)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusSetClipRect *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusSetClipRect),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeSetClipRect;
record->Header.Flags = (mode & 0xf) << 8;
record->ClipRect.X = x;
record->ClipRect.Y = y;
record->ClipRect.Width = width;
record->ClipRect.Height = height;
METAFILE_WriteRecords(metafile);
}
return Ok;
}
static GpStatus METAFILE_AddRegionObject(GpMetafile *metafile, GpRegion *region, DWORD *id)
{
EmfPlusObject *object_record;
DWORD size;
GpStatus stat;
*id = -1;
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
return Ok;
size = write_region_data(region, NULL);
stat = METAFILE_AllocateRecord(metafile,
FIELD_OFFSET(EmfPlusObject, ObjectData.region) + size, (void**)&object_record);
if (stat != Ok) return stat;
*id = METAFILE_AddObjectId(metafile);
object_record->Header.Type = EmfPlusRecordTypeObject;
object_record->Header.Flags = *id | ObjectTypeRegion << 8;
write_region_data(region, &object_record->ObjectData.region);
return Ok;
}
GpStatus METAFILE_SetClipRegion(GpMetafile* metafile, GpRegion* region, CombineMode mode)
{
EmfPlusRecordHeader *record;
DWORD region_id;
GpStatus stat;
if (metafile->metafile_type == MetafileTypeEmf)
{
FIXME("stub!\n");
return NotImplemented;
}
stat = METAFILE_AddRegionObject(metafile, region, &region_id);
if (stat != Ok) return stat;
stat = METAFILE_AllocateRecord(metafile, sizeof(*record), (void**)&record);
if (stat != Ok) return stat;
record->Type = EmfPlusRecordTypeSetClipRegion;
record->Flags = region_id | mode << 8;
METAFILE_WriteRecords(metafile);
return Ok;
}
GpStatus METAFILE_SetPageTransform(GpMetafile* metafile, GpUnit unit, REAL scale)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusSetPageTransform *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusSetPageTransform),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeSetPageTransform;
record->Header.Flags = unit;
record->PageScale = scale;
METAFILE_WriteRecords(metafile);
}
return Ok;
}
GpStatus METAFILE_SetWorldTransform(GpMetafile* metafile, GDIPCONST GpMatrix* transform)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusSetWorldTransform *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusSetWorldTransform),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeSetWorldTransform;
record->Header.Flags = 0;
memcpy(record->MatrixData, transform->matrix, sizeof(record->MatrixData));
METAFILE_WriteRecords(metafile);
}
return Ok;
}
GpStatus METAFILE_ScaleWorldTransform(GpMetafile* metafile, REAL sx, REAL sy, MatrixOrder order)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusScaleWorldTransform *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusScaleWorldTransform),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeScaleWorldTransform;
record->Header.Flags = (order == MatrixOrderAppend ? 0x2000 : 0);
record->Sx = sx;
record->Sy = sy;
METAFILE_WriteRecords(metafile);
}
return Ok;
}
GpStatus METAFILE_MultiplyWorldTransform(GpMetafile* metafile, GDIPCONST GpMatrix* matrix, MatrixOrder order)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusMultiplyWorldTransform *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusMultiplyWorldTransform),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeMultiplyWorldTransform;
record->Header.Flags = (order == MatrixOrderAppend ? 0x2000 : 0);
memcpy(record->MatrixData, matrix->matrix, sizeof(record->MatrixData));
METAFILE_WriteRecords(metafile);
}
return Ok;
}
GpStatus METAFILE_RotateWorldTransform(GpMetafile* metafile, REAL angle, MatrixOrder order)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusRotateWorldTransform *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusRotateWorldTransform),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeRotateWorldTransform;
record->Header.Flags = (order == MatrixOrderAppend ? 0x2000 : 0);
record->Angle = angle;
METAFILE_WriteRecords(metafile);
}
return Ok;
}
GpStatus METAFILE_TranslateWorldTransform(GpMetafile* metafile, REAL dx, REAL dy, MatrixOrder order)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusTranslateWorldTransform *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusTranslateWorldTransform),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeTranslateWorldTransform;
record->Header.Flags = (order == MatrixOrderAppend ? 0x2000 : 0);
record->dx = dx;
record->dy = dy;
METAFILE_WriteRecords(metafile);
}
return Ok;
}
GpStatus METAFILE_ResetWorldTransform(GpMetafile* metafile)
{
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 = EmfPlusRecordTypeResetWorldTransform;
record->Flags = 0;
METAFILE_WriteRecords(metafile);
}
return Ok;
}
GpStatus METAFILE_BeginContainer(GpMetafile* metafile, GDIPCONST GpRectF *dstrect,
GDIPCONST GpRectF *srcrect, GpUnit unit, DWORD StackIndex)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusBeginContainer *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile, sizeof(*record), (void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeBeginContainer;
record->Header.Flags = unit & 0xff;
record->DestRect = *dstrect;
record->SrcRect = *srcrect;
record->StackIndex = StackIndex;
METAFILE_WriteRecords(metafile);
}
return Ok;
}
GpStatus METAFILE_BeginContainerNoParams(GpMetafile* metafile, DWORD StackIndex)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusContainerRecord *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusContainerRecord),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeBeginContainerNoParams;
record->Header.Flags = 0;
record->StackIndex = StackIndex;
METAFILE_WriteRecords(metafile);
}
return Ok;
}
GpStatus METAFILE_EndContainer(GpMetafile* metafile, DWORD StackIndex)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusContainerRecord *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusContainerRecord),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeEndContainer;
record->Header.Flags = 0;
record->StackIndex = StackIndex;
METAFILE_WriteRecords(metafile);
}
return Ok;
}
GpStatus METAFILE_SaveGraphics(GpMetafile* metafile, DWORD StackIndex)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusContainerRecord *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusContainerRecord),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeSave;
record->Header.Flags = 0;
record->StackIndex = StackIndex;
METAFILE_WriteRecords(metafile);
}
return Ok;
}
GpStatus METAFILE_RestoreGraphics(GpMetafile* metafile, DWORD StackIndex)
{
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
{
EmfPlusContainerRecord *record;
GpStatus stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusContainerRecord),
(void**)&record);
if (stat != Ok)
return stat;
record->Header.Type = EmfPlusRecordTypeRestore;
record->Header.Flags = 0;
record->StackIndex = StackIndex;
METAFILE_WriteRecords(metafile);
}
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;
heap_free(metafile->comment_data);
metafile->comment_data = NULL;
metafile->comment_data_size = 0;
if (stat == Ok)
{
MetafileHeader header;
stat = GdipGetMetafileHeaderFromEmf(metafile->hemf, &header);
if (stat == Ok && metafile->auto_frame &&
metafile->auto_frame_max.X >= metafile->auto_frame_min.X)
{
RECTL bounds_rc, gdi_bounds_rc;
REAL x_scale = 2540.0 / header.DpiX;
REAL y_scale = 2540.0 / header.DpiY;
BYTE* buffer;
UINT buffer_size;
bounds_rc.left = floorf(metafile->auto_frame_min.X * x_scale);
bounds_rc.top = floorf(metafile->auto_frame_min.Y * y_scale);
bounds_rc.right = ceilf(metafile->auto_frame_max.X * x_scale);
bounds_rc.bottom = ceilf(metafile->auto_frame_max.Y * y_scale);
gdi_bounds_rc = header.u.EmfHeader.rclBounds;
if (gdi_bounds_rc.right > gdi_bounds_rc.left && gdi_bounds_rc.bottom > gdi_bounds_rc.top)
{
bounds_rc.left = min(bounds_rc.left, gdi_bounds_rc.left);
bounds_rc.top = min(bounds_rc.top, gdi_bounds_rc.top);
bounds_rc.right = max(bounds_rc.right, gdi_bounds_rc.right);
bounds_rc.bottom = max(bounds_rc.bottom, gdi_bounds_rc.bottom);
}
buffer_size = GetEnhMetaFileBits(metafile->hemf, 0, NULL);
buffer = heap_alloc(buffer_size);
if (buffer)
{
HENHMETAFILE new_hemf;
GetEnhMetaFileBits(metafile->hemf, buffer_size, buffer);
((ENHMETAHEADER*)buffer)->rclFrame = bounds_rc;
new_hemf = SetEnhMetaFileBits(buffer_size, buffer);
if (new_hemf)
{
DeleteEnhMetaFile(metafile->hemf);
metafile->hemf = new_hemf;
}
else
stat = OutOfMemory;
heap_free(buffer);
}
else
stat = OutOfMemory;
if (stat == Ok)
stat = GdipGetMetafileHeaderFromEmf(metafile->hemf, &header);
}
if (stat == Ok)
{
metafile->bounds.X = header.X;
metafile->bounds.Y = header.Y;
metafile->bounds.Width = header.Width;
metafile->bounds.Height = header.Height;
}
}
if (stat == Ok && metafile->record_stream)
{
BYTE *buffer;
UINT buffer_size;
buffer_size = GetEnhMetaFileBits(metafile->hemf, 0, NULL);
buffer = heap_alloc(buffer_size);
if (buffer)
{
HRESULT hr;
GetEnhMetaFileBits(metafile->hemf, buffer_size, buffer);
hr = IStream_Write(metafile->record_stream, buffer, buffer_size, NULL);
if (FAILED(hr))
stat = hresult_to_status(hr);
heap_free(buffer);
}
else
stat = OutOfMemory;
}
if (metafile->record_stream)
{
IStream_Release(metafile->record_stream);
metafile->record_stream = NULL;
}
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 void METAFILE_GetFinalGdiTransform(const GpMetafile *metafile, XFORM *result)
{
const GpRectF *rect;
const GpPointF *pt;
/* This transforms metafile device space to output points. */
rect = &metafile->src_rect;
pt = metafile->playback_points;
result->eM11 = (pt[1].X - pt[0].X) / rect->Width;
result->eM21 = (pt[2].X - pt[0].X) / rect->Height;
result->eDx = pt[0].X - result->eM11 * rect->X - result->eM21 * rect->Y;
result->eM12 = (pt[1].Y - pt[0].Y) / rect->Width;
result->eM22 = (pt[2].Y - pt[0].Y) / rect->Height;
result->eDy = pt[0].Y - result->eM12 * rect->X - result->eM22 * rect->Y;
}
static GpStatus METAFILE_PlaybackUpdateGdiTransform(GpMetafile *metafile)
{
XFORM combined, final;
METAFILE_GetFinalGdiTransform(metafile, &final);
CombineTransform(&combined, &metafile->gdiworldtransform, &final);
SetGraphicsMode(metafile->playback_dc, GM_ADVANCED);
SetWorldTransform(metafile->playback_dc, &combined);
return Ok;
}
static GpStatus METAFILE_PlaybackGetDC(GpMetafile *metafile)
{
GpStatus stat = Ok;
stat = GdipGetDC(metafile->playback_graphics, &metafile->playback_dc);
if (stat == Ok)
{
static const XFORM identity = {1, 0, 0, 1, 0, 0};
metafile->gdiworldtransform = identity;
METAFILE_PlaybackUpdateGdiTransform(metafile);
}
return stat;
}
static void METAFILE_PlaybackReleaseDC(GpMetafile *metafile)
{
if (metafile->playback_dc)
{
GdipReleaseDC(metafile->playback_graphics, metafile->playback_dc);
metafile->playback_dc = NULL;
}
}
static GpStatus METAFILE_PlaybackUpdateClip(GpMetafile *metafile)
{
GpStatus stat;
stat = GdipCombineRegionRegion(metafile->playback_graphics->clip, metafile->base_clip, CombineModeReplace);
if (stat == Ok)
stat = GdipCombineRegionRegion(metafile->playback_graphics->clip, metafile->clip, CombineModeIntersect);
return stat;
}
static GpStatus METAFILE_PlaybackUpdateWorldTransform(GpMetafile *metafile)
{
GpMatrix *real_transform;
GpStatus stat;
stat = GdipCreateMatrix3(&metafile->src_rect, metafile->playback_points, &real_transform);
if (stat == Ok)
{
REAL scale = units_to_pixels(1.0, metafile->page_unit, 96.0);
if (metafile->page_unit != UnitDisplay)
scale *= metafile->page_scale;
stat = GdipScaleMatrix(real_transform, scale, scale, MatrixOrderPrepend);
if (stat == Ok)
stat = GdipMultiplyMatrix(real_transform, metafile->world_transform, MatrixOrderPrepend);
if (stat == Ok)
stat = GdipSetWorldTransform(metafile->playback_graphics, real_transform);
GdipDeleteMatrix(real_transform);
}
return stat;
}
GpStatus WINGDIPAPI GdipPlayMetafileRecord(GDIPCONST GpMetafile *metafile,
EmfPlusRecordType recordType, UINT flags, UINT dataSize, GDIPCONST BYTE *data)
{
GpStatus stat;
GpMetafile *real_metafile = (GpMetafile*)metafile;
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)
{
switch (recordType)
{
case EMR_SETMAPMODE:
case EMR_SAVEDC:
case EMR_RESTOREDC:
case EMR_SETWINDOWORGEX:
case EMR_SETWINDOWEXTEX:
case EMR_SETVIEWPORTORGEX:
case EMR_SETVIEWPORTEXTEX:
case EMR_SCALEVIEWPORTEXTEX:
case EMR_SCALEWINDOWEXTEX:
case EMR_MODIFYWORLDTRANSFORM:
FIXME("not implemented for record type %x\n", recordType);
break;
case EMR_SETWORLDTRANSFORM:
{
const XFORM* xform = (void*)data;
real_metafile->gdiworldtransform = *xform;
METAFILE_PlaybackUpdateGdiTransform(real_metafile);
break;
}
case EMR_EXTSELECTCLIPRGN:
{
DWORD rgndatasize = *(DWORD*)data;
DWORD mode = *(DWORD*)(data + 4);
const RGNDATA *rgndata = (const RGNDATA*)(data + 8);
HRGN hrgn = NULL;
if (dataSize > 8)
{
XFORM final;
METAFILE_GetFinalGdiTransform(metafile, &final);
hrgn = ExtCreateRegion(&final, rgndatasize, rgndata);
}
ExtSelectClipRgn(metafile->playback_dc, hrgn, mode);
DeleteObject(hrgn);
return Ok;
}
default:
{
ENHMETARECORD *record = heap_alloc_zero(dataSize + 8);
if (record)
{
record->iType = recordType;
record->nSize = dataSize + 8;
memcpy(record->dParm, data, dataSize);
if(PlayEnhMetaFileRecord(metafile->playback_dc, metafile->handle_table,
record, metafile->handle_count) == 0)
ERR("PlayEnhMetaFileRecord failed\n");
heap_free(record);
}
else
return OutOfMemory;
break;
}
}
}
}
else
{
EmfPlusRecordHeader *header = (EmfPlusRecordHeader*)(data)-1;
METAFILE_PlaybackReleaseDC((GpMetafile*)metafile);
switch(recordType)
{
case EmfPlusRecordTypeHeader:
case EmfPlusRecordTypeEndOfFile:
break;
case EmfPlusRecordTypeGetDC:
METAFILE_PlaybackGetDC((GpMetafile*)metafile);
break;
case EmfPlusRecordTypeClear:
{
EmfPlusClear *record = (EmfPlusClear*)header;
return GdipGraphicsClear(metafile->playback_graphics, record->Color);
}
case EmfPlusRecordTypeFillRects:
{
EmfPlusFillRects *record = (EmfPlusFillRects*)header;
GpBrush *brush, *temp_brush=NULL;
GpRectF *rects, *temp_rects=NULL;
if (dataSize + sizeof(EmfPlusRecordHeader) < sizeof(EmfPlusFillRects))
return InvalidParameter;
if (flags & 0x4000)
{
if (dataSize + sizeof(EmfPlusRecordHeader) < sizeof(EmfPlusFillRects) + sizeof(EmfPlusRect) * record->Count)
return InvalidParameter;
}
else
{
if (dataSize + sizeof(EmfPlusRecordHeader) < sizeof(EmfPlusFillRects) + sizeof(GpRectF) * record->Count)
return InvalidParameter;
}
if (flags & 0x8000)
{
stat = GdipCreateSolidFill((ARGB)record->BrushID, (GpSolidFill**)&temp_brush);
brush = temp_brush;
}
else
{
FIXME("brush deserialization not implemented\n");
return NotImplemented;
}
if (stat == Ok)
{
if (flags & 0x4000)
{
EmfPlusRect *int_rects = (EmfPlusRect*)(record+1);
int i;
rects = temp_rects = heap_alloc_zero(sizeof(GpRectF) * record->Count);
if (rects)
{
for (i=0; i<record->Count; i++)
{
rects[i].X = int_rects[i].X;
rects[i].Y = int_rects[i].Y;
rects[i].Width = int_rects[i].Width;
rects[i].Height = int_rects[i].Height;
}
}
else
stat = OutOfMemory;
}
else
rects = (GpRectF*)(record+1);
}
if (stat == Ok)
{
stat = GdipFillRectangles(metafile->playback_graphics, brush, rects, record->Count);
}
GdipDeleteBrush(temp_brush);
heap_free(temp_rects);
return stat;
}
case EmfPlusRecordTypeSetClipRect:
{
EmfPlusSetClipRect *record = (EmfPlusSetClipRect*)header;
CombineMode mode = (CombineMode)((flags >> 8) & 0xf);
GpRegion *region;
GpMatrix world_to_device;
if (dataSize + sizeof(EmfPlusRecordHeader) < sizeof(*record))
return InvalidParameter;
stat = GdipCreateRegionRect(&record->ClipRect, &region);
if (stat == Ok)
{
get_graphics_transform(real_metafile->playback_graphics,
CoordinateSpaceDevice, CoordinateSpaceWorld, &world_to_device);
GdipTransformRegion(region, &world_to_device);
GdipCombineRegionRegion(real_metafile->clip, region, mode);
GdipDeleteRegion(region);
}
return METAFILE_PlaybackUpdateClip(real_metafile);
}
case EmfPlusRecordTypeSetPageTransform:
{
EmfPlusSetPageTransform *record = (EmfPlusSetPageTransform*)header;
GpUnit unit = (GpUnit)flags;
if (dataSize + sizeof(EmfPlusRecordHeader) < sizeof(EmfPlusSetPageTransform))
return InvalidParameter;
real_metafile->page_unit = unit;
real_metafile->page_scale = record->PageScale;
return METAFILE_PlaybackUpdateWorldTransform(real_metafile);
}
case EmfPlusRecordTypeSetWorldTransform:
{
EmfPlusSetWorldTransform *record = (EmfPlusSetWorldTransform*)header;
if (dataSize + sizeof(EmfPlusRecordHeader) < sizeof(EmfPlusSetWorldTransform))
return InvalidParameter;
memcpy(real_metafile->world_transform->matrix, record->MatrixData, sizeof(record->MatrixData));
return METAFILE_PlaybackUpdateWorldTransform(real_metafile);
}
case EmfPlusRecordTypeScaleWorldTransform:
{
EmfPlusScaleWorldTransform *record = (EmfPlusScaleWorldTransform*)header;
MatrixOrder order = (flags & 0x2000) ? MatrixOrderAppend : MatrixOrderPrepend;
if (dataSize + sizeof(EmfPlusRecordHeader) < sizeof(EmfPlusScaleWorldTransform))
return InvalidParameter;
GdipScaleMatrix(real_metafile->world_transform, record->Sx, record->Sy, order);
return METAFILE_PlaybackUpdateWorldTransform(real_metafile);
}
case EmfPlusRecordTypeMultiplyWorldTransform:
{
EmfPlusMultiplyWorldTransform *record = (EmfPlusMultiplyWorldTransform*)header;
MatrixOrder order = (flags & 0x2000) ? MatrixOrderAppend : MatrixOrderPrepend;
GpMatrix matrix;
if (dataSize + sizeof(EmfPlusRecordHeader) < sizeof(EmfPlusMultiplyWorldTransform))
return InvalidParameter;
memcpy(matrix.matrix, record->MatrixData, sizeof(matrix.matrix));
GdipMultiplyMatrix(real_metafile->world_transform, &matrix, order);
return METAFILE_PlaybackUpdateWorldTransform(real_metafile);
}
case EmfPlusRecordTypeRotateWorldTransform:
{
EmfPlusRotateWorldTransform *record = (EmfPlusRotateWorldTransform*)header;
MatrixOrder order = (flags & 0x2000) ? MatrixOrderAppend : MatrixOrderPrepend;
if (dataSize + sizeof(EmfPlusRecordHeader) < sizeof(EmfPlusRotateWorldTransform))
return InvalidParameter;
GdipRotateMatrix(real_metafile->world_transform, record->Angle, order);
return METAFILE_PlaybackUpdateWorldTransform(real_metafile);
}
case EmfPlusRecordTypeTranslateWorldTransform:
{
EmfPlusTranslateWorldTransform *record = (EmfPlusTranslateWorldTransform*)header;
MatrixOrder order = (flags & 0x2000) ? MatrixOrderAppend : MatrixOrderPrepend;
if (dataSize + sizeof(EmfPlusRecordHeader) < sizeof(EmfPlusTranslateWorldTransform))
return InvalidParameter;
GdipTranslateMatrix(real_metafile->world_transform, record->dx, record->dy, order);
return METAFILE_PlaybackUpdateWorldTransform(real_metafile);
}
case EmfPlusRecordTypeResetWorldTransform:
{
GdipSetMatrixElements(real_metafile->world_transform, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
return METAFILE_PlaybackUpdateWorldTransform(real_metafile);
}
case EmfPlusRecordTypeBeginContainer:
{
EmfPlusBeginContainer *record = (EmfPlusBeginContainer*)header;
container* cont;
GpUnit unit;
REAL scale_x, scale_y;
GpRectF scaled_srcrect;
GpMatrix transform;
cont = heap_alloc_zero(sizeof(*cont));
if (!cont)
return OutOfMemory;
stat = GdipCloneRegion(metafile->clip, &cont->clip);
if (stat != Ok)
{
heap_free(cont);
return stat;
}
stat = GdipBeginContainer2(metafile->playback_graphics, &cont->state);
if (stat != Ok)
{
GdipDeleteRegion(cont->clip);
heap_free(cont);
return stat;
}
cont->id = record->StackIndex;
cont->type = BEGIN_CONTAINER;
cont->world_transform = *metafile->world_transform;
cont->page_unit = metafile->page_unit;
cont->page_scale = metafile->page_scale;
list_add_head(&real_metafile->containers, &cont->entry);
unit = record->Header.Flags & 0xff;
scale_x = units_to_pixels(1.0, unit, metafile->image.xres);
scale_y = units_to_pixels(1.0, unit, metafile->image.yres);
scaled_srcrect.X = scale_x * record->SrcRect.X;
scaled_srcrect.Y = scale_y * record->SrcRect.Y;
scaled_srcrect.Width = scale_x * record->SrcRect.Width;
scaled_srcrect.Height = scale_y * record->SrcRect.Height;
transform.matrix[0] = record->DestRect.Width / scaled_srcrect.Width;
transform.matrix[1] = 0.0;
transform.matrix[2] = 0.0;
transform.matrix[3] = record->DestRect.Height / scaled_srcrect.Height;
transform.matrix[4] = record->DestRect.X - scaled_srcrect.X;
transform.matrix[5] = record->DestRect.Y - scaled_srcrect.Y;
GdipMultiplyMatrix(real_metafile->world_transform, &transform, MatrixOrderPrepend);
return METAFILE_PlaybackUpdateWorldTransform(real_metafile);
}
case EmfPlusRecordTypeBeginContainerNoParams:
case EmfPlusRecordTypeSave:
{
EmfPlusContainerRecord *record = (EmfPlusContainerRecord*)header;
container* cont;
cont = heap_alloc_zero(sizeof(*cont));
if (!cont)
return OutOfMemory;
stat = GdipCloneRegion(metafile->clip, &cont->clip);
if (stat != Ok)
{
heap_free(cont);
return stat;
}
if (recordType == EmfPlusRecordTypeBeginContainerNoParams)
stat = GdipBeginContainer2(metafile->playback_graphics, &cont->state);
else
stat = GdipSaveGraphics(metafile->playback_graphics, &cont->state);
if (stat != Ok)
{
GdipDeleteRegion(cont->clip);
heap_free(cont);
return stat;
}
cont->id = record->StackIndex;
if (recordType == EmfPlusRecordTypeBeginContainerNoParams)
cont->type = BEGIN_CONTAINER;
else
cont->type = SAVE_GRAPHICS;
cont->world_transform = *metafile->world_transform;
cont->page_unit = metafile->page_unit;
cont->page_scale = metafile->page_scale;
list_add_head(&real_metafile->containers, &cont->entry);
break;
}
case EmfPlusRecordTypeEndContainer:
case EmfPlusRecordTypeRestore:
{
EmfPlusContainerRecord *record = (EmfPlusContainerRecord*)header;
container* cont;
enum container_type type;
BOOL found=FALSE;
if (recordType == EmfPlusRecordTypeEndContainer)
type = BEGIN_CONTAINER;
else
type = SAVE_GRAPHICS;
LIST_FOR_EACH_ENTRY(cont, &real_metafile->containers, container, entry)
{
if (cont->id == record->StackIndex && cont->type == type)
{
found = TRUE;
break;
}
}
if (found)
{
container* cont2;
/* pop any newer items on the stack */
while ((cont2 = LIST_ENTRY(list_head(&real_metafile->containers), container, entry)) != cont)
{
list_remove(&cont2->entry);
GdipDeleteRegion(cont2->clip);
heap_free(cont2);
}
if (type == BEGIN_CONTAINER)
GdipEndContainer(real_metafile->playback_graphics, cont->state);
else
GdipRestoreGraphics(real_metafile->playback_graphics, cont->state);
*real_metafile->world_transform = cont->world_transform;
real_metafile->page_unit = cont->page_unit;
real_metafile->page_scale = cont->page_scale;
GdipCombineRegionRegion(real_metafile->clip, cont->clip, CombineModeReplace);
list_remove(&cont->entry);
GdipDeleteRegion(cont->clip);
heap_free(cont);
}
break;
}
case EmfPlusRecordTypeSetPixelOffsetMode:
{
return GdipSetPixelOffsetMode(real_metafile->playback_graphics, (flags >> 8) & 0xf);
}
case EmfPlusRecordTypeSetCompositingQuality:
{
return GdipSetCompositingQuality(real_metafile->playback_graphics, (flags >> 8) & 0xf);
}
case EmfPlusRecordTypeSetInterpolationMode:
{
return GdipSetInterpolationMode(real_metafile->playback_graphics, (flags >> 8) & 0xf);
}
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 */
GraphicsContainer state;
GpPath *dst_path;
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;
real_metafile->src_rect = *srcRect;
memcpy(real_metafile->playback_points, destPoints, sizeof(PointF) * 3);
stat = GdipTransformPoints(graphics, CoordinateSpaceDevice, CoordinateSpaceWorld, real_metafile->playback_points, 3);
if (stat == Ok)
stat = GdipBeginContainer2(graphics, &state);
if (stat == Ok)
{
stat = GdipSetPageScale(graphics, 1.0);
if (stat == Ok)
stat = GdipSetPageUnit(graphics, UnitPixel);
if (stat == Ok)
stat = GdipResetWorldTransform(graphics);
if (stat == Ok)
stat = GdipCreateRegion(&real_metafile->base_clip);
if (stat == Ok)
stat = GdipGetClip(graphics, real_metafile->base_clip);
if (stat == Ok)
stat = GdipCreateRegion(&real_metafile->clip);
if (stat == Ok)
stat = GdipCreatePath(FillModeAlternate, &dst_path);
if (stat == Ok)
{
GpPointF clip_points[4];
clip_points[0] = real_metafile->playback_points[0];
clip_points[1] = real_metafile->playback_points[1];
clip_points[2].X = real_metafile->playback_points[1].X + real_metafile->playback_points[2].X
- real_metafile->playback_points[0].X;
clip_points[2].Y = real_metafile->playback_points[1].Y + real_metafile->playback_points[2].Y
- real_metafile->playback_points[0].Y;
clip_points[3] = real_metafile->playback_points[2];
stat = GdipAddPathPolygon(dst_path, clip_points, 4);
if (stat == Ok)
stat = GdipCombineRegionPath(real_metafile->base_clip, dst_path, CombineModeIntersect);
GdipDeletePath(dst_path);
}
if (stat == Ok)
stat = GdipCreateMatrix(&real_metafile->world_transform);
if (stat == Ok)
{
real_metafile->page_unit = UnitDisplay;
real_metafile->page_scale = 1.0;
stat = METAFILE_PlaybackUpdateWorldTransform(real_metafile);
}
if (stat == Ok)
{
stat = METAFILE_PlaybackUpdateClip(real_metafile);
}
if (stat == Ok && (metafile->metafile_type == MetafileTypeEmf ||
metafile->metafile_type == MetafileTypeWmfPlaceable ||
metafile->metafile_type == MetafileTypeWmf))
stat = METAFILE_PlaybackGetDC(real_metafile);
if (stat == Ok)
EnumEnhMetaFile(0, metafile->hemf, enum_metafile_proc, &data, NULL);
METAFILE_PlaybackReleaseDC(real_metafile);
GdipDeleteMatrix(real_metafile->world_transform);
real_metafile->world_transform = NULL;
GdipDeleteRegion(real_metafile->base_clip);
real_metafile->base_clip = NULL;
GdipDeleteRegion(real_metafile->clip);
real_metafile->clip = NULL;
while (list_head(&real_metafile->containers))
{
container* cont = LIST_ENTRY(list_head(&real_metafile->containers), container, entry);
list_remove(&cont->entry);
GdipDeleteRegion(cont->clip);
heap_free(cont);
}
GdipEndContainer(graphics, state);
}
real_metafile->playback_graphics = NULL;
return stat;
}
GpStatus WINGDIPAPI GdipEnumerateMetafileDestRect(GpGraphics *graphics,
GDIPCONST GpMetafile *metafile, GDIPCONST GpRectF *dest,
EnumerateMetafileProc callback, VOID *cb_data, GDIPCONST GpImageAttributes *attrs)
{
GpPointF points[3];
if (!graphics || !metafile || !dest) return InvalidParameter;
points[0].X = points[2].X = dest->X;
points[0].Y = points[1].Y = dest->Y;
points[1].X = dest->X + dest->Width;
points[2].Y = dest->Y + dest->Height;
return GdipEnumerateMetafileSrcRectDestPoints(graphics, metafile, points, 3,
&metafile->bounds, metafile->unit, callback, cb_data, attrs);
}
GpStatus WINGDIPAPI GdipEnumerateMetafileDestRectI(GpGraphics *graphics,
GDIPCONST GpMetafile *metafile, GDIPCONST GpRect *dest,
EnumerateMetafileProc callback, VOID *cb_data, GDIPCONST GpImageAttributes *attrs)
{
GpRectF destf;
if (!graphics || !metafile || !dest) return InvalidParameter;
destf.X = dest->X;
destf.Y = dest->Y;
destf.Width = dest->Width;
destf.Height = dest->Height;
return GdipEnumerateMetafileDestRect(graphics, metafile, &destf, callback, cb_data, attrs);
}
GpStatus WINGDIPAPI GdipEnumerateMetafileDestPoint(GpGraphics *graphics,
GDIPCONST GpMetafile *metafile, GDIPCONST GpPointF *dest,
EnumerateMetafileProc callback, VOID *cb_data, GDIPCONST GpImageAttributes *attrs)
{
GpRectF destf;
if (!graphics || !metafile || !dest) return InvalidParameter;
destf.X = dest->X;
destf.Y = dest->Y;
destf.Width = units_to_pixels(metafile->bounds.Width, metafile->unit, metafile->image.xres);
destf.Height = units_to_pixels(metafile->bounds.Height, metafile->unit, metafile->image.yres);
return GdipEnumerateMetafileDestRect(graphics, metafile, &destf, callback, cb_data, attrs);
}
GpStatus WINGDIPAPI GdipEnumerateMetafileDestPointI(GpGraphics *graphics,
GDIPCONST GpMetafile *metafile, GDIPCONST GpPoint *dest,
EnumerateMetafileProc callback, VOID *cb_data, GDIPCONST GpImageAttributes *attrs)
{
GpPointF ptf;
if (!graphics || !metafile || !dest) return InvalidParameter;
ptf.X = dest->X;
ptf.Y = dest->Y;
return GdipEnumerateMetafileDestPoint(graphics, metafile, &ptf, callback, cb_data, attrs);
}
GpStatus WINGDIPAPI GdipGetMetafileHeaderFromMetafile(GpMetafile * metafile,
MetafileHeader * header)
{
GpStatus status;
TRACE("(%p, %p)\n", metafile, header);
if(!metafile || !header)
return InvalidParameter;
if (metafile->hemf)
{
status = GdipGetMetafileHeaderFromEmf(metafile->hemf, header);
if (status != Ok) return status;
}
else
{
memset(header, 0, sizeof(*header));
header->Version = VERSION_MAGIC2;
}
header->Type = metafile->metafile_type;
header->DpiX = metafile->image.xres;
header->DpiY = metafile->image.yres;
header->Width = gdip_round(metafile->bounds.Width);
header->Height = gdip_round(metafile->bounds.Height);
return Ok;
}
static int CALLBACK get_emfplus_header_proc(HDC hDC, HANDLETABLE *lpHTable, const ENHMETARECORD *lpEMFR,
int nObj, LPARAM lpData)
{
EmfPlusHeader *dst_header = (EmfPlusHeader*)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(EmfPlusHeader) <= comment->cbData &&
header->Type == EmfPlusRecordTypeHeader)
{
memcpy(dst_header, header, sizeof(*dst_header));
}
}
}
else if (lpEMFR->iType == EMR_HEADER)
return TRUE;
return FALSE;
}
GpStatus WINGDIPAPI GdipGetMetafileHeaderFromEmf(HENHMETAFILE hemf,
MetafileHeader *header)
{
ENHMETAHEADER3 emfheader;
EmfPlusHeader emfplusheader;
MetafileType metafile_type;
TRACE("(%p,%p)\n", hemf, header);
if(!hemf || !header)
return InvalidParameter;
if (GetEnhMetaFileHeader(hemf, sizeof(emfheader), (ENHMETAHEADER*)&emfheader) == 0)
return GenericError;
emfplusheader.Header.Type = 0;
EnumEnhMetaFile(NULL, hemf, get_emfplus_header_proc, &emfplusheader, NULL);
if (emfplusheader.Header.Type == EmfPlusRecordTypeHeader)
{
if ((emfplusheader.Header.Flags & 1) == 1)
metafile_type = MetafileTypeEmfPlusDual;
else
metafile_type = MetafileTypeEmfPlusOnly;
}
else
metafile_type = MetafileTypeEmf;
header->Type = metafile_type;
header->Size = emfheader.nBytes;
header->DpiX = (REAL)emfheader.szlDevice.cx * 25.4 / emfheader.szlMillimeters.cx;
header->DpiY = (REAL)emfheader.szlDevice.cy * 25.4 / emfheader.szlMillimeters.cy;
header->X = gdip_round((REAL)emfheader.rclFrame.left / 2540.0 * header->DpiX);
header->Y = gdip_round((REAL)emfheader.rclFrame.top / 2540.0 * header->DpiY);
header->Width = gdip_round((REAL)(emfheader.rclFrame.right - emfheader.rclFrame.left) / 2540.0 * header->DpiX);
header->Height = gdip_round((REAL)(emfheader.rclFrame.bottom - emfheader.rclFrame.top) / 2540.0 * header->DpiY);
header->u.EmfHeader = emfheader;
if (metafile_type == MetafileTypeEmfPlusDual || metafile_type == MetafileTypeEmfPlusOnly)
{
header->Version = emfplusheader.Version;
header->EmfPlusFlags = emfplusheader.EmfPlusFlags;
header->EmfPlusHeaderSize = emfplusheader.Header.Size;
header->LogicalDpiX = emfplusheader.LogicalDpiX;
header->LogicalDpiY = emfplusheader.LogicalDpiY;
}
else
{
header->Version = emfheader.nVersion;
header->EmfPlusFlags = 0;
header->EmfPlusHeaderSize = 0;
header->LogicalDpiX = 0;
header->LogicalDpiY = 0;
}
return Ok;
}
GpStatus WINGDIPAPI GdipGetMetafileHeaderFromWmf(HMETAFILE hwmf,
GDIPCONST WmfPlaceableFileHeader *placeable, MetafileHeader *header)
{
GpStatus status;
GpMetafile *metafile;
TRACE("(%p,%p,%p)\n", hwmf, placeable, header);
status = GdipCreateMetafileFromWmf(hwmf, FALSE, placeable, &metafile);
if (status == Ok)
{
status = GdipGetMetafileHeaderFromMetafile(metafile, header);
GdipDisposeImage(&metafile->image);
}
return status;
}
GpStatus WINGDIPAPI GdipGetMetafileHeaderFromFile(GDIPCONST WCHAR *filename,
MetafileHeader *header)
{
GpStatus status;
GpMetafile *metafile;
TRACE("(%s,%p)\n", debugstr_w(filename), header);
if (!filename || !header)
return InvalidParameter;
status = GdipCreateMetafileFromFile(filename, &metafile);
if (status == Ok)
{
status = GdipGetMetafileHeaderFromMetafile(metafile, header);
GdipDisposeImage(&metafile->image);
}
return status;
}
GpStatus WINGDIPAPI GdipGetMetafileHeaderFromStream(IStream *stream,
MetafileHeader *header)
{
GpStatus status;
GpMetafile *metafile;
TRACE("(%p,%p)\n", stream, header);
if (!stream || !header)
return InvalidParameter;
status = GdipCreateMetafileFromStream(stream, &metafile);
if (status == Ok)
{
status = GdipGetMetafileHeaderFromMetafile(metafile, header);
GdipDisposeImage(&metafile->image);
}
return status;
}
GpStatus WINGDIPAPI GdipCreateMetafileFromEmf(HENHMETAFILE hemf, BOOL delete,
GpMetafile **metafile)
{
GpStatus stat;
MetafileHeader header;
TRACE("(%p,%i,%p)\n", hemf, delete, metafile);
if(!hemf || !metafile)
return InvalidParameter;
stat = GdipGetMetafileHeaderFromEmf(hemf, &header);
if (stat != Ok)
return stat;
*metafile = heap_alloc_zero(sizeof(GpMetafile));
if (!*metafile)
return OutOfMemory;
(*metafile)->image.type = ImageTypeMetafile;
(*metafile)->image.format = ImageFormatEMF;
(*metafile)->image.frame_count = 1;
(*metafile)->image.xres = header.DpiX;
(*metafile)->image.yres = header.DpiY;
(*metafile)->bounds.X = (REAL)header.u.EmfHeader.rclFrame.left / 2540.0 * header.DpiX;
(*metafile)->bounds.Y = (REAL)header.u.EmfHeader.rclFrame.top / 2540.0 * header.DpiY;
(*metafile)->bounds.Width = (REAL)(header.u.EmfHeader.rclFrame.right - header.u.EmfHeader.rclFrame.left)
/ 2540.0 * header.DpiX;
(*metafile)->bounds.Height = (REAL)(header.u.EmfHeader.rclFrame.bottom - header.u.EmfHeader.rclFrame.top)
/ 2540.0 * header.DpiY;
(*metafile)->unit = UnitPixel;
(*metafile)->metafile_type = header.Type;
(*metafile)->hemf = hemf;
(*metafile)->preserve_hemf = !delete;
list_init(&(*metafile)->containers);
TRACE("<-- %p\n", *metafile);
return Ok;
}
GpStatus WINGDIPAPI GdipCreateMetafileFromWmf(HMETAFILE hwmf, BOOL delete,
GDIPCONST WmfPlaceableFileHeader * placeable, GpMetafile **metafile)
{
UINT read;
BYTE *copy;
HENHMETAFILE hemf;
GpStatus retval = Ok;
TRACE("(%p, %d, %p, %p)\n", hwmf, delete, placeable, metafile);
if(!hwmf || !metafile)
return InvalidParameter;
*metafile = NULL;
read = GetMetaFileBitsEx(hwmf, 0, NULL);
if(!read)
return GenericError;
copy = heap_alloc_zero(read);
GetMetaFileBitsEx(hwmf, read, copy);
hemf = SetWinMetaFileBits(read, copy, NULL, NULL);
heap_free(copy);
/* FIXME: We should store and use hwmf instead of converting to hemf */
retval = GdipCreateMetafileFromEmf(hemf, TRUE, metafile);
if (retval == Ok)
{
if (placeable)
{
(*metafile)->image.xres = (REAL)placeable->Inch;
(*metafile)->image.yres = (REAL)placeable->Inch;
(*metafile)->bounds.X = ((REAL)placeable->BoundingBox.Left) / ((REAL)placeable->Inch);
(*metafile)->bounds.Y = ((REAL)placeable->BoundingBox.Top) / ((REAL)placeable->Inch);
(*metafile)->bounds.Width = (REAL)(placeable->BoundingBox.Right -
placeable->BoundingBox.Left);
(*metafile)->bounds.Height = (REAL)(placeable->BoundingBox.Bottom -
placeable->BoundingBox.Top);
(*metafile)->metafile_type = MetafileTypeWmfPlaceable;
}
else
(*metafile)->metafile_type = MetafileTypeWmf;
(*metafile)->image.format = ImageFormatWMF;
if (delete) DeleteMetaFile(hwmf);
}
else
DeleteEnhMetaFile(hemf);
return retval;
}
GpStatus WINGDIPAPI GdipCreateMetafileFromWmfFile(GDIPCONST WCHAR *file,
GDIPCONST WmfPlaceableFileHeader * placeable, GpMetafile **metafile)
{
HMETAFILE hmf;
HENHMETAFILE emf;
TRACE("(%s, %p, %p)\n", debugstr_w(file), placeable, metafile);
hmf = GetMetaFileW(file);
if(hmf)
return GdipCreateMetafileFromWmf(hmf, TRUE, placeable, metafile);
emf = GetEnhMetaFileW(file);
if(emf)
return GdipCreateMetafileFromEmf(emf, TRUE, metafile);
return GenericError;
}
GpStatus WINGDIPAPI GdipCreateMetafileFromFile(GDIPCONST WCHAR *file,
GpMetafile **metafile)
{
GpStatus status;
IStream *stream;
TRACE("(%p, %p)\n", file, metafile);
if (!file || !metafile) return InvalidParameter;
*metafile = NULL;
status = GdipCreateStreamOnFile(file, GENERIC_READ, &stream);
if (status == Ok)
{
status = GdipCreateMetafileFromStream(stream, metafile);
IStream_Release(stream);
}
return status;
}
GpStatus WINGDIPAPI GdipCreateMetafileFromStream(IStream *stream,
GpMetafile **metafile)
{
GpStatus stat;
TRACE("%p %p\n", stream, metafile);
stat = GdipLoadImageFromStream(stream, (GpImage **)metafile);
if (stat != Ok) return stat;
if ((*metafile)->image.type != ImageTypeMetafile)
{
GdipDisposeImage(&(*metafile)->image);
*metafile = NULL;
return GenericError;
}
return Ok;
}
GpStatus WINGDIPAPI GdipSetMetafileDownLevelRasterizationLimit(GpMetafile *metafile,
UINT limitDpi)
{
TRACE("(%p,%u)\n", metafile, limitDpi);
return Ok;
}
GpStatus WINGDIPAPI GdipConvertToEmfPlus(const GpGraphics* ref,
GpMetafile* metafile, BOOL* succ, EmfType emfType,
const WCHAR* description, GpMetafile** out_metafile)
{
static int calls;
TRACE("(%p,%p,%p,%u,%s,%p)\n", ref, metafile, succ, emfType,
debugstr_w(description), out_metafile);
if(!ref || !metafile || !out_metafile || emfType < EmfTypeEmfOnly || emfType > EmfTypeEmfPlusDual)
return InvalidParameter;
if(succ)
*succ = FALSE;
*out_metafile = NULL;
if(!(calls++))
FIXME("not implemented\n");
return NotImplemented;
}
GpStatus WINGDIPAPI GdipEmfToWmfBits(HENHMETAFILE hemf, UINT cbData16,
LPBYTE pData16, INT iMapMode, INT eFlags)
{
FIXME("(%p, %d, %p, %d, %d): stub\n", hemf, cbData16, pData16, iMapMode, eFlags);
return NotImplemented;
}
GpStatus WINGDIPAPI GdipRecordMetafileFileName(GDIPCONST WCHAR* fileName,
HDC hdc, EmfType type, GDIPCONST GpRectF *pFrameRect,
MetafileFrameUnit frameUnit, GDIPCONST WCHAR *desc,
GpMetafile **metafile)
{
FIXME("%s %p %d %p %d %s %p stub!\n", debugstr_w(fileName), hdc, type, pFrameRect,
frameUnit, debugstr_w(desc), metafile);
return NotImplemented;
}
GpStatus WINGDIPAPI GdipRecordMetafileFileNameI(GDIPCONST WCHAR* fileName, HDC hdc, EmfType type,
GDIPCONST GpRect *pFrameRect, MetafileFrameUnit frameUnit,
GDIPCONST WCHAR *desc, GpMetafile **metafile)
{
FIXME("%s %p %d %p %d %s %p stub!\n", debugstr_w(fileName), hdc, type, pFrameRect,
frameUnit, debugstr_w(desc), metafile);
return NotImplemented;
}
/*****************************************************************************
* GdipConvertToEmfPlusToFile [GDIPLUS.@]
*/
GpStatus WINGDIPAPI GdipConvertToEmfPlusToFile(const GpGraphics* refGraphics,
GpMetafile* metafile, BOOL* conversionSuccess,
const WCHAR* filename, EmfType emfType,
const WCHAR* description, GpMetafile** out_metafile)
{
FIXME("stub: %p, %p, %p, %p, %u, %p, %p\n", refGraphics, metafile, conversionSuccess, filename, emfType, description, out_metafile);
return NotImplemented;
}
static GpStatus METAFILE_CreateCompressedImageStream(GpImage *image, IStream **stream, DWORD *size)
{
LARGE_INTEGER zero;
STATSTG statstg;
GpStatus stat;
HRESULT hr;
*size = 0;
hr = CreateStreamOnHGlobal(NULL, TRUE, stream);
if (FAILED(hr)) return hresult_to_status(hr);
stat = encode_image_png(image, *stream, NULL);
if (stat != Ok)
{
IStream_Release(*stream);
return stat;
}
hr = IStream_Stat(*stream, &statstg, 1);
if (FAILED(hr))
{
IStream_Release(*stream);
return hresult_to_status(hr);
}
*size = statstg.cbSize.u.LowPart;
zero.QuadPart = 0;
hr = IStream_Seek(*stream, zero, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
{
IStream_Release(*stream);
return hresult_to_status(hr);
}
return Ok;
}
static GpStatus METAFILE_FillEmfPlusBitmap(EmfPlusBitmap *record, IStream *stream, DWORD size)
{
HRESULT hr;
record->Width = 0;
record->Height = 0;
record->Stride = 0;
record->PixelFormat = 0;
record->Type = BitmapDataTypeCompressed;
hr = IStream_Read(stream, record->BitmapData, size, NULL);
if (FAILED(hr)) return hresult_to_status(hr);
return Ok;
}
static GpStatus METAFILE_AddImageObject(GpMetafile *metafile, GpImage *image, DWORD *id)
{
EmfPlusObject *object_record;
GpStatus stat;
DWORD size;
*id = -1;
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
return Ok;
if (image->type == ImageTypeBitmap)
{
IStream *stream;
DWORD aligned_size;
stat = METAFILE_CreateCompressedImageStream(image, &stream, &size);
if (stat != Ok) return stat;
aligned_size = (size + 3) & ~3;
stat = METAFILE_AllocateRecord(metafile,
FIELD_OFFSET(EmfPlusObject, ObjectData.image.ImageData.bitmap.BitmapData[aligned_size]),
(void**)&object_record);
if (stat != Ok)
{
IStream_Release(stream);
return stat;
}
memset(object_record->ObjectData.image.ImageData.bitmap.BitmapData + size, 0, aligned_size - size);
*id = METAFILE_AddObjectId(metafile);
object_record->Header.Type = EmfPlusRecordTypeObject;
object_record->Header.Flags = *id | ObjectTypeImage << 8;
object_record->ObjectData.image.Version = VERSION_MAGIC2;
object_record->ObjectData.image.Type = ImageDataTypeBitmap;
stat = METAFILE_FillEmfPlusBitmap(&object_record->ObjectData.image.ImageData.bitmap, stream, size);
IStream_Release(stream);
if (stat != Ok) METAFILE_RemoveLastRecord(metafile, &object_record->Header);
return stat;
}
else if (image->type == ImageTypeMetafile)
{
HENHMETAFILE hemf = ((GpMetafile*)image)->hemf;
EmfPlusMetafile *metafile_record;
if (!hemf) return InvalidParameter;
size = GetEnhMetaFileBits(hemf, 0, NULL);
if (!size) return GenericError;
stat = METAFILE_AllocateRecord(metafile,
FIELD_OFFSET(EmfPlusObject, ObjectData.image.ImageData.metafile.MetafileData[size]),
(void**)&object_record);
if (stat != Ok) return stat;
*id = METAFILE_AddObjectId(metafile);
object_record->Header.Type = EmfPlusRecordTypeObject;
object_record->Header.Flags = *id | ObjectTypeImage << 8;
object_record->ObjectData.image.Version = VERSION_MAGIC2;
object_record->ObjectData.image.Type = ImageDataTypeMetafile;
metafile_record = &object_record->ObjectData.image.ImageData.metafile;
metafile_record->Type = ((GpMetafile*)image)->metafile_type;
metafile_record->MetafileDataSize = size;
if (GetEnhMetaFileBits(hemf, size, metafile_record->MetafileData) != size)
{
METAFILE_RemoveLastRecord(metafile, &object_record->Header);
return GenericError;
}
return Ok;
}
else
{
FIXME("not supported image type (%d)\n", image->type);
return NotImplemented;
}
}
static GpStatus METAFILE_AddImageAttributesObject(GpMetafile *metafile, const GpImageAttributes *attrs, DWORD *id)
{
EmfPlusObject *object_record;
EmfPlusImageAttributes *attrs_record;
GpStatus stat;
*id = -1;
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
return Ok;
if (!attrs)
return Ok;
stat = METAFILE_AllocateRecord(metafile,
FIELD_OFFSET(EmfPlusObject, ObjectData.image_attributes) + sizeof(EmfPlusImageAttributes),
(void**)&object_record);
if (stat != Ok) return stat;
*id = METAFILE_AddObjectId(metafile);
object_record->Header.Type = EmfPlusRecordTypeObject;
object_record->Header.Flags = *id | (ObjectTypeImageAttributes << 8);
attrs_record = &object_record->ObjectData.image_attributes;
attrs_record->Version = VERSION_MAGIC2;
attrs_record->Reserved1 = 0;
attrs_record->WrapMode = attrs->wrap;
attrs_record->ClampColor.Blue = attrs->outside_color & 0xff;
attrs_record->ClampColor.Green = (attrs->outside_color >> 8) & 0xff;
attrs_record->ClampColor.Red = (attrs->outside_color >> 16) & 0xff;
attrs_record->ClampColor.Alpha = attrs->outside_color >> 24;
attrs_record->ObjectClamp = attrs->clamp;
attrs_record->Reserved2 = 0;
return Ok;
}
GpStatus METAFILE_DrawImagePointsRect(GpMetafile *metafile, GpImage *image,
GDIPCONST GpPointF *points, INT count, REAL srcx, REAL srcy, REAL srcwidth,
REAL srcheight, GpUnit srcUnit, GDIPCONST GpImageAttributes* imageAttributes,
DrawImageAbort callback, VOID *callbackData)
{
EmfPlusDrawImagePoints *draw_image_record;
DWORD image_id, attributes_id;
GpStatus stat;
if (count != 3) return InvalidParameter;
if (metafile->metafile_type == MetafileTypeEmf)
{
FIXME("MetafileTypeEmf metafiles not supported\n");
return NotImplemented;
}
else
FIXME("semi-stub\n");
if (!imageAttributes)
{
stat = METAFILE_AddImageObject(metafile, image, &image_id);
}
else if (image->type == ImageTypeBitmap)
{
INT width = ((GpBitmap*)image)->width;
INT height = ((GpBitmap*)image)->height;
GpGraphics *graphics;
GpBitmap *bitmap;
stat = GdipCreateBitmapFromScan0(width, height,
0, PixelFormat32bppARGB, NULL, &bitmap);
if (stat != Ok) return stat;
stat = GdipGetImageGraphicsContext((GpImage*)bitmap, &graphics);
if (stat != Ok)
{
GdipDisposeImage((GpImage*)bitmap);
return stat;
}
stat = GdipDrawImageRectRectI(graphics, image, 0, 0, width, height,
0, 0, width, height, UnitPixel, imageAttributes, NULL, NULL);
GdipDeleteGraphics(graphics);
if (stat != Ok)
{
GdipDisposeImage((GpImage*)bitmap);
return stat;
}
stat = METAFILE_AddImageObject(metafile, (GpImage*)bitmap, &image_id);
GdipDisposeImage((GpImage*)bitmap);
}
else
{
FIXME("imageAttributes not supported (image type %d)\n", image->type);
return NotImplemented;
}
if (stat != Ok) return stat;
stat = METAFILE_AddImageAttributesObject(metafile, imageAttributes, &attributes_id);
if (stat != Ok) return stat;
stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusDrawImagePoints), (void**)&draw_image_record);
if (stat != Ok) return stat;
draw_image_record->Header.Type = EmfPlusRecordTypeDrawImagePoints;
draw_image_record->Header.Flags = image_id;
draw_image_record->ImageAttributesID = attributes_id;
draw_image_record->SrcUnit = UnitPixel;
draw_image_record->SrcRect.X = units_to_pixels(srcx, srcUnit, metafile->image.xres);
draw_image_record->SrcRect.Y = units_to_pixels(srcy, srcUnit, metafile->image.yres);
draw_image_record->SrcRect.Width = units_to_pixels(srcwidth, srcUnit, metafile->image.xres);
draw_image_record->SrcRect.Height = units_to_pixels(srcheight, srcUnit, metafile->image.yres);
draw_image_record->count = 3;
draw_image_record->PointData[0].pointF.X = points[0].X;
draw_image_record->PointData[0].pointF.Y = points[0].Y;
draw_image_record->PointData[1].pointF.X = points[1].X;
draw_image_record->PointData[1].pointF.Y = points[1].Y;
draw_image_record->PointData[2].pointF.X = points[2].X;
draw_image_record->PointData[2].pointF.Y = points[2].Y;
METAFILE_WriteRecords(metafile);
return Ok;
}
GpStatus METAFILE_AddSimpleProperty(GpMetafile *metafile, SHORT prop, SHORT val)
{
EmfPlusRecordHeader *record;
GpStatus stat;
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
return Ok;
stat = METAFILE_AllocateRecord(metafile, sizeof(*record), (void**)&record);
if (stat != Ok) return stat;
record->Type = prop;
record->Flags = val;
METAFILE_WriteRecords(metafile);
return Ok;
}
static GpStatus METAFILE_AddPathObject(GpMetafile *metafile, GpPath *path, DWORD *id)
{
EmfPlusObject *object_record;
GpStatus stat;
DWORD size;
*id = -1;
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
return Ok;
size = write_path_data(path, NULL);
stat = METAFILE_AllocateRecord(metafile,
FIELD_OFFSET(EmfPlusObject, ObjectData.path) + size,
(void**)&object_record);
if (stat != Ok) return stat;
*id = METAFILE_AddObjectId(metafile);
object_record->Header.Type = EmfPlusRecordTypeObject;
object_record->Header.Flags = *id | ObjectTypePath << 8;
write_path_data(path, &object_record->ObjectData.path);
return Ok;
}
static GpStatus METAFILE_PrepareBrushData(GpBrush *brush, DWORD *size)
{
if (brush->bt == BrushTypeSolidColor)
{
*size = FIELD_OFFSET(EmfPlusBrush, BrushData.solid) + sizeof(EmfPlusSolidBrushData);
return Ok;
}
FIXME("unsupported brush type: %d\n", brush->bt);
return NotImplemented;
}
static void METAFILE_FillBrushData(GpBrush *brush, EmfPlusBrush *data)
{
if (brush->bt == BrushTypeSolidColor)
{
GpSolidFill *solid = (GpSolidFill*)brush;
data->Version = VERSION_MAGIC2;
data->Type = solid->brush.bt;
data->BrushData.solid.SolidColor.Blue = solid->color & 0xff;
data->BrushData.solid.SolidColor.Green = (solid->color >> 8) & 0xff;
data->BrushData.solid.SolidColor.Red = (solid->color >> 16) & 0xff;
data->BrushData.solid.SolidColor.Alpha = solid->color >> 24;
}
}
static GpStatus METAFILE_AddPenObject(GpMetafile *metafile, GpPen *pen, DWORD *id)
{
DWORD i, data_flags, pen_data_size, brush_size;
EmfPlusObject *object_record;
EmfPlusPenData *pen_data;
GpStatus stat;
BOOL result;
*id = -1;
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
return Ok;
data_flags = 0;
pen_data_size = FIELD_OFFSET(EmfPlusPenData, OptionalData);
GdipIsMatrixIdentity(&pen->transform, &result);
if (!result)
{
data_flags |= PenDataTransform;
pen_data_size += sizeof(EmfPlusTransformMatrix);
}
if (pen->startcap != LineCapFlat)
{
data_flags |= PenDataStartCap;
pen_data_size += sizeof(DWORD);
}
if (pen->endcap != LineCapFlat)
{
data_flags |= PenDataEndCap;
pen_data_size += sizeof(DWORD);
}
if (pen->join != LineJoinMiter)
{
data_flags |= PenDataJoin;
pen_data_size += sizeof(DWORD);
}
if (pen->miterlimit != 10.0)
{
data_flags |= PenDataMiterLimit;
pen_data_size += sizeof(REAL);
}
if (pen->style != GP_DEFAULT_PENSTYLE)
{
data_flags |= PenDataLineStyle;
pen_data_size += sizeof(DWORD);
}
if (pen->dashcap != DashCapFlat)
{
data_flags |= PenDataDashedLineCap;
pen_data_size += sizeof(DWORD);
}
data_flags |= PenDataDashedLineOffset;
pen_data_size += sizeof(REAL);
if (pen->numdashes)
{
data_flags |= PenDataDashedLine;
pen_data_size += sizeof(DWORD) + pen->numdashes*sizeof(REAL);
}
if (pen->align != PenAlignmentCenter)
{
data_flags |= PenDataNonCenter;
pen_data_size += sizeof(DWORD);
}
/* TODO: Add support for PenDataCompoundLine */
if (pen->customstart)
{
FIXME("ignoring custom start cup\n");
}
if (pen->customend)
{
FIXME("ignoring custom end cup\n");
}
stat = METAFILE_PrepareBrushData(pen->brush, &brush_size);
if (stat != Ok) return stat;
stat = METAFILE_AllocateRecord(metafile,
FIELD_OFFSET(EmfPlusObject, ObjectData.pen.data) + pen_data_size + brush_size,
(void**)&object_record);
if (stat != Ok) return stat;
*id = METAFILE_AddObjectId(metafile);
object_record->Header.Type = EmfPlusRecordTypeObject;
object_record->Header.Flags = *id | ObjectTypePen << 8;
object_record->ObjectData.pen.Version = VERSION_MAGIC2;
object_record->ObjectData.pen.Type = 0;
pen_data = (EmfPlusPenData*)object_record->ObjectData.pen.data;
pen_data->PenDataFlags = data_flags;
pen_data->PenUnit = pen->unit;
pen_data->PenWidth = pen->width;
i = 0;
if (data_flags & PenDataTransform)
{
EmfPlusTransformMatrix *m = (EmfPlusTransformMatrix*)(pen_data->OptionalData + i);
memcpy(m, &pen->transform, sizeof(*m));
i += sizeof(EmfPlusTransformMatrix);
}
if (data_flags & PenDataStartCap)
{
*(DWORD*)(pen_data->OptionalData + i) = pen->startcap;
i += sizeof(DWORD);
}
if (data_flags & PenDataEndCap)
{
*(DWORD*)(pen_data->OptionalData + i) = pen->endcap;
i += sizeof(DWORD);
}
if (data_flags & PenDataJoin)
{
*(DWORD*)(pen_data->OptionalData + i) = pen->join;
i += sizeof(DWORD);
}
if (data_flags & PenDataMiterLimit)
{
*(REAL*)(pen_data->OptionalData + i) = pen->miterlimit;
i += sizeof(REAL);
}
if (data_flags & PenDataLineStyle)
{
switch (pen->style & PS_STYLE_MASK)
{
case PS_SOLID: *(DWORD*)(pen_data->OptionalData + i) = LineStyleSolid; break;
case PS_DASH: *(DWORD*)(pen_data->OptionalData + i) = LineStyleDash; break;
case PS_DOT: *(DWORD*)(pen_data->OptionalData + i) = LineStyleDot; break;
case PS_DASHDOT: *(DWORD*)(pen_data->OptionalData + i) = LineStyleDashDot; break;
case PS_DASHDOTDOT: *(DWORD*)(pen_data->OptionalData + i) = LineStyleDashDotDot; break;
default: *(DWORD*)(pen_data->OptionalData + i) = LineStyleCustom; break;
}
i += sizeof(DWORD);
}
if (data_flags & PenDataDashedLineCap)
{
*(DWORD*)(pen_data->OptionalData + i) = pen->dashcap;
i += sizeof(DWORD);
}
if (data_flags & PenDataDashedLineOffset)
{
*(REAL*)(pen_data->OptionalData + i) = pen->offset;
i += sizeof(REAL);
}
if (data_flags & PenDataDashedLine)
{
int j;
*(DWORD*)(pen_data->OptionalData + i) = pen->numdashes;
i += sizeof(DWORD);
for (j=0; j<pen->numdashes; j++)
{
*(REAL*)(pen_data->OptionalData + i) = pen->dashes[j];
i += sizeof(REAL);
}
}
if (data_flags & PenDataNonCenter)
{
*(REAL*)(pen_data->OptionalData + i) = pen->align;
i += sizeof(DWORD);
}
METAFILE_FillBrushData(pen->brush,
(EmfPlusBrush*)(object_record->ObjectData.pen.data + pen_data_size));
return Ok;
}
GpStatus METAFILE_DrawPath(GpMetafile *metafile, GpPen *pen, GpPath *path)
{
EmfPlusDrawPath *draw_path_record;
DWORD path_id;
DWORD pen_id;
GpStatus stat;
if (metafile->metafile_type == MetafileTypeEmf)
{
FIXME("stub!\n");
return NotImplemented;
}
stat = METAFILE_AddPenObject(metafile, pen, &pen_id);
if (stat != Ok) return stat;
stat = METAFILE_AddPathObject(metafile, path, &path_id);
if (stat != Ok) return stat;
stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusDrawPath), (void**)&draw_path_record);
if (stat != Ok) return stat;
draw_path_record->Header.Type = EmfPlusRecordTypeDrawPath;
draw_path_record->Header.Flags = path_id;
draw_path_record->PenId = pen_id;
METAFILE_WriteRecords(metafile);
return Ok;
}
static GpStatus METAFILE_AddBrushObject(GpMetafile *metafile, GpBrush *brush, DWORD *id)
{
EmfPlusObject *object_record;
GpStatus stat;
DWORD size;
*id = -1;
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
return Ok;
stat = METAFILE_PrepareBrushData(brush, &size);
if (stat != Ok) return stat;
stat = METAFILE_AllocateRecord(metafile,
FIELD_OFFSET(EmfPlusObject, ObjectData) + size, (void**)&object_record);
if (stat != Ok) return stat;
*id = METAFILE_AddObjectId(metafile);
object_record->Header.Type = EmfPlusRecordTypeObject;
object_record->Header.Flags = *id | ObjectTypeBrush << 8;
METAFILE_FillBrushData(brush, &object_record->ObjectData.brush);
return Ok;
}
GpStatus METAFILE_FillPath(GpMetafile *metafile, GpBrush *brush, GpPath *path)
{
EmfPlusFillPath *fill_path_record;
DWORD brush_id = -1, path_id;
BOOL inline_color;
GpStatus stat;
if (metafile->metafile_type == MetafileTypeEmf)
{
FIXME("stub!\n");
return NotImplemented;
}
inline_color = brush->bt == BrushTypeSolidColor;
if (!inline_color)
{
stat = METAFILE_AddBrushObject(metafile, brush, &brush_id);
if (stat != Ok) return stat;
}
stat = METAFILE_AddPathObject(metafile, path, &path_id);
if (stat != Ok) return stat;
stat = METAFILE_AllocateRecord(metafile,
sizeof(EmfPlusFillPath), (void**)&fill_path_record);
if (stat != Ok) return stat;
fill_path_record->Header.Type = EmfPlusRecordTypeFillPath;
if (inline_color)
{
fill_path_record->Header.Flags = 0x8000 | path_id;
fill_path_record->data.Color.Blue = ((GpSolidFill*)brush)->color & 0xff;
fill_path_record->data.Color.Green = (((GpSolidFill*)brush)->color >> 8) & 0xff;
fill_path_record->data.Color.Red = (((GpSolidFill*)brush)->color >> 16) & 0xff;
fill_path_record->data.Color.Alpha = ((GpSolidFill*)brush)->color >> 24;
}
else
{
fill_path_record->Header.Flags = path_id;
fill_path_record->data.BrushId = brush_id;
}
METAFILE_WriteRecords(metafile);
return Ok;
}