wine-wine/dlls/gdiplus/metafile.c

4627 lines
141 KiB
C

/*
* 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"
#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);
HRESULT WINAPI WICCreateImagingFactory_Proxy(UINT, IWICImagingFactory**);
typedef ARGB 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 EmfPlusDashedLineData
{
DWORD DashedLineDataSize;
BYTE data[1];
} EmfPlusDashedLineData;
typedef struct EmfPlusCompoundLineData
{
DWORD CompoundLineDataSize;
BYTE data[1];
} EmfPlusCompoundLineData;
typedef struct EmfPlusCustomStartCapData
{
DWORD CustomStartCapSize;
BYTE data[1];
} EmfPlusCustomStartCapData;
typedef struct EmfPlusCustomEndCapData
{
DWORD CustomEndCapSize;
BYTE data[1];
} EmfPlusCustomEndCapData;
typedef struct EmfPlusPenData
{
DWORD PenDataFlags;
DWORD PenUnit;
REAL PenWidth;
BYTE OptionalData[1];
} EmfPlusPenData;
enum BrushDataFlags
{
BrushDataPath = 1 << 0,
BrushDataTransform = 1 << 1,
BrushDataPresetColors = 1 << 2,
BrushDataBlendFactorsH = 1 << 3,
BrushDataBlendFactorsV = 1 << 4,
BrushDataFocusScales = 1 << 6,
BrushDataIsGammaCorrected = 1 << 7,
BrushDataDoNotTransform = 1 << 8,
};
typedef struct EmfPlusSolidBrushData
{
EmfPlusARGB SolidColor;
} EmfPlusSolidBrushData;
typedef struct EmfPlusHatchBrushData
{
DWORD HatchStyle;
EmfPlusARGB ForeColor;
EmfPlusARGB BackColor;
} EmfPlusHatchBrushData;
typedef struct EmfPlusTextureBrushData
{
DWORD BrushDataFlags;
INT WrapMode;
BYTE OptionalData[1];
} EmfPlusTextureBrushData;
typedef struct EmfPlusRectF
{
float X;
float Y;
float Width;
float Height;
} EmfPlusRectF;
typedef struct EmfPlusLinearGradientBrushData
{
DWORD BrushDataFlags;
INT WrapMode;
EmfPlusRectF RectF;
EmfPlusARGB StartColor;
EmfPlusARGB EndColor;
DWORD Reserved1;
DWORD Reserved2;
BYTE OptionalData[1];
} EmfPlusLinearGradientBrushData;
typedef struct EmfPlusBrush
{
DWORD Version;
DWORD Type;
union {
EmfPlusSolidBrushData solid;
EmfPlusHatchBrushData hatch;
EmfPlusTextureBrushData texture;
EmfPlusLinearGradientBrushData lineargradient;
} 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 EmfPlusRegionNodePath
{
DWORD RegionNodePathLength;
EmfPlusPath RegionNodePath;
} EmfPlusRegionNodePath;
typedef struct EmfPlusRegion
{
DWORD Version;
DWORD RegionNodeCount;
BYTE RegionNode[1];
} EmfPlusRegion;
typedef struct EmfPlusPalette
{
DWORD PaletteStyleFlags;
DWORD PaletteCount;
BYTE PaletteEntries[1];
} EmfPlusPalette;
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 struct EmfPlusObject
{
EmfPlusRecordHeader Header;
union
{
EmfPlusBrush brush;
EmfPlusPen pen;
EmfPlusPath path;
EmfPlusRegion region;
EmfPlusImage image;
EmfPlusImageAttributes image_attributes;
} ObjectData;
} EmfPlusObject;
typedef struct EmfPlusPointR7
{
BYTE X;
BYTE Y;
} EmfPlusPointR7;
typedef struct EmfPlusPoint
{
short X;
short Y;
} EmfPlusPoint;
typedef struct EmfPlusPointF
{
float X;
float Y;
} EmfPlusPointF;
typedef struct EmfPlusDrawImage
{
EmfPlusRecordHeader Header;
DWORD ImageAttributesID;
DWORD SrcUnit;
EmfPlusRectF SrcRect;
union
{
EmfPlusRect rect;
EmfPlusRectF rectF;
} RectData;
} EmfPlusDrawImage;
typedef struct EmfPlusDrawImagePoints
{
EmfPlusRecordHeader Header;
DWORD ImageAttributesID;
DWORD SrcUnit;
EmfPlusRectF SrcRect;
DWORD count;
union
{
EmfPlusPointR7 pointsR[3];
EmfPlusPoint points[3];
EmfPlusPointF pointsF[3];
} PointData;
} EmfPlusDrawImagePoints;
typedef struct EmfPlusDrawPath
{
EmfPlusRecordHeader Header;
DWORD PenId;
} EmfPlusDrawPath;
typedef struct EmfPlusDrawArc
{
EmfPlusRecordHeader Header;
float StartAngle;
float SweepAngle;
union
{
EmfPlusRect rect;
EmfPlusRectF rectF;
} RectData;
} EmfPlusDrawArc;
typedef struct EmfPlusDrawEllipse
{
EmfPlusRecordHeader Header;
union
{
EmfPlusRect rect;
EmfPlusRectF rectF;
} RectData;
} EmfPlusDrawEllipse;
typedef struct EmfPlusDrawPie
{
EmfPlusRecordHeader Header;
float StartAngle;
float SweepAngle;
union
{
EmfPlusRect rect;
EmfPlusRectF rectF;
} RectData;
} EmfPlusDrawPie;
typedef struct EmfPlusDrawRects
{
EmfPlusRecordHeader Header;
DWORD Count;
union
{
EmfPlusRect rect[1];
EmfPlusRectF rectF[1];
} RectData;
} EmfPlusDrawRects;
typedef struct EmfPlusFillPath
{
EmfPlusRecordHeader Header;
union
{
DWORD BrushId;
EmfPlusARGB Color;
} data;
} EmfPlusFillPath;
typedef struct EmfPlusFillClosedCurve
{
EmfPlusRecordHeader Header;
DWORD BrushId;
float Tension;
DWORD Count;
union
{
EmfPlusPointR7 pointsR[1];
EmfPlusPoint points[1];
EmfPlusPointF pointsF[1];
} PointData;
} EmfPlusFillClosedCurve;
typedef struct EmfPlusFillEllipse
{
EmfPlusRecordHeader Header;
DWORD BrushId;
union
{
EmfPlusRect rect;
EmfPlusRectF rectF;
} RectData;
} EmfPlusFillEllipse;
typedef struct EmfPlusFillPie
{
EmfPlusRecordHeader Header;
DWORD BrushId;
float StartAngle;
float SweepAngle;
union
{
EmfPlusRect rect;
EmfPlusRectF rectF;
} RectData;
} EmfPlusFillPie;
typedef struct EmfPlusFont
{
DWORD Version;
float EmSize;
DWORD SizeUnit;
DWORD FontStyleFlags;
DWORD Reserved;
DWORD Length;
WCHAR FamilyName[1];
} EmfPlusFont;
static void metafile_free_object_table_entry(GpMetafile *metafile, BYTE id)
{
struct emfplus_object *object = &metafile->objtable[id];
switch (object->type)
{
case ObjectTypeInvalid:
break;
case ObjectTypeBrush:
GdipDeleteBrush(object->u.brush);
break;
case ObjectTypePen:
GdipDeletePen(object->u.pen);
break;
case ObjectTypePath:
GdipDeletePath(object->u.path);
break;
case ObjectTypeRegion:
GdipDeleteRegion(object->u.region);
break;
case ObjectTypeImage:
GdipDisposeImage(object->u.image);
break;
case ObjectTypeFont:
GdipDeleteFont(object->u.font);
break;
case ObjectTypeImageAttributes:
GdipDisposeImageAttributes(object->u.image_attributes);
break;
default:
FIXME("not implemented for object type %u.\n", object->type);
return;
}
object->type = ObjectTypeInvalid;
object->u.object = NULL;
}
void METAFILE_Free(GpMetafile *metafile)
{
unsigned int i;
heap_free(metafile->comment_data);
DeleteEnhMetaFile(CloseEnhMetaFile(metafile->record_dc));
if (!metafile->preserve_hemf)
DeleteEnhMetaFile(metafile->hemf);
if (metafile->record_graphics)
{
WARN("metafile closed while recording\n");
/* not sure what to do here; for now just prevent the graphics from functioning or using this object */
metafile->record_graphics->image = NULL;
metafile->record_graphics->busy = TRUE;
}
if (metafile->record_stream)
IStream_Release(metafile->record_stream);
for (i = 0; i < ARRAY_SIZE(metafile->objtable); i++)
metafile_free_object_table_entry(metafile, i);
}
static DWORD METAFILE_AddObjectId(GpMetafile *metafile)
{
return (metafile->next_object_id++) % EmfPlusObjectTableSize;
}
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 %s %d %p %p)\n", hdc, type, debugstr_rectf(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)->limit_dpi = 96;
(*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 GdipRecordMetafileStreamI(IStream *stream, HDC hdc, EmfType type, GDIPCONST GpRect *frameRect,
MetafileFrameUnit frameUnit, GDIPCONST WCHAR *desc, GpMetafile **metafile)
{
GpRectF frameRectF, *pFrameRectF;
TRACE("(%p %p %d %p %d %p %p)\n", stream, 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 GdipRecordMetafileStream(stream, 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 %s %d %p %p)\n", stream, hdc, type, debugstr_rectf(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;
}
static GpStatus METAFILE_PrepareBrushData(GpBrush *brush, DWORD *size)
{
switch (brush->bt)
{
case BrushTypeSolidColor:
*size = FIELD_OFFSET(EmfPlusBrush, BrushData) + sizeof(EmfPlusSolidBrushData);
break;
case BrushTypeHatchFill:
*size = FIELD_OFFSET(EmfPlusBrush, BrushData) + sizeof(EmfPlusHatchBrushData);
break;
default:
FIXME("unsupported brush type: %d\n", brush->bt);
return NotImplemented;
}
return Ok;
}
static void METAFILE_FillBrushData(GpBrush *brush, EmfPlusBrush *data)
{
data->Version = VERSION_MAGIC2;
data->Type = brush->bt;
switch (brush->bt)
{
case BrushTypeSolidColor:
{
GpSolidFill *solid = (GpSolidFill *)brush;
data->BrushData.solid.SolidColor = solid->color;
break;
}
case BrushTypeHatchFill:
{
GpHatch *hatch = (GpHatch *)brush;
data->BrushData.hatch.HatchStyle = hatch->hatchstyle;
data->BrushData.hatch.ForeColor = hatch->forecol;
data->BrushData.hatch.BackColor = hatch->backcol;
break;
}
default:
FIXME("unsupported brush type: %d\n", brush->bt);
}
}
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_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
{
stat = METAFILE_AddBrushObject(metafile, brush, &brushid);
if (stat != Ok)
return stat;
}
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;
}
static void metafile_set_object_table_entry(GpMetafile *metafile, BYTE id, BYTE type, void *object)
{
metafile_free_object_table_entry(metafile, id);
metafile->objtable[id].type = type;
metafile->objtable[id].u.object = object;
}
static GpStatus metafile_deserialize_image(const BYTE *record_data, UINT data_size, GpImage **image)
{
EmfPlusImage *data = (EmfPlusImage *)record_data;
GpStatus status;
*image = NULL;
if (data_size < FIELD_OFFSET(EmfPlusImage, ImageData))
return InvalidParameter;
data_size -= FIELD_OFFSET(EmfPlusImage, ImageData);
switch (data->Type)
{
case ImageDataTypeBitmap:
{
EmfPlusBitmap *bitmapdata = &data->ImageData.bitmap;
if (data_size <= FIELD_OFFSET(EmfPlusBitmap, BitmapData))
return InvalidParameter;
data_size -= FIELD_OFFSET(EmfPlusBitmap, BitmapData);
switch (bitmapdata->Type)
{
case BitmapDataTypePixel:
{
ColorPalette *palette;
BYTE *scan0;
if (bitmapdata->PixelFormat & PixelFormatIndexed)
{
EmfPlusPalette *palette_obj = (EmfPlusPalette *)bitmapdata->BitmapData;
UINT palette_size = FIELD_OFFSET(EmfPlusPalette, PaletteEntries);
if (data_size <= palette_size)
return InvalidParameter;
palette_size += palette_obj->PaletteCount * sizeof(EmfPlusARGB);
if (data_size < palette_size)
return InvalidParameter;
data_size -= palette_size;
palette = (ColorPalette *)bitmapdata->BitmapData;
scan0 = (BYTE *)bitmapdata->BitmapData + palette_size;
}
else
{
palette = NULL;
scan0 = bitmapdata->BitmapData;
}
if (data_size < bitmapdata->Height * bitmapdata->Stride)
return InvalidParameter;
status = GdipCreateBitmapFromScan0(bitmapdata->Width, bitmapdata->Height, bitmapdata->Stride,
bitmapdata->PixelFormat, scan0, (GpBitmap **)image);
if (status == Ok && palette)
{
status = GdipSetImagePalette(*image, palette);
if (status != Ok)
{
GdipDisposeImage(*image);
*image = NULL;
}
}
break;
}
case BitmapDataTypeCompressed:
{
IWICImagingFactory *factory;
IWICStream *stream;
HRESULT hr;
if (WICCreateImagingFactory_Proxy(WINCODEC_SDK_VERSION, &factory) != S_OK)
return GenericError;
hr = IWICImagingFactory_CreateStream(factory, &stream);
IWICImagingFactory_Release(factory);
if (hr != S_OK)
return GenericError;
if (IWICStream_InitializeFromMemory(stream, bitmapdata->BitmapData, data_size) == S_OK)
status = GdipCreateBitmapFromStream((IStream *)stream, (GpBitmap **)image);
else
status = GenericError;
IWICStream_Release(stream);
break;
}
default:
WARN("Invalid bitmap type %d.\n", bitmapdata->Type);
return InvalidParameter;
}
break;
}
case ImageDataTypeMetafile:
{
EmfPlusMetafile *metafiledata = &data->ImageData.metafile;
if (data_size <= FIELD_OFFSET(EmfPlusMetafile, MetafileData))
return InvalidParameter;
data_size -= FIELD_OFFSET(EmfPlusMetafile, MetafileData);
switch (metafiledata->Type) {
case MetafileTypeEmf:
case MetafileTypeEmfPlusOnly:
case MetafileTypeEmfPlusDual:
{
HENHMETAFILE hemf;
hemf = SetEnhMetaFileBits(data_size, metafiledata->MetafileData);
if (!hemf)
return GenericError;
status = GdipCreateMetafileFromEmf(hemf, TRUE, (GpMetafile**)image);
if (status != Ok)
DeleteEnhMetaFile(hemf);
break;
}
default:
FIXME("metafile type %d not supported.\n", metafiledata->Type);
return NotImplemented;
}
break;
}
default:
FIXME("image type %d not supported.\n", data->Type);
return NotImplemented;
}
return status;
}
static GpStatus metafile_deserialize_path(const BYTE *record_data, UINT data_size, GpPath **path)
{
EmfPlusPath *data = (EmfPlusPath *)record_data;
GpStatus status;
BYTE *types;
UINT size;
DWORD i;
*path = NULL;
if (data_size <= FIELD_OFFSET(EmfPlusPath, data))
return InvalidParameter;
data_size -= FIELD_OFFSET(EmfPlusPath, data);
if (data->PathPointFlags & 0x800) /* R */
{
FIXME("RLE encoded path data is not supported.\n");
return NotImplemented;
}
else
{
if (data->PathPointFlags & 0x4000) /* C */
size = sizeof(EmfPlusPoint);
else
size = sizeof(EmfPlusPointF);
size += sizeof(BYTE); /* EmfPlusPathPointType */
size *= data->PathPointCount;
}
if (data_size < size)
return InvalidParameter;
status = GdipCreatePath(FillModeAlternate, path);
if (status != Ok)
return status;
(*path)->pathdata.Count = data->PathPointCount;
(*path)->pathdata.Points = GdipAlloc(data->PathPointCount * sizeof(*(*path)->pathdata.Points));
(*path)->pathdata.Types = GdipAlloc(data->PathPointCount * sizeof(*(*path)->pathdata.Types));
(*path)->datalen = (*path)->pathdata.Count;
if (!(*path)->pathdata.Points || !(*path)->pathdata.Types)
{
GdipDeletePath(*path);
return OutOfMemory;
}
if (data->PathPointFlags & 0x4000) /* C */
{
EmfPlusPoint *points = (EmfPlusPoint *)data->data;
for (i = 0; i < data->PathPointCount; i++)
{
(*path)->pathdata.Points[i].X = points[i].X;
(*path)->pathdata.Points[i].Y = points[i].Y;
}
types = (BYTE *)(points + i);
}
else
{
EmfPlusPointF *points = (EmfPlusPointF *)data->data;
memcpy((*path)->pathdata.Points, points, sizeof(*points) * data->PathPointCount);
types = (BYTE *)(points + data->PathPointCount);
}
memcpy((*path)->pathdata.Types, types, sizeof(*types) * data->PathPointCount);
return Ok;
}
static GpStatus metafile_read_region_node(struct memory_buffer *mbuf, GpRegion *region, region_element *node, UINT *count)
{
const DWORD *type;
GpStatus status;
type = buffer_read(mbuf, sizeof(*type));
if (!type) return Ok;
node->type = *type;
switch (node->type)
{
case CombineModeReplace:
case CombineModeIntersect:
case CombineModeUnion:
case CombineModeXor:
case CombineModeExclude:
case CombineModeComplement:
{
region_element *left, *right;
left = heap_alloc_zero(sizeof(*left));
if (!left)
return OutOfMemory;
right = heap_alloc_zero(sizeof(*right));
if (!right)
{
heap_free(left);
return OutOfMemory;
}
status = metafile_read_region_node(mbuf, region, left, count);
if (status == Ok)
{
status = metafile_read_region_node(mbuf, region, right, count);
if (status == Ok)
{
node->elementdata.combine.left = left;
node->elementdata.combine.right = right;
region->num_children += 2;
return Ok;
}
}
heap_free(left);
heap_free(right);
return status;
}
case RegionDataRect:
{
const EmfPlusRectF *rect;
rect = buffer_read(mbuf, sizeof(*rect));
if (!rect)
return InvalidParameter;
memcpy(&node->elementdata.rect, rect, sizeof(*rect));
*count += 1;
return Ok;
}
case RegionDataPath:
{
const BYTE *path_data;
const UINT *data_size;
GpPath *path;
data_size = buffer_read(mbuf, FIELD_OFFSET(EmfPlusRegionNodePath, RegionNodePath));
if (!data_size)
return InvalidParameter;
path_data = buffer_read(mbuf, *data_size);
if (!path_data)
return InvalidParameter;
status = metafile_deserialize_path(path_data, *data_size, &path);
if (status == Ok)
{
node->elementdata.path = path;
*count += 1;
}
return Ok;
}
case RegionDataEmptyRect:
case RegionDataInfiniteRect:
*count += 1;
return Ok;
default:
FIXME("element type %#x is not supported\n", *type);
break;
}
return InvalidParameter;
}
static GpStatus metafile_deserialize_region(const BYTE *record_data, UINT data_size, GpRegion **region)
{
struct memory_buffer mbuf;
GpStatus status;
UINT count;
*region = NULL;
init_memory_buffer(&mbuf, record_data, data_size);
if (!buffer_read(&mbuf, FIELD_OFFSET(EmfPlusRegion, RegionNode)))
return InvalidParameter;
status = GdipCreateRegion(region);
if (status != Ok)
return status;
count = 0;
status = metafile_read_region_node(&mbuf, *region, &(*region)->node, &count);
if (status == Ok && !count)
status = InvalidParameter;
if (status != Ok)
{
GdipDeleteRegion(*region);
*region = NULL;
}
return status;
}
static GpStatus metafile_deserialize_brush(const BYTE *record_data, UINT data_size, GpBrush **brush)
{
static const UINT header_size = FIELD_OFFSET(EmfPlusBrush, BrushData);
EmfPlusBrush *data = (EmfPlusBrush *)record_data;
EmfPlusTransformMatrix *transform = NULL;
DWORD brushflags;
GpStatus status;
UINT offset;
*brush = NULL;
if (data_size < header_size)
return InvalidParameter;
switch (data->Type)
{
case BrushTypeSolidColor:
if (data_size != header_size + sizeof(EmfPlusSolidBrushData))
return InvalidParameter;
status = GdipCreateSolidFill(data->BrushData.solid.SolidColor, (GpSolidFill **)brush);
break;
case BrushTypeHatchFill:
if (data_size != header_size + sizeof(EmfPlusHatchBrushData))
return InvalidParameter;
status = GdipCreateHatchBrush(data->BrushData.hatch.HatchStyle, data->BrushData.hatch.ForeColor,
data->BrushData.hatch.BackColor, (GpHatch **)brush);
break;
case BrushTypeTextureFill:
{
GpImage *image;
offset = header_size + FIELD_OFFSET(EmfPlusTextureBrushData, OptionalData);
if (data_size <= offset)
return InvalidParameter;
brushflags = data->BrushData.texture.BrushDataFlags;
if (brushflags & BrushDataTransform)
{
if (data_size <= offset + sizeof(EmfPlusTransformMatrix))
return InvalidParameter;
transform = (EmfPlusTransformMatrix *)(record_data + offset);
offset += sizeof(EmfPlusTransformMatrix);
}
status = metafile_deserialize_image(record_data + offset, data_size - offset, &image);
if (status != Ok)
return status;
status = GdipCreateTexture(image, data->BrushData.texture.WrapMode, (GpTexture **)brush);
if (status == Ok && transform && !(brushflags & BrushDataDoNotTransform))
GdipSetTextureTransform((GpTexture *)*brush, (const GpMatrix *)transform);
GdipDisposeImage(image);
break;
}
case BrushTypeLinearGradient:
{
GpLineGradient *gradient = NULL;
GpPointF startpoint, endpoint;
UINT position_count = 0;
offset = header_size + FIELD_OFFSET(EmfPlusLinearGradientBrushData, OptionalData);
if (data_size <= offset)
return InvalidParameter;
brushflags = data->BrushData.lineargradient.BrushDataFlags;
if ((brushflags & BrushDataPresetColors) && (brushflags & (BrushDataBlendFactorsH | BrushDataBlendFactorsV)))
return InvalidParameter;
if (brushflags & BrushDataTransform)
{
if (data_size <= offset + sizeof(EmfPlusTransformMatrix))
return InvalidParameter;
transform = (EmfPlusTransformMatrix *)(record_data + offset);
offset += sizeof(EmfPlusTransformMatrix);
}
if (brushflags & (BrushDataPresetColors | BrushDataBlendFactorsH | BrushDataBlendFactorsV))
{
if (data_size <= offset + sizeof(DWORD)) /* Number of factors/preset colors. */
return InvalidParameter;
position_count = *(DWORD *)(record_data + offset);
offset += sizeof(DWORD);
}
if (brushflags & BrushDataPresetColors)
{
if (data_size != offset + position_count * (sizeof(float) + sizeof(EmfPlusARGB)))
return InvalidParameter;
}
else if (brushflags & BrushDataBlendFactorsH)
{
if (data_size != offset + position_count * 2 * sizeof(float))
return InvalidParameter;
}
startpoint.X = data->BrushData.lineargradient.RectF.X;
startpoint.Y = data->BrushData.lineargradient.RectF.Y;
endpoint.X = startpoint.X + data->BrushData.lineargradient.RectF.Width;
endpoint.Y = startpoint.Y + data->BrushData.lineargradient.RectF.Height;
status = GdipCreateLineBrush(&startpoint, &endpoint, data->BrushData.lineargradient.StartColor,
data->BrushData.lineargradient.EndColor, data->BrushData.lineargradient.WrapMode, &gradient);
if (status == Ok)
{
if (transform)
status = GdipSetLineTransform(gradient, (const GpMatrix *)transform);
if (status == Ok)
{
if (brushflags & BrushDataPresetColors)
status = GdipSetLinePresetBlend(gradient, (ARGB *)(record_data + offset +
position_count * sizeof(REAL)), (REAL *)(record_data + offset), position_count);
else if (brushflags & BrushDataBlendFactorsH)
status = GdipSetLineBlend(gradient, (REAL *)(record_data + offset + position_count * sizeof(REAL)),
(REAL *)(record_data + offset), position_count);
if (brushflags & BrushDataIsGammaCorrected)
FIXME("BrushDataIsGammaCorrected is not handled.\n");
}
}
if (status == Ok)
*brush = (GpBrush *)gradient;
else
GdipDeleteBrush((GpBrush *)gradient);
break;
}
default:
FIXME("brush type %u is not supported.\n", data->Type);
return NotImplemented;
}
return status;
}
static GpStatus metafile_get_pen_brush_data_offset(EmfPlusPen *data, UINT data_size, DWORD *ret)
{
EmfPlusPenData *pendata = (EmfPlusPenData *)data->data;
DWORD offset = FIELD_OFFSET(EmfPlusPen, data);
if (data_size <= offset)
return InvalidParameter;
offset += FIELD_OFFSET(EmfPlusPenData, OptionalData);
if (data_size <= offset)
return InvalidParameter;
if (pendata->PenDataFlags & PenDataTransform)
offset += sizeof(EmfPlusTransformMatrix);
if (pendata->PenDataFlags & PenDataStartCap)
offset += sizeof(DWORD);
if (pendata->PenDataFlags & PenDataEndCap)
offset += sizeof(DWORD);
if (pendata->PenDataFlags & PenDataJoin)
offset += sizeof(DWORD);
if (pendata->PenDataFlags & PenDataMiterLimit)
offset += sizeof(REAL);
if (pendata->PenDataFlags & PenDataLineStyle)
offset += sizeof(DWORD);
if (pendata->PenDataFlags & PenDataDashedLineCap)
offset += sizeof(DWORD);
if (pendata->PenDataFlags & PenDataDashedLineOffset)
offset += sizeof(REAL);
if (pendata->PenDataFlags & PenDataDashedLine)
{
EmfPlusDashedLineData *dashedline = (EmfPlusDashedLineData *)((BYTE *)data + offset);
offset += FIELD_OFFSET(EmfPlusDashedLineData, data);
if (data_size <= offset)
return InvalidParameter;
offset += dashedline->DashedLineDataSize * sizeof(float);
}
if (pendata->PenDataFlags & PenDataNonCenter)
offset += sizeof(DWORD);
if (pendata->PenDataFlags & PenDataCompoundLine)
{
EmfPlusCompoundLineData *compoundline = (EmfPlusCompoundLineData *)((BYTE *)data + offset);
offset += FIELD_OFFSET(EmfPlusCompoundLineData, data);
if (data_size <= offset)
return InvalidParameter;
offset += compoundline->CompoundLineDataSize * sizeof(float);
}
if (pendata->PenDataFlags & PenDataCustomStartCap)
{
EmfPlusCustomStartCapData *startcap = (EmfPlusCustomStartCapData *)((BYTE *)data + offset);
offset += FIELD_OFFSET(EmfPlusCustomStartCapData, data);
if (data_size <= offset)
return InvalidParameter;
offset += startcap->CustomStartCapSize;
}
if (pendata->PenDataFlags & PenDataCustomEndCap)
{
EmfPlusCustomEndCapData *endcap = (EmfPlusCustomEndCapData *)((BYTE *)data + offset);
offset += FIELD_OFFSET(EmfPlusCustomEndCapData, data);
if (data_size <= offset)
return InvalidParameter;
offset += endcap->CustomEndCapSize;
}
*ret = offset;
return Ok;
}
static GpStatus METAFILE_PlaybackObject(GpMetafile *metafile, UINT flags, UINT data_size, const BYTE *record_data)
{
BYTE type = (flags >> 8) & 0xff;
BYTE id = flags & 0xff;
void *object = NULL;
GpStatus status;
if (type > ObjectTypeMax || id >= EmfPlusObjectTableSize)
return InvalidParameter;
switch (type)
{
case ObjectTypeBrush:
status = metafile_deserialize_brush(record_data, data_size, (GpBrush **)&object);
break;
case ObjectTypePen:
{
EmfPlusPen *data = (EmfPlusPen *)record_data;
EmfPlusPenData *pendata = (EmfPlusPenData *)data->data;
GpBrush *brush;
DWORD offset;
GpPen *pen;
status = metafile_get_pen_brush_data_offset(data, data_size, &offset);
if (status != Ok)
return status;
status = metafile_deserialize_brush(record_data + offset, data_size - offset, &brush);
if (status != Ok)
return status;
status = GdipCreatePen2(brush, pendata->PenWidth, pendata->PenUnit, &pen);
GdipDeleteBrush(brush);
if (status != Ok)
return status;
offset = FIELD_OFFSET(EmfPlusPenData, OptionalData);
if (pendata->PenDataFlags & PenDataTransform)
{
FIXME("PenDataTransform is not supported.\n");
offset += sizeof(EmfPlusTransformMatrix);
}
if (pendata->PenDataFlags & PenDataStartCap)
{
if ((status = GdipSetPenStartCap(pen, *(DWORD *)((BYTE *)pendata + offset))) != Ok)
goto penfailed;
offset += sizeof(DWORD);
}
if (pendata->PenDataFlags & PenDataEndCap)
{
if ((status = GdipSetPenEndCap(pen, *(DWORD *)((BYTE *)pendata + offset))) != Ok)
goto penfailed;
offset += sizeof(DWORD);
}
if (pendata->PenDataFlags & PenDataJoin)
{
if ((status = GdipSetPenLineJoin(pen, *(DWORD *)((BYTE *)pendata + offset))) != Ok)
goto penfailed;
offset += sizeof(DWORD);
}
if (pendata->PenDataFlags & PenDataMiterLimit)
{
if ((status = GdipSetPenMiterLimit(pen, *(REAL *)((BYTE *)pendata + offset))) != Ok)
goto penfailed;
offset += sizeof(REAL);
}
if (pendata->PenDataFlags & PenDataLineStyle)
{
if ((status = GdipSetPenDashStyle(pen, *(DWORD *)((BYTE *)pendata + offset))) != Ok)
goto penfailed;
offset += sizeof(DWORD);
}
if (pendata->PenDataFlags & PenDataDashedLineCap)
{
FIXME("PenDataDashedLineCap is not supported.\n");
offset += sizeof(DWORD);
}
if (pendata->PenDataFlags & PenDataDashedLineOffset)
{
if ((status = GdipSetPenDashOffset(pen, *(REAL *)((BYTE *)pendata + offset))) != Ok)
goto penfailed;
offset += sizeof(REAL);
}
if (pendata->PenDataFlags & PenDataDashedLine)
{
EmfPlusDashedLineData *dashedline = (EmfPlusDashedLineData *)((BYTE *)pendata + offset);
FIXME("PenDataDashedLine is not supported.\n");
offset += FIELD_OFFSET(EmfPlusDashedLineData, data) + dashedline->DashedLineDataSize * sizeof(float);
}
if (pendata->PenDataFlags & PenDataNonCenter)
{
FIXME("PenDataNonCenter is not supported.\n");
offset += sizeof(DWORD);
}
if (pendata->PenDataFlags & PenDataCompoundLine)
{
EmfPlusCompoundLineData *compoundline = (EmfPlusCompoundLineData *)((BYTE *)pendata + offset);
FIXME("PenDataCompoundLine is not supported.\n");
offset += FIELD_OFFSET(EmfPlusCompoundLineData, data) + compoundline->CompoundLineDataSize * sizeof(float);
}
if (pendata->PenDataFlags & PenDataCustomStartCap)
{
EmfPlusCustomStartCapData *startcap = (EmfPlusCustomStartCapData *)((BYTE *)pendata + offset);
FIXME("PenDataCustomStartCap is not supported.\n");
offset += FIELD_OFFSET(EmfPlusCustomStartCapData, data) + startcap->CustomStartCapSize;
}
if (pendata->PenDataFlags & PenDataCustomEndCap)
{
EmfPlusCustomEndCapData *endcap = (EmfPlusCustomEndCapData *)((BYTE *)pendata + offset);
FIXME("PenDataCustomEndCap is not supported.\n");
offset += FIELD_OFFSET(EmfPlusCustomEndCapData, data) + endcap->CustomEndCapSize;
}
object = pen;
break;
penfailed:
GdipDeletePen(pen);
return status;
}
case ObjectTypePath:
status = metafile_deserialize_path(record_data, data_size, (GpPath **)&object);
break;
case ObjectTypeRegion:
status = metafile_deserialize_region(record_data, data_size, (GpRegion **)&object);
break;
case ObjectTypeImage:
status = metafile_deserialize_image(record_data, data_size, (GpImage **)&object);
break;
case ObjectTypeFont:
{
EmfPlusFont *data = (EmfPlusFont *)record_data;
GpFontFamily *family;
WCHAR *familyname;
if (data_size <= FIELD_OFFSET(EmfPlusFont, FamilyName))
return InvalidParameter;
data_size -= FIELD_OFFSET(EmfPlusFont, FamilyName);
if (data_size < data->Length * sizeof(WCHAR))
return InvalidParameter;
if (!(familyname = GdipAlloc((data->Length + 1) * sizeof(*familyname))))
return OutOfMemory;
memcpy(familyname, data->FamilyName, data->Length * sizeof(*familyname));
familyname[data->Length] = 0;
status = GdipCreateFontFamilyFromName(familyname, NULL, &family);
GdipFree(familyname);
if (status != Ok)
return InvalidParameter;
status = GdipCreateFont(family, data->EmSize, data->FontStyleFlags, data->SizeUnit, (GpFont **)&object);
GdipDeleteFontFamily(family);
break;
}
case ObjectTypeImageAttributes:
{
EmfPlusImageAttributes *data = (EmfPlusImageAttributes *)record_data;
GpImageAttributes *attributes = NULL;
if (data_size != sizeof(*data))
return InvalidParameter;
if ((status = GdipCreateImageAttributes(&attributes)) != Ok)
return status;
status = GdipSetImageAttributesWrapMode(attributes, data->WrapMode, *(DWORD *)&data->ClampColor,
!!data->ObjectClamp);
if (status == Ok)
object = attributes;
else
GdipDisposeImageAttributes(attributes);
break;
}
default:
FIXME("not implemented for object type %d.\n", type);
return NotImplemented;
}
if (status == Ok)
metafile_set_object_table_entry(metafile, id, type, object);
return status;
}
static GpStatus metafile_set_clip_region(GpMetafile *metafile, GpRegion *region, CombineMode mode)
{
GpMatrix world_to_device;
get_graphics_transform(metafile->playback_graphics, CoordinateSpaceDevice, CoordinateSpaceWorld, &world_to_device);
GdipTransformRegion(region, &world_to_device);
GdipCombineRegionRegion(metafile->clip, region, mode);
return METAFILE_PlaybackUpdateClip(metafile);
}
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;
if (dataSize != sizeof(record->Color))
return InvalidParameter;
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(record->BrushID, (GpSolidFill **)&temp_brush);
brush = temp_brush;
}
else
{
if (record->BrushID >= EmfPlusObjectTableSize ||
real_metafile->objtable[record->BrushID].type != ObjectTypeBrush)
return InvalidParameter;
brush = real_metafile->objtable[record->BrushID].u.brush;
stat = Ok;
}
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;
if (dataSize + sizeof(EmfPlusRecordHeader) < sizeof(*record))
return InvalidParameter;
stat = GdipCreateRegionRect(&record->ClipRect, &region);
if (stat == Ok)
{
stat = metafile_set_clip_region(real_metafile, region, mode);
GdipDeleteRegion(region);
}
return stat;
}
case EmfPlusRecordTypeSetClipRegion:
{
CombineMode mode = (flags >> 8) & 0xf;
BYTE regionid = flags & 0xff;
GpRegion *region;
if (dataSize != 0)
return InvalidParameter;
if (regionid >= EmfPlusObjectTableSize || real_metafile->objtable[regionid].type != ObjectTypeRegion)
return InvalidParameter;
stat = GdipCloneRegion(real_metafile->objtable[regionid].u.region, &region);
if (stat == Ok)
{
stat = metafile_set_clip_region(real_metafile, region, mode);
GdipDeleteRegion(region);
}
return stat;
}
case EmfPlusRecordTypeSetClipPath:
{
CombineMode mode = (flags >> 8) & 0xf;
BYTE pathid = flags & 0xff;
GpRegion *region;
if (dataSize != 0)
return InvalidParameter;
if (pathid >= EmfPlusObjectTableSize || real_metafile->objtable[pathid].type != ObjectTypePath)
return InvalidParameter;
stat = GdipCreateRegionPath(real_metafile->objtable[pathid].u.path, &region);
if (stat == Ok)
{
stat = metafile_set_clip_region(real_metafile, region, mode);
GdipDeleteRegion(region);
}
return stat;
}
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 & 0xff);
}
case EmfPlusRecordTypeSetCompositingQuality:
{
return GdipSetCompositingQuality(real_metafile->playback_graphics, flags & 0xff);
}
case EmfPlusRecordTypeSetInterpolationMode:
{
return GdipSetInterpolationMode(real_metafile->playback_graphics, flags & 0xff);
}
case EmfPlusRecordTypeSetTextRenderingHint:
{
return GdipSetTextRenderingHint(real_metafile->playback_graphics, flags & 0xff);
}
case EmfPlusRecordTypeSetAntiAliasMode:
{
return GdipSetSmoothingMode(real_metafile->playback_graphics, (flags >> 1) & 0xff);
}
case EmfPlusRecordTypeSetCompositingMode:
{
return GdipSetCompositingMode(real_metafile->playback_graphics, flags & 0xff);
}
case EmfPlusRecordTypeObject:
{
return METAFILE_PlaybackObject(real_metafile, flags, dataSize, data);
}
case EmfPlusRecordTypeDrawImage:
{
EmfPlusDrawImage *draw = (EmfPlusDrawImage *)header;
BYTE image = flags & 0xff;
GpPointF points[3];
if (image >= EmfPlusObjectTableSize || real_metafile->objtable[image].type != ObjectTypeImage)
return InvalidParameter;
if (dataSize != FIELD_OFFSET(EmfPlusDrawImage, RectData) - sizeof(EmfPlusRecordHeader) +
(flags & 0x4000 ? sizeof(EmfPlusRect) : sizeof(EmfPlusRectF)))
return InvalidParameter;
if (draw->ImageAttributesID >= EmfPlusObjectTableSize ||
real_metafile->objtable[draw->ImageAttributesID].type != ObjectTypeImageAttributes)
return InvalidParameter;
if (flags & 0x4000) /* C */
{
points[0].X = draw->RectData.rect.X;
points[0].Y = draw->RectData.rect.Y;
points[1].X = points[0].X + draw->RectData.rect.Width;
points[1].Y = points[0].Y;
points[2].X = points[1].X;
points[2].Y = points[1].Y + draw->RectData.rect.Height;
}
else
{
points[0].X = draw->RectData.rectF.X;
points[0].Y = draw->RectData.rectF.Y;
points[1].X = points[0].X + draw->RectData.rectF.Width;
points[1].Y = points[0].Y;
points[2].X = points[1].X;
points[2].Y = points[1].Y + draw->RectData.rectF.Height;
}
return GdipDrawImagePointsRect(real_metafile->playback_graphics, real_metafile->objtable[image].u.image,
points, 3, draw->SrcRect.X, draw->SrcRect.Y, draw->SrcRect.Width, draw->SrcRect.Height, draw->SrcUnit,
real_metafile->objtable[draw->ImageAttributesID].u.image_attributes, NULL, NULL);
}
case EmfPlusRecordTypeDrawImagePoints:
{
EmfPlusDrawImagePoints *draw = (EmfPlusDrawImagePoints *)header;
static const UINT fixed_part_size = FIELD_OFFSET(EmfPlusDrawImagePoints, PointData) -
FIELD_OFFSET(EmfPlusDrawImagePoints, ImageAttributesID);
BYTE image = flags & 0xff;
GpPointF points[3];
unsigned int i;
UINT size;
if (image >= EmfPlusObjectTableSize || real_metafile->objtable[image].type != ObjectTypeImage)
return InvalidParameter;
if (dataSize <= fixed_part_size)
return InvalidParameter;
dataSize -= fixed_part_size;
if (draw->ImageAttributesID >= EmfPlusObjectTableSize ||
real_metafile->objtable[draw->ImageAttributesID].type != ObjectTypeImageAttributes)
return InvalidParameter;
if (draw->count != 3)
return InvalidParameter;
if ((flags >> 13) & 1) /* E */
FIXME("image effects are not supported.\n");
if ((flags >> 11) & 1) /* P */
size = sizeof(EmfPlusPointR7) * draw->count;
else if ((flags >> 14) & 1) /* C */
size = sizeof(EmfPlusPoint) * draw->count;
else
size = sizeof(EmfPlusPointF) * draw->count;
if (dataSize != size)
return InvalidParameter;
if ((flags >> 11) & 1) /* P */
{
points[0].X = draw->PointData.pointsR[0].X;
points[0].Y = draw->PointData.pointsR[0].Y;
for (i = 1; i < 3; i++)
{
points[i].X = points[i-1].X + draw->PointData.pointsR[i].X;
points[i].Y = points[i-1].Y + draw->PointData.pointsR[i].Y;
}
}
else if ((flags >> 14) & 1) /* C */
{
for (i = 0; i < 3; i++)
{
points[i].X = draw->PointData.points[i].X;
points[i].Y = draw->PointData.points[i].Y;
}
}
else
memcpy(points, draw->PointData.pointsF, sizeof(points));
return GdipDrawImagePointsRect(real_metafile->playback_graphics, real_metafile->objtable[image].u.image,
points, 3, draw->SrcRect.X, draw->SrcRect.Y, draw->SrcRect.Width, draw->SrcRect.Height, draw->SrcUnit,
real_metafile->objtable[draw->ImageAttributesID].u.image_attributes, NULL, NULL);
}
case EmfPlusRecordTypeFillPath:
{
EmfPlusFillPath *fill = (EmfPlusFillPath *)header;
GpSolidFill *solidfill = NULL;
BYTE path = flags & 0xff;
GpBrush *brush;
if (path >= EmfPlusObjectTableSize || real_metafile->objtable[path].type != ObjectTypePath)
return InvalidParameter;
if (dataSize != sizeof(fill->data.BrushId))
return InvalidParameter;
if (flags & 0x8000)
{
stat = GdipCreateSolidFill(fill->data.Color, &solidfill);
if (stat != Ok)
return stat;
brush = (GpBrush *)solidfill;
}
else
{
if (fill->data.BrushId >= EmfPlusObjectTableSize ||
real_metafile->objtable[fill->data.BrushId].type != ObjectTypeBrush)
return InvalidParameter;
brush = real_metafile->objtable[fill->data.BrushId].u.brush;
}
stat = GdipFillPath(real_metafile->playback_graphics, brush, real_metafile->objtable[path].u.path);
GdipDeleteBrush((GpBrush *)solidfill);
return stat;
}
case EmfPlusRecordTypeFillClosedCurve:
{
static const UINT fixed_part_size = FIELD_OFFSET(EmfPlusFillClosedCurve, PointData) -
sizeof(EmfPlusRecordHeader);
EmfPlusFillClosedCurve *fill = (EmfPlusFillClosedCurve *)header;
GpSolidFill *solidfill = NULL;
GpFillMode mode;
GpBrush *brush;
UINT size, i;
if (dataSize <= fixed_part_size)
return InvalidParameter;
if (fill->Count == 0)
return InvalidParameter;
if (flags & 0x800) /* P */
size = (fixed_part_size + sizeof(EmfPlusPointR7) * fill->Count + 3) & ~3;
else if (flags & 0x4000) /* C */
size = fixed_part_size + sizeof(EmfPlusPoint) * fill->Count;
else
size = fixed_part_size + sizeof(EmfPlusPointF) * fill->Count;
if (dataSize != size)
return InvalidParameter;
mode = flags & 0x200 ? FillModeWinding : FillModeAlternate; /* W */
if (flags & 0x8000) /* S */
{
stat = GdipCreateSolidFill(fill->BrushId, &solidfill);
if (stat != Ok)
return stat;
brush = (GpBrush *)solidfill;
}
else
{
if (fill->BrushId >= EmfPlusObjectTableSize ||
real_metafile->objtable[fill->BrushId].type != ObjectTypeBrush)
return InvalidParameter;
brush = real_metafile->objtable[fill->BrushId].u.brush;
}
if (flags & (0x800 | 0x4000))
{
GpPointF *points = GdipAlloc(fill->Count * sizeof(*points));
if (points)
{
if (flags & 0x800) /* P */
{
for (i = 1; i < fill->Count; i++)
{
points[i].X = points[i - 1].X + fill->PointData.pointsR[i].X;
points[i].Y = points[i - 1].Y + fill->PointData.pointsR[i].Y;
}
}
else
{
for (i = 0; i < fill->Count; i++)
{
points[i].X = fill->PointData.points[i].X;
points[i].Y = fill->PointData.points[i].Y;
}
}
stat = GdipFillClosedCurve2(real_metafile->playback_graphics, brush,
points, fill->Count, fill->Tension, mode);
GdipFree(points);
}
else
stat = OutOfMemory;
}
else
stat = GdipFillClosedCurve2(real_metafile->playback_graphics, brush,
(const GpPointF *)fill->PointData.pointsF, fill->Count, fill->Tension, mode);
GdipDeleteBrush((GpBrush *)solidfill);
return stat;
}
case EmfPlusRecordTypeFillEllipse:
{
EmfPlusFillEllipse *fill = (EmfPlusFillEllipse *)header;
GpSolidFill *solidfill = NULL;
GpBrush *brush;
if (dataSize <= FIELD_OFFSET(EmfPlusFillEllipse, RectData) - sizeof(EmfPlusRecordHeader))
return InvalidParameter;
dataSize -= FIELD_OFFSET(EmfPlusFillEllipse, RectData) - sizeof(EmfPlusRecordHeader);
if (dataSize != (flags & 0x4000 ? sizeof(EmfPlusRect) : sizeof(EmfPlusRectF)))
return InvalidParameter;
if (flags & 0x8000)
{
stat = GdipCreateSolidFill(fill->BrushId, &solidfill);
if (stat != Ok)
return stat;
brush = (GpBrush *)solidfill;
}
else
{
if (fill->BrushId >= EmfPlusObjectTableSize ||
real_metafile->objtable[fill->BrushId].type != ObjectTypeBrush)
return InvalidParameter;
brush = real_metafile->objtable[fill->BrushId].u.brush;
}
if (flags & 0x4000)
stat = GdipFillEllipseI(real_metafile->playback_graphics, brush, fill->RectData.rect.X,
fill->RectData.rect.Y, fill->RectData.rect.Width, fill->RectData.rect.Height);
else
stat = GdipFillEllipse(real_metafile->playback_graphics, brush, fill->RectData.rectF.X,
fill->RectData.rectF.Y, fill->RectData.rectF.Width, fill->RectData.rectF.Height);
GdipDeleteBrush((GpBrush *)solidfill);
return stat;
}
case EmfPlusRecordTypeFillPie:
{
EmfPlusFillPie *fill = (EmfPlusFillPie *)header;
GpSolidFill *solidfill = NULL;
GpBrush *brush;
if (dataSize <= FIELD_OFFSET(EmfPlusFillPie, RectData) - sizeof(EmfPlusRecordHeader))
return InvalidParameter;
dataSize -= FIELD_OFFSET(EmfPlusFillPie, RectData) - sizeof(EmfPlusRecordHeader);
if (dataSize != (flags & 0x4000 ? sizeof(EmfPlusRect) : sizeof(EmfPlusRectF)))
return InvalidParameter;
if (flags & 0x8000) /* S */
{
stat = GdipCreateSolidFill(fill->BrushId, &solidfill);
if (stat != Ok)
return stat;
brush = (GpBrush *)solidfill;
}
else
{
if (fill->BrushId >= EmfPlusObjectTableSize ||
real_metafile->objtable[fill->BrushId].type != ObjectTypeBrush)
return InvalidParameter;
brush = real_metafile->objtable[fill->BrushId].u.brush;
}
if (flags & 0x4000) /* C */
stat = GdipFillPieI(real_metafile->playback_graphics, brush, fill->RectData.rect.X,
fill->RectData.rect.Y, fill->RectData.rect.Width, fill->RectData.rect.Height,
fill->StartAngle, fill->SweepAngle);
else
stat = GdipFillPie(real_metafile->playback_graphics, brush, fill->RectData.rectF.X,
fill->RectData.rectF.Y, fill->RectData.rectF.Width, fill->RectData.rectF.Height,
fill->StartAngle, fill->SweepAngle);
GdipDeleteBrush((GpBrush *)solidfill);
return stat;
}
case EmfPlusRecordTypeDrawPath:
{
EmfPlusDrawPath *draw = (EmfPlusDrawPath *)header;
BYTE path = flags & 0xff;
if (dataSize != sizeof(draw->PenId))
return InvalidParameter;
if (path >= EmfPlusObjectTableSize || draw->PenId >= EmfPlusObjectTableSize)
return InvalidParameter;
if (real_metafile->objtable[path].type != ObjectTypePath ||
real_metafile->objtable[draw->PenId].type != ObjectTypePen)
return InvalidParameter;
return GdipDrawPath(real_metafile->playback_graphics, real_metafile->objtable[draw->PenId].u.pen,
real_metafile->objtable[path].u.path);
}
case EmfPlusRecordTypeDrawArc:
{
EmfPlusDrawArc *draw = (EmfPlusDrawArc *)header;
BYTE pen = flags & 0xff;
if (pen >= EmfPlusObjectTableSize || real_metafile->objtable[pen].type != ObjectTypePen)
return InvalidParameter;
if (dataSize != FIELD_OFFSET(EmfPlusDrawArc, RectData) - sizeof(EmfPlusRecordHeader) +
(flags & 0x4000 ? sizeof(EmfPlusRect) : sizeof(EmfPlusRectF)))
return InvalidParameter;
if (flags & 0x4000) /* C */
return GdipDrawArcI(real_metafile->playback_graphics, real_metafile->objtable[pen].u.pen,
draw->RectData.rect.X, draw->RectData.rect.Y, draw->RectData.rect.Width,
draw->RectData.rect.Height, draw->StartAngle, draw->SweepAngle);
else
return GdipDrawArc(real_metafile->playback_graphics, real_metafile->objtable[pen].u.pen,
draw->RectData.rectF.X, draw->RectData.rectF.Y, draw->RectData.rectF.Width,
draw->RectData.rectF.Height, draw->StartAngle, draw->SweepAngle);
}
case EmfPlusRecordTypeDrawEllipse:
{
EmfPlusDrawEllipse *draw = (EmfPlusDrawEllipse *)header;
BYTE pen = flags & 0xff;
if (pen >= EmfPlusObjectTableSize || real_metafile->objtable[pen].type != ObjectTypePen)
return InvalidParameter;
if (dataSize != (flags & 0x4000 ? sizeof(EmfPlusRect) : sizeof(EmfPlusRectF)))
return InvalidParameter;
if (flags & 0x4000) /* C */
return GdipDrawEllipseI(real_metafile->playback_graphics, real_metafile->objtable[pen].u.pen,
draw->RectData.rect.X, draw->RectData.rect.Y, draw->RectData.rect.Width,
draw->RectData.rect.Height);
else
return GdipDrawEllipse(real_metafile->playback_graphics, real_metafile->objtable[pen].u.pen,
draw->RectData.rectF.X, draw->RectData.rectF.Y, draw->RectData.rectF.Width,
draw->RectData.rectF.Height);
}
case EmfPlusRecordTypeDrawPie:
{
EmfPlusDrawPie *draw = (EmfPlusDrawPie *)header;
BYTE pen = flags & 0xff;
if (pen >= EmfPlusObjectTableSize || real_metafile->objtable[pen].type != ObjectTypePen)
return InvalidParameter;
if (dataSize != FIELD_OFFSET(EmfPlusDrawPie, RectData) - sizeof(EmfPlusRecordHeader) +
(flags & 0x4000 ? sizeof(EmfPlusRect) : sizeof(EmfPlusRectF)))
return InvalidParameter;
if (flags & 0x4000) /* C */
return GdipDrawPieI(real_metafile->playback_graphics, real_metafile->objtable[pen].u.pen,
draw->RectData.rect.X, draw->RectData.rect.Y, draw->RectData.rect.Width,
draw->RectData.rect.Height, draw->StartAngle, draw->SweepAngle);
else
return GdipDrawPie(real_metafile->playback_graphics, real_metafile->objtable[pen].u.pen,
draw->RectData.rectF.X, draw->RectData.rectF.Y, draw->RectData.rectF.Width,
draw->RectData.rectF.Height, draw->StartAngle, draw->SweepAngle);
}
case EmfPlusRecordTypeDrawRects:
{
EmfPlusDrawRects *draw = (EmfPlusDrawRects *)header;
BYTE pen = flags & 0xff;
GpRectF *rects = NULL;
if (pen >= EmfPlusObjectTableSize || real_metafile->objtable[pen].type != ObjectTypePen)
return InvalidParameter;
if (dataSize <= FIELD_OFFSET(EmfPlusDrawRects, RectData) - sizeof(EmfPlusRecordHeader))
return InvalidParameter;
dataSize -= FIELD_OFFSET(EmfPlusDrawRects, RectData) - sizeof(EmfPlusRecordHeader);
if (dataSize != draw->Count * (flags & 0x4000 ? sizeof(EmfPlusRect) : sizeof(EmfPlusRectF)))
return InvalidParameter;
if (flags & 0x4000)
{
DWORD i;
rects = GdipAlloc(draw->Count * sizeof(*rects));
if (!rects)
return OutOfMemory;
for (i = 0; i < draw->Count; i++)
{
rects[i].X = draw->RectData.rect[i].X;
rects[i].Y = draw->RectData.rect[i].Y;
rects[i].Width = draw->RectData.rect[i].Width;
rects[i].Height = draw->RectData.rect[i].Height;
}
}
stat = GdipDrawRectangles(real_metafile->playback_graphics, real_metafile->objtable[pen].u.pen,
rects ? rects : (GpRectF *)draw->RectData.rectF, draw->Count);
GdipFree(rects);
return stat;
}
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 GdipGetMetafileDownLevelRasterizationLimit(GDIPCONST GpMetafile *metafile,
UINT *limitDpi)
{
TRACE("(%p,%p)\n", metafile, limitDpi);
if (!metafile || !limitDpi)
return InvalidParameter;
if (!metafile->record_dc)
return WrongState;
*limitDpi = metafile->limit_dpi;
return Ok;
}
GpStatus WINGDIPAPI GdipSetMetafileDownLevelRasterizationLimit(GpMetafile *metafile,
UINT limitDpi)
{
TRACE("(%p,%u)\n", metafile, limitDpi);
if (limitDpi == 0)
limitDpi = 96;
if (!metafile || limitDpi < 10)
return InvalidParameter;
if (!metafile->record_dc)
return WrongState;
metafile->limit_dpi = 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 %s %d %s %p stub!\n", debugstr_w(fileName), hdc, type, debugstr_rectf(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 = attrs->outside_color;
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;
memcpy(draw_image_record->PointData.pointsF, points, 3 * sizeof(*points));
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_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;
}
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 = ((GpSolidFill *)brush)->color;
}
else
{
fill_path_record->Header.Flags = path_id;
fill_path_record->data.BrushId = brush_id;
}
METAFILE_WriteRecords(metafile);
return Ok;
}